diff --git a/src/components/layout/MainContent.tsx b/src/components/layout/MainContent.tsx index 0fc793f..1d4688e 100644 --- a/src/components/layout/MainContent.tsx +++ b/src/components/layout/MainContent.tsx @@ -79,6 +79,11 @@ const MailFlowRulesPage = lazyFeature( () => import('@/features/dlp/RulesPage'), (m) => m.MailFlowRulesPage, ); +// INBUXA: journaling (journaling spec, §3) +const JournalPage = lazyFeature( + () => import('@/features/journal/JournalPage'), + (m) => m.JournalPage, +); const HeldMailPage = lazyFeature( () => import('@/features/dlp/HeldMailPage'), (m) => m.HeldMailPage, @@ -240,6 +245,9 @@ function renderView(schema: Schema | null, viewName?: string, id?: string, secti if (componentName === 'MailFlowRules') { return ; } + if (componentName === 'Journal') { + return ; + } if (componentName === 'HeldMail') { return ; } diff --git a/src/features/dlp/IdPicker.tsx b/src/features/dlp/IdPicker.tsx new file mode 100644 index 0000000..8b32739 --- /dev/null +++ b/src/features/dlp/IdPicker.tsx @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2026 Coffey Labs + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * INBUXA: picking directory objects by id, each shown by its name: DLP + * rule conditions and journal scopes. + */ + +import { X } from 'lucide-react'; +import { ObjectPicker } from '@/components/common/ObjectPicker'; +import { useObjectLabel } from '@/lib/objectOptions'; +import { useSchemaStore } from '@/stores/schemaStore'; + +function IdChip({ labelObject, id, onRemove }: { labelObject: string; id: string; onRemove: () => void }) { + const schema = useSchemaStore((s) => s.schema); + const { label } = useObjectLabel(labelObject, id, schema!); + return ( + + {label ?? id} + + + ); +} + +/** Accounts, groups, domains or tenants, picked from the directory. */ +export function IdPicker({ + objectName, + labelObject, + ids, + onChange, + placeholder, +}: { + objectName: string; + labelObject: string; + ids: string[]; + onChange: (ids: string[]) => void; + placeholder: string; +}) { + const schema = useSchemaStore((s) => s.schema); + if (!schema) return null; + return ( +
+ {ids.map((id) => ( + onChange(ids.filter((x) => x !== id))} /> + ))} + { + if (id && !ids.includes(id)) onChange([...ids, id]); + }} + placeholder={placeholder} + /> +
+ ); +} diff --git a/src/features/dlp/RuleEditor.tsx b/src/features/dlp/RuleEditor.tsx index a9fd434..89eb968 100644 --- a/src/features/dlp/RuleEditor.tsx +++ b/src/features/dlp/RuleEditor.tsx @@ -28,9 +28,8 @@ import { DialogTitle, } from '@/components/ui/dialog'; import { toast } from '@/hooks/use-toast'; -import { ObjectPicker } from '@/components/common/ObjectPicker'; -import { useObjectLabel } from '@/lib/objectOptions'; -import { useSchemaStore } from '@/stores/schemaStore'; +import { IdPicker } from './IdPicker'; +import { JournalSelect } from '@/features/journal/JournalSelect'; import { saveRule } from './api'; import { DETECTORS, TEMPLATES, describeRule, type Action, type Condition, type Direction, type Rule } from './model'; @@ -185,53 +184,6 @@ function DetectorPicker({ ); } -function IdChip({ labelObject, id, onRemove }: { labelObject: string; id: string; onRemove: () => void }) { - const schema = useSchemaStore((s) => s.schema); - const { label } = useObjectLabel(labelObject, id, schema!); - return ( - - {label ?? id} - - - ); -} - -/** Groups or tenants, picked from the directory. */ -function IdPicker({ - objectName, - labelObject, - ids, - onChange, - placeholder, -}: { - objectName: string; - labelObject: string; - ids: string[]; - onChange: (ids: string[]) => void; - placeholder: string; -}) { - const schema = useSchemaStore((s) => s.schema); - if (!schema) return null; - return ( -
- {ids.map((id) => ( - onChange(ids.filter((x) => x !== id))} /> - ))} - { - if (id && !ids.includes(id)) onChange([...ids, id]); - }} - placeholder={placeholder} - /> -
- ); -} - function ConditionFields({ value, onChange }: { value: Condition; onChange: (c: Condition) => void }) { const { t } = useTranslation(); const listField = (items: string[], build: (items: string[]) => Condition, hint: string) => ( @@ -416,6 +368,7 @@ const TRANSPORT_ACTIONS: { type: Action['type']; label: string }[] = [ { type: 'addRecipient', label: 'Send a copy to…' }, { type: 'redirect', label: 'Send it to someone else instead' }, { type: 'route', label: 'Send it through a queue' }, + { type: 'journal', label: 'Journal it' }, { type: 'refuse', label: 'Refuse it' }, ]; @@ -435,6 +388,8 @@ function blankAction(type: Action['type']): Action { return { type, addresses: [] }; case 'route': return { type, queue: '' }; + case 'journal': + return { type, journal: '' }; case 'refuse': return { type, text: '' }; case 'block': @@ -539,6 +494,8 @@ function ActionFields({ value, onChange }: { value: Action; onChange: (a: Action onChange={(e) => onChange({ ...value, queue: e.target.value })} /> ); + case 'journal': + return onChange({ ...value, journal })} />; case 'refuse': return (