@open-mercato/core 0.8.1-develop.7458.1.3847638f67 → 0.9.1-develop.7459.1.99e3accd30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/availability/api/policies/route.js +6 -6
- package/dist/modules/availability/api/policies/route.js.map +2 -2
- package/dist/modules/availability/backend/availability/check/page.js +4 -3
- package/dist/modules/availability/backend/availability/check/page.js.map +2 -2
- package/dist/modules/availability/backend/availability/policies/create/page.js +3 -0
- package/dist/modules/availability/backend/availability/policies/create/page.js.map +2 -2
- package/dist/modules/availability/lib/organizationScope.js +24 -1
- package/dist/modules/availability/lib/organizationScope.js.map +2 -2
- package/dist/modules/catalog/api/categories/bulk-create/route.js +16 -2
- package/dist/modules/catalog/api/categories/bulk-create/route.js.map +2 -2
- package/dist/modules/catalog/api/products/bulk-create/route.js +16 -2
- package/dist/modules/catalog/api/products/bulk-create/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1 -0
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +2 -2
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id]/sessions/[sessionId].js +78 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/sessions/[sessionId].js.map +7 -0
- package/dist/modules/customer_groups/api/customer-groups/price-kinds/route.js +90 -0
- package/dist/modules/customer_groups/api/customer-groups/price-kinds/route.js.map +7 -0
- package/dist/modules/customer_groups/backend/customer-groups/page.js +5 -1
- package/dist/modules/customer_groups/backend/customer-groups/page.js.map +2 -2
- package/dist/modules/customer_groups/components/CustomerGroupTermsPriceKindField.js +2 -2
- package/dist/modules/customer_groups/components/CustomerGroupTermsPriceKindField.js.map +2 -2
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +3 -1
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
- package/dist/modules/customer_groups/lib/priceKindScope.js +35 -1
- package/dist/modules/customer_groups/lib/priceKindScope.js.map +2 -2
- package/dist/modules/customer_groups/widgets/injection/person-groups-explain-terms.js +1 -1
- package/dist/modules/customer_groups/widgets/injection/person-groups-explain-terms.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +14 -1
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +98 -87
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/forms/backend/forms/[id]/submissions/components/SubmissionDrawer.js +3 -3
- package/dist/modules/forms/backend/forms/[id]/submissions/components/SubmissionDrawer.js.map +2 -2
- package/dist/modules/forms/backend/forms/[id]/submissions/components/formSummary.js +9 -0
- package/dist/modules/forms/backend/forms/[id]/submissions/components/formSummary.js.map +7 -0
- package/dist/modules/forms/backend/forms/[id]/submissions/page.js +3 -6
- package/dist/modules/forms/backend/forms/[id]/submissions/page.js.map +2 -2
- package/dist/modules/forms/commands/distribution.js +7 -3
- package/dist/modules/forms/commands/distribution.js.map +2 -2
- package/dist/modules/forms/commands/form-version.js +9 -4
- package/dist/modules/forms/commands/form-version.js.map +2 -2
- package/dist/modules/forms/commands/form.js +9 -4
- package/dist/modules/forms/commands/form.js.map +2 -2
- package/dist/modules/forms/commands/invitation.js +7 -3
- package/dist/modules/forms/commands/invitation.js.map +2 -2
- package/dist/modules/forms/commands/submission.js +13 -6
- package/dist/modules/forms/commands/submission.js.map +2 -2
- package/package.json +8 -8
- package/src/modules/availability/api/policies/route.ts +10 -11
- package/src/modules/availability/backend/availability/check/page.tsx +38 -35
- package/src/modules/availability/backend/availability/policies/create/page.tsx +3 -0
- package/src/modules/availability/i18n/de.json +1 -0
- package/src/modules/availability/i18n/en.json +1 -0
- package/src/modules/availability/i18n/es.json +1 -0
- package/src/modules/availability/i18n/ko.json +1 -0
- package/src/modules/availability/i18n/pl.json +1 -0
- package/src/modules/availability/lib/organizationScope.ts +37 -0
- package/src/modules/catalog/api/categories/bulk-create/route.ts +16 -2
- package/src/modules/catalog/api/products/bulk-create/route.ts +16 -2
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +1 -0
- package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +2 -2
- package/src/modules/customer_accounts/AGENTS.md +1 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/sessions/[sessionId].ts +89 -0
- package/src/modules/customer_groups/api/customer-groups/price-kinds/route.ts +99 -0
- package/src/modules/customer_groups/backend/customer-groups/page.tsx +7 -1
- package/src/modules/customer_groups/components/CustomerGroupTermsPriceKindField.tsx +5 -4
- package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +8 -4
- package/src/modules/customer_groups/lib/priceKindScope.ts +43 -0
- package/src/modules/customer_groups/widgets/injection/person-groups-explain-terms.tsx +2 -2
- package/src/modules/customers/commands/companies.ts +16 -1
- package/src/modules/dictionaries/components/DictionariesManager.tsx +8 -1
- package/src/modules/eudr/i18n/de.json +1 -0
- package/src/modules/eudr/i18n/en.json +1 -0
- package/src/modules/eudr/i18n/es.json +1 -0
- package/src/modules/eudr/i18n/ko.json +1 -0
- package/src/modules/eudr/i18n/pl.json +1 -0
- package/src/modules/forms/backend/forms/[id]/submissions/components/SubmissionDrawer.tsx +6 -5
- package/src/modules/forms/backend/forms/[id]/submissions/components/formSummary.ts +19 -0
- package/src/modules/forms/backend/forms/[id]/submissions/page.tsx +4 -16
- package/src/modules/forms/commands/distribution.ts +7 -3
- package/src/modules/forms/commands/form-version.ts +9 -4
- package/src/modules/forms/commands/form.ts +9 -4
- package/src/modules/forms/commands/invitation.ts +7 -3
- package/src/modules/forms/commands/submission.ts +13 -6
- package/src/modules/forms/i18n/de.json +21 -0
- package/src/modules/forms/i18n/en.json +21 -0
- package/src/modules/forms/i18n/es.json +21 -0
- package/src/modules/forms/i18n/ko.json +21 -0
- package/src/modules/forms/i18n/pl.json +21 -0
- package/src/modules/notifications/AGENTS.md +1 -1
- package/src/modules/push_notifications/i18n/de.json +1 -0
- package/src/modules/push_notifications/i18n/en.json +1 -0
- package/src/modules/push_notifications/i18n/es.json +1 -0
- package/src/modules/push_notifications/i18n/ko.json +1 -0
- package/src/modules/push_notifications/i18n/pl.json +1 -0
- package/src/modules/warranty_claims/i18n/de.json +1 -0
- package/src/modules/warranty_claims/i18n/en.json +1 -0
- package/src/modules/warranty_claims/i18n/es.json +1 -0
- package/src/modules/warranty_claims/i18n/ko.json +1 -0
- package/src/modules/warranty_claims/i18n/pl.json +1 -0
- package/src/modules/wms/i18n/de.json +1 -0
- package/src/modules/wms/i18n/en.json +1 -0
- package/src/modules/wms/i18n/es.json +1 -0
- package/src/modules/wms/i18n/ko.json +1 -0
- package/src/modules/wms/i18n/pl.json +1 -0
package/dist/modules/forms/backend/forms/[id]/submissions/components/SubmissionDrawer.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/forms/backend/forms/%5Bid%5D/submissions/components/SubmissionDrawer.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { ArrowRight, History, Lock, Trash2 } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/forms/extension-points'\nimport { colorForSavedByRole } from './RowBadges'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\n\nexport const FORMS_DRAWER_REFRESH_EVENT = 'forms:submission-drawer:refresh'\n\nexport const SUBMISSION_DRAWER_HEADER_ACTIONS_SPOT = extensionPoints.hosts.submissionDrawerHeaderActions.spotId\nexport const SUBMISSION_DRAWER_ACCESS_AUDIT_SPOT = extensionPoints.hosts.submissionDrawerAccessAudit.spotId\nexport const SUBMISSION_DRAWER_FOOTER_SPOT = extensionPoints.hosts.submissionDrawerFooter.spotId\nexport const SUBMISSION_DRAWER_ANONYMIZE_ACTION_SPOT = extensionPoints.hosts.submissionDrawerAnonymizeAction.spotId\n\nexport type DrawerSubmission = {\n id: string\n status: 'draft' | 'submitted' | 'reopened' | 'archived' | 'anonymized'\n formVersionNumber: number\n subjectType: string\n subjectId: string\n anonymizedAt: string | null\n pdfSnapshotAttachmentId: string | null\n}\n\nexport type DrawerRevision = {\n id: string\n revisionNumber: number\n savedAt: string | null\n savedBy: string\n savedByRole: string | null\n changedFieldKeys: string[]\n anonymizedAt: string | null\n}\n\nexport type DrawerActor = {\n id: string\n userId: string\n role: string\n assignedAt: string | null\n revokedAt: string | null\n}\n\nexport type DrawerSubmissionDetail = {\n submission: DrawerSubmission\n revision: DrawerRevision\n decoded_data: Record<string, unknown>\n actors: DrawerActor[]\n}\n\nexport type DrawerInjectionContext = {\n submissionId: string\n formId: string\n status: DrawerSubmission['status']\n isAnonymized: boolean\n retryLastMutation: () => Promise<boolean>\n}\n\nexport type SubmissionDrawerProps = {\n formId: string\n submissionId: string\n formVersionRoles: string[]\n onClose: () => void\n onMutated?: () => void\n}\n\nexport function SubmissionDrawer({\n formId,\n submissionId,\n formVersionRoles,\n onClose,\n onMutated,\n}: SubmissionDrawerProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation({ contextId: 'forms.submission.drawer' })\n const [detail, setDetail] = React.useState<DrawerSubmissionDetail | null>(null)\n const [revisions, setRevisions] = React.useState<DrawerRevision[]>([])\n const [activeRevisionId, setActiveRevisionId] = React.useState<string | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [actorOpen, setActorOpen] = React.useState(false)\n const triggerRef = React.useRef<HTMLDivElement | null>(null)\n\n const reload = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n try {\n const [detailResp, revisionsResp] = await Promise.all([\n apiCall<DrawerSubmissionDetail>(`/api/forms/submissions/${encodeURIComponent(submissionId)}`),\n apiCall<{ items: DrawerRevision[] }>(\n `/api/forms/submissions/${encodeURIComponent(submissionId)}/revisions`,\n ),\n ])\n if (!detailResp.ok || !detailResp.result) {\n throw new Error(t('forms.errors.submission_not_found'))\n }\n if (!revisionsResp.ok || !revisionsResp.result) {\n throw new Error(t('forms.errors.internal'))\n }\n setDetail(detailResp.result)\n setRevisions(revisionsResp.result.items)\n setActiveRevisionId(detailResp.result.revision.id)\n } catch (err) {\n setError(err instanceof Error ? err.message : t('forms.errors.internal'))\n } finally {\n setIsLoading(false)\n }\n }, [submissionId, t])\n\n React.useEffect(() => {\n void reload()\n }, [reload])\n\n React.useEffect(() => {\n function handleRefresh() {\n onMutated?.()\n void reload()\n }\n window.addEventListener(FORMS_DRAWER_REFRESH_EVENT, handleRefresh)\n return () => window.removeEventListener(FORMS_DRAWER_REFRESH_EVENT, handleRefresh)\n }, [onMutated, reload])\n\n const handleReopen = React.useCallback(async () => {\n if (!detail) return\n const ok = await confirm({\n title: t('forms.drawer.reopen.confirm', { fallback: 'Reopen this submission?' }),\n description: t('forms.drawer.reopen.body', {\n fallback: 'Reopening allows further edits and creates a new revision when changes are saved.',\n }),\n })\n if (!ok) return\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(detail.submission.id)}/reopen`,\n { method: 'POST' },\n ),\n context: { submissionId: detail.submission.id },\n mutationPayload: { submissionId: detail.submission.id },\n })\n if (!resp.ok) {\n flash(t('forms.drawer.reopen.failed'), 'error')\n return\n }\n flash(t('forms.drawer.reopen.success'), 'success')\n onMutated?.()\n void reload()\n }, [confirm, detail, onMutated, reload, runMutation, t])\n\n const handleRevokeActor = React.useCallback(\n async (actor: DrawerActor) => {\n if (!detail) return\n const ok = await confirm({\n title: t('forms.actor.revoke.confirm', { fallback: 'Revoke this actor?' }),\n variant: 'destructive',\n })\n if (!ok) return\n // optimistic-lock-exempt: assignment row. `forms_form_submission_actor` is\n // a junction between a submission and a user; revoking stamps `revoked_at`\n // and is idempotent, so there is no user-editable field two actors can\n // clobber. Junction/assignment tables are exempt from the `updated_at`\n // requirement per packages/core/AGENTS.md \u00A7 Database Entities.\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(detail.submission.id)}/actors/${encodeURIComponent(actor.id)}`,\n { method: 'DELETE' },\n ),\n context: { submissionId: detail.submission.id, actorId: actor.id },\n mutationPayload: { submissionId: detail.submission.id, actorId: actor.id },\n })\n if (!resp.ok) {\n flash(t('forms.actor.revoke.failed'), 'error')\n return\n }\n flash(t('forms.actor.revoke.success'), 'success')\n onMutated?.()\n void reload()\n },\n [confirm, detail, onMutated, reload, runMutation, t],\n )\n\n if (isLoading) {\n return (\n <DrawerShell onClose={onClose} title={t('forms.drawer.loading', { fallback: 'Loading\u2026' })}>\n <div className=\"flex items-center justify-center py-12\">\n <Spinner className=\"h-5 w-5\" />\n </div>\n {ConfirmDialogElement}\n </DrawerShell>\n )\n }\n\n if (error || !detail) {\n return (\n <DrawerShell onClose={onClose} title={t('forms.drawer.error', { fallback: 'Error' })}>\n <Alert status=\"error\" style=\"light\">\n <AlertDescription>{error ?? t('forms.drawer.not_available')}</AlertDescription>\n </Alert>\n {ConfirmDialogElement}\n </DrawerShell>\n )\n }\n\n const isAnonymized = !!detail.submission.anonymizedAt\n const canReopen = detail.submission.status === 'submitted' && !isAnonymized\n const activeRevision = revisions.find((rev) => rev.id === activeRevisionId) ?? detail.revision\n const isViewingLatest = activeRevisionId === detail.revision.id\n const injectionContext: DrawerInjectionContext = {\n submissionId: detail.submission.id,\n formId,\n status: detail.submission.status,\n isAnonymized,\n retryLastMutation,\n }\n\n return (\n <DrawerShell\n ref={triggerRef}\n onClose={onClose}\n title={`${t('forms.drawer.title', { fallback: 'Submission' })} \u2014 v${detail.submission.formVersionNumber}`}\n headerExtras={\n <div className=\"flex items-center gap-2\">\n <Tag variant={isAnonymized ? 'error' : 'success'} dot>\n {detail.submission.status}\n </Tag>\n {canReopen ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleReopen}>\n {t('forms.drawer.actions.reopen', { fallback: 'Reopen' })}\n </Button>\n ) : null}\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_HEADER_ACTIONS_SPOT}\n context={injectionContext}\n />\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_ANONYMIZE_ACTION_SPOT}\n context={injectionContext}\n />\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-[200px_1fr_240px]\">\n <RevisionTimeline\n revisions={revisions}\n activeRevisionId={activeRevisionId ?? null}\n onSelect={setActiveRevisionId}\n />\n\n <section className=\"flex flex-col gap-3\">\n {!isViewingLatest ? (\n <div className=\"flex items-center justify-between rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n <span>\n {t('forms.drawer.replay_footer', {\n fallback: 'Viewing as of rev {n}',\n n: String(activeRevision.revisionNumber),\n })}\n </span>\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n onClick={() => setActiveRevisionId(detail.revision.id)}\n >\n {t('forms.drawer.jump_to_latest', { fallback: 'Jump to latest' })}\n </Button>\n </div>\n ) : null}\n\n {activeRevision.anonymizedAt ? (\n <Alert status=\"information\" style=\"light\">\n <AlertDescription>\n {t('forms.drawer.anonymized_revision', {\n fallback: 'This revision has been anonymized \u2014 content unavailable.',\n })}\n </AlertDescription>\n </Alert>\n ) : (\n <DataReadOnly data={detail.decoded_data} />\n )}\n\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_ACCESS_AUDIT_SPOT}\n context={injectionContext}\n />\n </section>\n\n <ActorPanel\n actors={detail.actors.filter((actor) => !actor.revokedAt)}\n formVersionRoles={formVersionRoles}\n submissionId={detail.submission.id}\n onAssigned={() => {\n setActorOpen(false)\n onMutated?.()\n void reload()\n }}\n onRevoke={handleRevokeActor}\n isOpen={actorOpen}\n onOpen={() => setActorOpen(true)}\n onClose={() => setActorOpen(false)}\n />\n </div>\n\n <footer className=\"mt-4 border-t border-border pt-3 text-xs text-muted-foreground\">\n <p>\n {t('forms.drawer.audit_note', {\n fallback: 'This view is being written to the access audit log \u2014 purpose: view.',\n })}\n </p>\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_FOOTER_SPOT}\n context={injectionContext}\n />\n </footer>\n\n {ConfirmDialogElement}\n </DrawerShell>\n )\n}\n\nconst DrawerShell = React.forwardRef<HTMLDivElement, {\n onClose: () => void\n title: string\n headerExtras?: React.ReactNode\n children: React.ReactNode\n}>(function DrawerShell({ onClose, title, headerExtras, children }, ref) {\n const t = useT()\n return (\n <Drawer open onOpenChange={(open) => { if (!open) onClose() }}>\n <DrawerContent\n ref={ref}\n className=\"max-w-3xl\"\n closeAriaLabel={t('forms.drawer.actions.close')}\n >\n <DrawerHeader className=\"items-center\">\n <DrawerTitle>{title}</DrawerTitle>\n {headerExtras ? <div className=\"flex items-center gap-2\">{headerExtras}</div> : null}\n </DrawerHeader>\n <DrawerBody>{children}</DrawerBody>\n </DrawerContent>\n </Drawer>\n )\n})\n\nfunction RevisionTimeline({\n revisions,\n activeRevisionId,\n onSelect,\n}: {\n revisions: DrawerRevision[]\n activeRevisionId: string | null\n onSelect: (id: string) => void\n}) {\n const t = useT()\n return (\n <nav\n aria-label={t('forms.drawer.timeline.title', { fallback: 'Revisions' })}\n className=\"flex flex-col gap-2\"\n >\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('forms.drawer.timeline.title', { fallback: 'Revisions' })}\n </h3>\n <ol className=\"flex flex-col gap-1\">\n {revisions.map((rev) => {\n const tone = colorForSavedByRole(rev.savedByRole)\n const active = rev.id === activeRevisionId\n return (\n <li key={rev.id}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSelect(rev.id)}\n className={`h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-xs ${\n active\n ? 'border-primary bg-primary/10'\n : 'border-border hover:bg-muted'\n }`}\n aria-current={active ? 'true' : undefined}\n >\n <span\n aria-hidden=\"true\"\n className={`h-2 w-2 rounded-full ${\n tone === 'primary'\n ? 'bg-primary'\n : tone === 'success'\n ? 'bg-status-success-solid'\n : tone === 'info'\n ? 'bg-status-info-solid'\n : tone === 'warning'\n ? 'bg-status-warning-solid'\n : 'bg-muted-foreground'\n }`}\n />\n <span className=\"font-mono\">#{rev.revisionNumber}</span>\n <span className=\"truncate text-muted-foreground\">\n {rev.savedByRole ?? 'system'}\n </span>\n {rev.anonymizedAt ? (\n <Lock className=\"ml-auto h-3 w-3 text-muted-foreground\" aria-hidden=\"true\" />\n ) : null}\n </Button>\n </li>\n )\n })}\n </ol>\n </nav>\n )\n}\n\nfunction DataReadOnly({ data }: { data: Record<string, unknown> }) {\n const entries = Object.entries(data)\n const t = useT()\n if (entries.length === 0) {\n return (\n <p className=\"rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground\">\n {t('forms.drawer.body.empty', { fallback: 'No answers recorded yet.' })}\n </p>\n )\n }\n return (\n <dl className=\"grid grid-cols-1 gap-2 rounded-md border border-border bg-card p-3 text-sm\">\n {entries.map(([key, value]) => (\n <div key={key} className=\"grid grid-cols-1 gap-1 sm:grid-cols-3 sm:items-baseline\">\n <dt className=\"font-mono text-xs text-muted-foreground\">{key}</dt>\n <dd className=\"col-span-1 text-foreground sm:col-span-2\">\n {typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n ? String(value)\n : JSON.stringify(value)}\n </dd>\n </div>\n ))}\n </dl>\n )\n}\n\nfunction ActorPanel({\n actors,\n formVersionRoles,\n submissionId,\n isOpen,\n onOpen,\n onClose,\n onAssigned,\n onRevoke,\n}: {\n actors: DrawerActor[]\n formVersionRoles: string[]\n submissionId: string\n isOpen: boolean\n onOpen: () => void\n onClose: () => void\n onAssigned: () => void\n onRevoke: (actor: DrawerActor) => void\n}) {\n const t = useT()\n const { runMutation } = useGuardedMutation({ contextId: 'forms.submission.actor.assign' })\n const [userId, setUserId] = React.useState('')\n const [role, setRole] = React.useState(formVersionRoles[0] ?? '')\n const [submitting, setSubmitting] = React.useState(false)\n\n const handleAssign = React.useCallback(async () => {\n if (!userId || !role) return\n setSubmitting(true)\n try {\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(submissionId)}/actors`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ user_id: userId, role }),\n },\n ),\n context: { submissionId, userId, role },\n mutationPayload: { submissionId, userId, role },\n })\n if (!resp.ok) {\n flash(t('forms.actor.assign.failed'), 'error')\n return\n }\n flash(t('forms.actor.assign.success'), 'success')\n setUserId('')\n onAssigned()\n } finally {\n setSubmitting(false)\n }\n }, [onAssigned, role, runMutation, submissionId, t, userId])\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleAssign()\n } else if (event.key === 'Escape') {\n event.preventDefault()\n onClose()\n }\n },\n [handleAssign, onClose],\n )\n\n return (\n <aside\n aria-label={t('forms.actor.panel.title', { fallback: 'Actors' })}\n className=\"flex flex-col gap-2\"\n >\n <header className=\"flex items-center justify-between\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('forms.actor.panel.title', { fallback: 'Actors' })}\n </h3>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onOpen}>\n {t('forms.actor.assign', { fallback: 'Assign' })}\n </Button>\n </header>\n <ul className=\"flex flex-col gap-1\">\n {actors.map((actor) => (\n <li\n key={actor.id}\n className=\"flex items-center justify-between rounded-md border border-border bg-card px-2 py-1.5 text-xs\"\n >\n <div className=\"flex flex-col\">\n <span className=\"font-mono text-muted-foreground\">{actor.userId.slice(0, 8)}\u2026</span>\n <Tag variant=\"info\" dot>\n {actor.role}\n </Tag>\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={t('forms.actor.revoke', { fallback: 'Revoke actor' })}\n onClick={() => onRevoke(actor)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" aria-hidden=\"true\" />\n </IconButton>\n </li>\n ))}\n </ul>\n {isOpen ? (\n <div\n className=\"rounded-md border border-border bg-muted/30 p-3\"\n onKeyDown={onKeyDown}\n >\n <FormField label={t('forms.actor.user_id', { fallback: 'User ID' })} required>\n <Input\n value={userId}\n onChange={(event) => setUserId(event.target.value)}\n placeholder=\"UUID\"\n />\n </FormField>\n <FormField label={t('forms.actor.role', { fallback: 'Role' })} required>\n <Select value={role} onValueChange={setRole}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {formVersionRoles.map((entry) => (\n <SelectItem key={entry} value={entry}>\n {entry}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n <div className=\"mt-2 flex justify-end gap-2\">\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={onClose}>\n {t('forms.actor.cancel', { fallback: 'Cancel' })}\n </Button>\n <Button type=\"button\" size=\"sm\" disabled={submitting || !userId || !role} onClick={handleAssign}>\n {t('forms.actor.assign', { fallback: 'Assign' })}{' '}\n <ArrowRight className=\"ml-1 h-3 w-3\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n ) : null}\n </aside>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { ArrowRight, History, Lock, Trash2 } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/forms/extension-points'\nimport { colorForSavedByRole } from './RowBadges'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\n\nexport const FORMS_DRAWER_REFRESH_EVENT = 'forms:submission-drawer:refresh'\n\nexport const SUBMISSION_DRAWER_HEADER_ACTIONS_SPOT = extensionPoints.hosts.submissionDrawerHeaderActions.spotId\nexport const SUBMISSION_DRAWER_ACCESS_AUDIT_SPOT = extensionPoints.hosts.submissionDrawerAccessAudit.spotId\nexport const SUBMISSION_DRAWER_FOOTER_SPOT = extensionPoints.hosts.submissionDrawerFooter.spotId\nexport const SUBMISSION_DRAWER_ANONYMIZE_ACTION_SPOT = extensionPoints.hosts.submissionDrawerAnonymizeAction.spotId\n\nexport type DrawerSubmission = {\n id: string\n status: 'draft' | 'submitted' | 'reopened' | 'archived' | 'anonymized'\n formVersionNumber?: number\n subjectType: string\n subjectId: string\n anonymizedAt: string | null\n pdfSnapshotAttachmentId: string | null\n}\n\nexport type DrawerRevision = {\n id: string\n revisionNumber: number\n savedAt: string | null\n savedBy: string\n savedByRole: string | null\n changedFieldKeys: string[]\n anonymizedAt: string | null\n}\n\nexport type DrawerActor = {\n id: string\n userId: string\n role: string\n assignedAt: string | null\n revokedAt: string | null\n}\n\nexport type DrawerSubmissionDetail = {\n submission: DrawerSubmission\n revision: DrawerRevision\n decoded_data: Record<string, unknown>\n actors: DrawerActor[]\n formVersion?: { id: string; versionNumber: number; roles: string[] }\n}\n\nexport type DrawerInjectionContext = {\n submissionId: string\n formId: string\n status: DrawerSubmission['status']\n isAnonymized: boolean\n retryLastMutation: () => Promise<boolean>\n}\n\nexport type SubmissionDrawerProps = {\n formId: string\n submissionId: string\n formVersionRoles: string[]\n onClose: () => void\n onMutated?: () => void\n}\n\nexport function SubmissionDrawer({\n formId,\n submissionId,\n formVersionRoles,\n onClose,\n onMutated,\n}: SubmissionDrawerProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation({ contextId: 'forms.submission.drawer' })\n const [detail, setDetail] = React.useState<DrawerSubmissionDetail | null>(null)\n const [revisions, setRevisions] = React.useState<DrawerRevision[]>([])\n const [activeRevisionId, setActiveRevisionId] = React.useState<string | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [actorOpen, setActorOpen] = React.useState(false)\n const triggerRef = React.useRef<HTMLDivElement | null>(null)\n\n const reload = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n try {\n const [detailResp, revisionsResp] = await Promise.all([\n apiCall<DrawerSubmissionDetail>(`/api/forms/submissions/${encodeURIComponent(submissionId)}`),\n apiCall<{ revisions?: DrawerRevision[] }>(\n `/api/forms/submissions/${encodeURIComponent(submissionId)}/revisions`,\n ),\n ])\n if (!detailResp.ok || !detailResp.result) {\n throw new Error(t('forms.errors.submission_not_found'))\n }\n if (!revisionsResp.ok || !revisionsResp.result) {\n throw new Error(t('forms.errors.internal'))\n }\n setDetail(detailResp.result)\n setRevisions(revisionsResp.result.revisions ?? [])\n setActiveRevisionId(detailResp.result.revision.id)\n } catch (err) {\n setError(err instanceof Error ? err.message : t('forms.errors.internal'))\n } finally {\n setIsLoading(false)\n }\n }, [submissionId, t])\n\n React.useEffect(() => {\n void reload()\n }, [reload])\n\n React.useEffect(() => {\n function handleRefresh() {\n onMutated?.()\n void reload()\n }\n window.addEventListener(FORMS_DRAWER_REFRESH_EVENT, handleRefresh)\n return () => window.removeEventListener(FORMS_DRAWER_REFRESH_EVENT, handleRefresh)\n }, [onMutated, reload])\n\n const handleReopen = React.useCallback(async () => {\n if (!detail) return\n const ok = await confirm({\n title: t('forms.drawer.reopen.confirm', { fallback: 'Reopen this submission?' }),\n description: t('forms.drawer.reopen.body', {\n fallback: 'Reopening allows further edits and creates a new revision when changes are saved.',\n }),\n })\n if (!ok) return\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(detail.submission.id)}/reopen`,\n { method: 'POST' },\n ),\n context: { submissionId: detail.submission.id },\n mutationPayload: { submissionId: detail.submission.id },\n })\n if (!resp.ok) {\n flash(t('forms.drawer.reopen.failed'), 'error')\n return\n }\n flash(t('forms.drawer.reopen.success'), 'success')\n onMutated?.()\n void reload()\n }, [confirm, detail, onMutated, reload, runMutation, t])\n\n const handleRevokeActor = React.useCallback(\n async (actor: DrawerActor) => {\n if (!detail) return\n const ok = await confirm({\n title: t('forms.actor.revoke.confirm', { fallback: 'Revoke this actor?' }),\n variant: 'destructive',\n })\n if (!ok) return\n // optimistic-lock-exempt: assignment row. `forms_form_submission_actor` is\n // a junction between a submission and a user; revoking stamps `revoked_at`\n // and is idempotent, so there is no user-editable field two actors can\n // clobber. Junction/assignment tables are exempt from the `updated_at`\n // requirement per packages/core/AGENTS.md \u00A7 Database Entities.\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(detail.submission.id)}/actors/${encodeURIComponent(actor.id)}`,\n { method: 'DELETE' },\n ),\n context: { submissionId: detail.submission.id, actorId: actor.id },\n mutationPayload: { submissionId: detail.submission.id, actorId: actor.id },\n })\n if (!resp.ok) {\n flash(t('forms.actor.revoke.failed'), 'error')\n return\n }\n flash(t('forms.actor.revoke.success'), 'success')\n onMutated?.()\n void reload()\n },\n [confirm, detail, onMutated, reload, runMutation, t],\n )\n\n if (isLoading) {\n return (\n <DrawerShell onClose={onClose} title={t('forms.drawer.loading', { fallback: 'Loading\u2026' })}>\n <div className=\"flex items-center justify-center py-12\">\n <Spinner className=\"h-5 w-5\" />\n </div>\n {ConfirmDialogElement}\n </DrawerShell>\n )\n }\n\n if (error || !detail) {\n return (\n <DrawerShell onClose={onClose} title={t('forms.drawer.error', { fallback: 'Error' })}>\n <Alert status=\"error\" style=\"light\">\n <AlertDescription>{error ?? t('forms.drawer.not_available')}</AlertDescription>\n </Alert>\n {ConfirmDialogElement}\n </DrawerShell>\n )\n }\n\n const isAnonymized = !!detail.submission.anonymizedAt\n const canReopen = detail.submission.status === 'submitted' && !isAnonymized\n const activeRevision = revisions.find((rev) => rev.id === activeRevisionId) ?? detail.revision\n const isViewingLatest = activeRevisionId === detail.revision.id\n const injectionContext: DrawerInjectionContext = {\n submissionId: detail.submission.id,\n formId,\n status: detail.submission.status,\n isAnonymized,\n retryLastMutation,\n }\n\n return (\n <DrawerShell\n ref={triggerRef}\n onClose={onClose}\n title={detail.formVersion ? `${t('forms.drawer.title', { fallback: 'Submission' })} \u2014 v${detail.formVersion.versionNumber}` : t('forms.drawer.title', { fallback: 'Submission' })}\n headerExtras={\n <div className=\"flex items-center gap-2\">\n <Tag variant={isAnonymized ? 'error' : 'success'} dot>\n {detail.submission.status}\n </Tag>\n {canReopen ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleReopen}>\n {t('forms.drawer.actions.reopen', { fallback: 'Reopen' })}\n </Button>\n ) : null}\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_HEADER_ACTIONS_SPOT}\n context={injectionContext}\n />\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_ANONYMIZE_ACTION_SPOT}\n context={injectionContext}\n />\n </div>\n }\n >\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-[200px_1fr_240px]\">\n <RevisionTimeline\n revisions={revisions}\n activeRevisionId={activeRevisionId ?? null}\n onSelect={setActiveRevisionId}\n />\n\n <section className=\"flex flex-col gap-3\">\n {!isViewingLatest ? (\n <div className=\"flex items-center justify-between rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n <span>\n {t('forms.drawer.replay_footer', {\n fallback: 'Viewing as of rev {n}',\n n: String(activeRevision.revisionNumber),\n })}\n </span>\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n onClick={() => setActiveRevisionId(detail.revision.id)}\n >\n {t('forms.drawer.jump_to_latest', { fallback: 'Jump to latest' })}\n </Button>\n </div>\n ) : null}\n\n {activeRevision.anonymizedAt ? (\n <Alert status=\"information\" style=\"light\">\n <AlertDescription>\n {t('forms.drawer.anonymized_revision', {\n fallback: 'This revision has been anonymized \u2014 content unavailable.',\n })}\n </AlertDescription>\n </Alert>\n ) : (\n <DataReadOnly data={detail.decoded_data} />\n )}\n\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_ACCESS_AUDIT_SPOT}\n context={injectionContext}\n />\n </section>\n\n <ActorPanel\n actors={detail.actors.filter((actor) => !actor.revokedAt)}\n formVersionRoles={detail.formVersion?.roles?.length ? detail.formVersion.roles : formVersionRoles}\n submissionId={detail.submission.id}\n onAssigned={() => {\n setActorOpen(false)\n onMutated?.()\n void reload()\n }}\n onRevoke={handleRevokeActor}\n isOpen={actorOpen}\n onOpen={() => setActorOpen(true)}\n onClose={() => setActorOpen(false)}\n />\n </div>\n\n <footer className=\"mt-4 border-t border-border pt-3 text-xs text-muted-foreground\">\n <p>\n {t('forms.drawer.audit_note', {\n fallback: 'This view is being written to the access audit log \u2014 purpose: view.',\n })}\n </p>\n <InjectionSpot\n spotId={SUBMISSION_DRAWER_FOOTER_SPOT}\n context={injectionContext}\n />\n </footer>\n\n {ConfirmDialogElement}\n </DrawerShell>\n )\n}\n\nconst DrawerShell = React.forwardRef<HTMLDivElement, {\n onClose: () => void\n title: string\n headerExtras?: React.ReactNode\n children: React.ReactNode\n}>(function DrawerShell({ onClose, title, headerExtras, children }, ref) {\n const t = useT()\n return (\n <Drawer open onOpenChange={(open) => { if (!open) onClose() }}>\n <DrawerContent\n ref={ref}\n className=\"max-w-3xl\"\n closeAriaLabel={t('forms.drawer.actions.close')}\n >\n <DrawerHeader className=\"items-center\">\n <DrawerTitle>{title}</DrawerTitle>\n {headerExtras ? <div className=\"flex items-center gap-2\">{headerExtras}</div> : null}\n </DrawerHeader>\n <DrawerBody>{children}</DrawerBody>\n </DrawerContent>\n </Drawer>\n )\n})\n\nfunction RevisionTimeline({\n revisions,\n activeRevisionId,\n onSelect,\n}: {\n revisions: DrawerRevision[]\n activeRevisionId: string | null\n onSelect: (id: string) => void\n}) {\n const t = useT()\n return (\n <nav\n aria-label={t('forms.drawer.timeline.title', { fallback: 'Revisions' })}\n className=\"flex flex-col gap-2\"\n >\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('forms.drawer.timeline.title', { fallback: 'Revisions' })}\n </h3>\n <ol className=\"flex flex-col gap-1\">\n {revisions.map((rev) => {\n const tone = colorForSavedByRole(rev.savedByRole)\n const active = rev.id === activeRevisionId\n return (\n <li key={rev.id}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSelect(rev.id)}\n className={`h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-xs ${\n active\n ? 'border-primary bg-primary/10'\n : 'border-border hover:bg-muted'\n }`}\n aria-current={active ? 'true' : undefined}\n >\n <span\n aria-hidden=\"true\"\n className={`h-2 w-2 rounded-full ${\n tone === 'primary'\n ? 'bg-primary'\n : tone === 'success'\n ? 'bg-status-success-solid'\n : tone === 'info'\n ? 'bg-status-info-solid'\n : tone === 'warning'\n ? 'bg-status-warning-solid'\n : 'bg-muted-foreground'\n }`}\n />\n <span className=\"font-mono\">#{rev.revisionNumber}</span>\n <span className=\"truncate text-muted-foreground\">\n {rev.savedByRole ?? 'system'}\n </span>\n {rev.anonymizedAt ? (\n <Lock className=\"ml-auto h-3 w-3 text-muted-foreground\" aria-hidden=\"true\" />\n ) : null}\n </Button>\n </li>\n )\n })}\n </ol>\n </nav>\n )\n}\n\nfunction DataReadOnly({ data }: { data: Record<string, unknown> }) {\n const entries = Object.entries(data)\n const t = useT()\n if (entries.length === 0) {\n return (\n <p className=\"rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground\">\n {t('forms.drawer.body.empty', { fallback: 'No answers recorded yet.' })}\n </p>\n )\n }\n return (\n <dl className=\"grid grid-cols-1 gap-2 rounded-md border border-border bg-card p-3 text-sm\">\n {entries.map(([key, value]) => (\n <div key={key} className=\"grid grid-cols-1 gap-1 sm:grid-cols-3 sm:items-baseline\">\n <dt className=\"font-mono text-xs text-muted-foreground\">{key}</dt>\n <dd className=\"col-span-1 text-foreground sm:col-span-2\">\n {typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n ? String(value)\n : JSON.stringify(value)}\n </dd>\n </div>\n ))}\n </dl>\n )\n}\n\nfunction ActorPanel({\n actors,\n formVersionRoles,\n submissionId,\n isOpen,\n onOpen,\n onClose,\n onAssigned,\n onRevoke,\n}: {\n actors: DrawerActor[]\n formVersionRoles: string[]\n submissionId: string\n isOpen: boolean\n onOpen: () => void\n onClose: () => void\n onAssigned: () => void\n onRevoke: (actor: DrawerActor) => void\n}) {\n const t = useT()\n const { runMutation } = useGuardedMutation({ contextId: 'forms.submission.actor.assign' })\n const [userId, setUserId] = React.useState('')\n const [role, setRole] = React.useState(formVersionRoles[0] ?? '')\n const [submitting, setSubmitting] = React.useState(false)\n\n const handleAssign = React.useCallback(async () => {\n if (!userId || !role) return\n setSubmitting(true)\n try {\n const resp = await runMutation({\n operation: () => apiCall(\n `/api/forms/submissions/${encodeURIComponent(submissionId)}/actors`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ user_id: userId, role }),\n },\n ),\n context: { submissionId, userId, role },\n mutationPayload: { submissionId, userId, role },\n })\n if (!resp.ok) {\n flash(t('forms.actor.assign.failed'), 'error')\n return\n }\n flash(t('forms.actor.assign.success'), 'success')\n setUserId('')\n onAssigned()\n } finally {\n setSubmitting(false)\n }\n }, [onAssigned, role, runMutation, submissionId, t, userId])\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleAssign()\n } else if (event.key === 'Escape') {\n event.preventDefault()\n onClose()\n }\n },\n [handleAssign, onClose],\n )\n\n return (\n <aside\n aria-label={t('forms.actor.panel.title', { fallback: 'Actors' })}\n className=\"flex flex-col gap-2\"\n >\n <header className=\"flex items-center justify-between\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('forms.actor.panel.title', { fallback: 'Actors' })}\n </h3>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onOpen}>\n {t('forms.actor.assign', { fallback: 'Assign' })}\n </Button>\n </header>\n <ul className=\"flex flex-col gap-1\">\n {actors.map((actor) => (\n <li\n key={actor.id}\n className=\"flex items-center justify-between rounded-md border border-border bg-card px-2 py-1.5 text-xs\"\n >\n <div className=\"flex flex-col\">\n <span className=\"font-mono text-muted-foreground\">{actor.userId.slice(0, 8)}\u2026</span>\n <Tag variant=\"info\" dot>\n {actor.role}\n </Tag>\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={t('forms.actor.revoke', { fallback: 'Revoke actor' })}\n onClick={() => onRevoke(actor)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" aria-hidden=\"true\" />\n </IconButton>\n </li>\n ))}\n </ul>\n {isOpen ? (\n <div\n className=\"rounded-md border border-border bg-muted/30 p-3\"\n onKeyDown={onKeyDown}\n >\n <FormField label={t('forms.actor.user_id', { fallback: 'User ID' })} required>\n <Input\n value={userId}\n onChange={(event) => setUserId(event.target.value)}\n placeholder=\"UUID\"\n />\n </FormField>\n <FormField label={t('forms.actor.role', { fallback: 'Role' })} required>\n <Select value={role} onValueChange={setRole}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {formVersionRoles.map((entry) => (\n <SelectItem key={entry} value={entry}>\n {entry}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n <div className=\"mt-2 flex justify-end gap-2\">\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={onClose}>\n {t('forms.actor.cancel', { fallback: 'Cancel' })}\n </Button>\n <Button type=\"button\" size=\"sm\" disabled={submitting || !userId || !role} onClick={handleAssign}>\n {t('forms.actor.assign', { fallback: 'Assign' })}{' '}\n <ArrowRight className=\"ml-1 h-3 w-3\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n ) : null}\n </aside>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["DrawerShell"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
function resolvePublishedVersionRoles(summary) {
|
|
2
|
+
if (!summary?.versions || !summary.currentPublishedVersionId) return [];
|
|
3
|
+
const published = summary.versions.find((version) => version.id === summary.currentPublishedVersionId);
|
|
4
|
+
return Array.isArray(published?.roles) ? published.roles : [];
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
resolvePublishedVersionRoles
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=formSummary.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/forms/backend/forms/%5Bid%5D/submissions/components/formSummary.ts"],
|
|
4
|
+
"sourcesContent": ["export type FormSummaryVersion = {\n id: string\n status: string\n versionNumber: number\n roles?: string[]\n}\n\nexport type FormSummaryResponse = {\n id: string\n name: string\n currentPublishedVersionId: string | null\n versions?: FormSummaryVersion[]\n}\n\nexport function resolvePublishedVersionRoles(summary: FormSummaryResponse | null | undefined): string[] {\n if (!summary?.versions || !summary.currentPublishedVersionId) return []\n const published = summary.versions.find((version) => version.id === summary.currentPublishedVersionId)\n return Array.isArray(published?.roles) ? published.roles : []\n}\n"],
|
|
5
|
+
"mappings": "AAcO,SAAS,6BAA6B,SAA2D;AACtG,MAAI,CAAC,SAAS,YAAY,CAAC,QAAQ,0BAA2B,QAAO,CAAC;AACtE,QAAM,YAAY,QAAQ,SAAS,KAAK,CAAC,YAAY,QAAQ,OAAO,QAAQ,yBAAyB;AACrG,SAAO,MAAM,QAAQ,WAAW,KAAK,IAAI,UAAU,QAAQ,CAAC;AAC9D;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
ROW_BADGE_TONE
|
|
15
15
|
} from "./components/RowBadges.js";
|
|
16
16
|
import { SubmissionDrawer } from "./components/SubmissionDrawer.js";
|
|
17
|
+
import { resolvePublishedVersionRoles } from "./components/formSummary.js";
|
|
17
18
|
function formatDate(value) {
|
|
18
19
|
if (!value) return "";
|
|
19
20
|
const date = new Date(value);
|
|
@@ -96,11 +97,7 @@ function FormSubmissionInboxPage({ params }) {
|
|
|
96
97
|
};
|
|
97
98
|
}, [filterValues, formId, page, reloadToken, scopeVersion, search, t]);
|
|
98
99
|
const reload = React.useCallback(() => setReloadToken((token) => token + 1), []);
|
|
99
|
-
const formVersionRoles = React.useMemo(() =>
|
|
100
|
-
if (!formSummary?.versions) return [];
|
|
101
|
-
const published = formSummary.versions.find((v) => v.id === formSummary.form.currentPublishedVersionId);
|
|
102
|
-
return published?.roles ?? [];
|
|
103
|
-
}, [formSummary]);
|
|
100
|
+
const formVersionRoles = React.useMemo(() => resolvePublishedVersionRoles(formSummary), [formSummary]);
|
|
104
101
|
const columns = React.useMemo(
|
|
105
102
|
() => [
|
|
106
103
|
{
|
|
@@ -168,7 +165,7 @@ function FormSubmissionInboxPage({ params }) {
|
|
|
168
165
|
/* @__PURE__ */ jsx(
|
|
169
166
|
DataTable,
|
|
170
167
|
{
|
|
171
|
-
title: formSummary?.
|
|
168
|
+
title: formSummary?.name ? `${formSummary.name} \u2014 ${t("forms.inbox.title", { fallback: "Submissions" })}` : t("forms.inbox.title", { fallback: "Submissions" }),
|
|
172
169
|
columns,
|
|
173
170
|
data: rows,
|
|
174
171
|
isLoading,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/forms/backend/forms/%5Bid%5D/submissions/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { File as FileIcon, Lock, Users } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport {\n fromSubmission,\n ROW_BADGE_TONE,\n type SubmissionInboxRow,\n} from './components/RowBadges'\nimport { SubmissionDrawer } from './components/SubmissionDrawer'\n\ntype InboxResponse = {\n items: Array<SubmissionInboxRow & {\n subjectType: string\n subjectId: string\n submittedAt: string | null\n updatedAt: string\n locale: string | null\n }>\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\ntype FormSummaryResponse = {\n form: {\n id: string\n name: string\n currentPublishedVersionId: string | null\n }\n versions?: Array<{ id: string; status: string; roles: string[]; versionNumber: number }>\n}\n\nfunction formatDate(value: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleString()\n}\n\nexport default function FormSubmissionInboxPage({ params }: { params?: { id?: string } }) {\n const formId = params?.id ?? ''\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n\n const [rows, setRows] = React.useState<InboxResponse['items']>([])\n const [total, setTotal] = React.useState(0)\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [openSubmissionId, setOpenSubmissionId] = React.useState<string | null>(null)\n const [formSummary, setFormSummary] = React.useState<FormSummaryResponse | null>(null)\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'status',\n label: t('forms.inbox.filter.status', { fallback: 'Status' }),\n type: 'select',\n options: [\n { value: 'draft', label: t('forms.submission.status.draft', { fallback: 'Draft' }) },\n { value: 'submitted', label: t('forms.submission.status.submitted', { fallback: 'Submitted' }) },\n { value: 'reopened', label: t('forms.submission.status.reopened', { fallback: 'Reopened' }) },\n { value: 'archived', label: t('forms.submission.status.archived', { fallback: 'Archived' }) },\n { value: 'anonymized', label: t('forms.submission.status.anonymized', { fallback: 'Anonymized' }) },\n ],\n multi: true,\n },\n ],\n [t],\n )\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '20')\n if (search.trim()) params.set('q', search.trim())\n const statusValue = filterValues.status\n if (Array.isArray(statusValue) && statusValue.length > 0) {\n params.set('status', statusValue.join(','))\n } else if (typeof statusValue === 'string' && statusValue) {\n params.set('status', statusValue)\n }\n try {\n const [listResp, summaryResp] = await Promise.all([\n apiCall<InboxResponse>(`/api/forms/${encodeURIComponent(formId)}/submissions?${params.toString()}`),\n apiCall<FormSummaryResponse>(`/api/forms/${encodeURIComponent(formId)}`),\n ])\n if (cancelled) return\n if (!listResp.ok || !listResp.result) {\n flash(t('forms.errors.internal'), 'error')\n return\n }\n setRows(listResp.result.items)\n setTotal(listResp.result.total)\n setTotalPages(listResp.result.totalPages || 1)\n if (summaryResp.ok && summaryResp.result) {\n setFormSummary(summaryResp.result)\n }\n } catch {\n if (!cancelled) {\n flash(t('forms.errors.internal'), 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n void load()\n return () => {\n cancelled = true\n }\n }, [filterValues, formId, page, reloadToken, scopeVersion, search, t])\n\n const reload = React.useCallback(() => setReloadToken((token) => token + 1), [])\n\n const formVersionRoles = React.useMemo(() => {\n if (!formSummary?.versions) return []\n const published = formSummary.versions.find((v) => v.id === formSummary.form.currentPublishedVersionId)\n return published?.roles ?? []\n }, [formSummary])\n\n const columns = React.useMemo<ColumnDef<InboxResponse['items'][number]>[]>(\n () => [\n {\n header: t('forms.inbox.columns.subject', { fallback: 'Subject' }),\n accessorKey: 'subjectId',\n cell: ({ row }) => {\n const isAnonymized = !!row.original.anonymizedAt\n return (\n <span className={`font-mono text-xs ${isAnonymized ? 'italic text-muted-foreground' : 'text-foreground'}`}>\n {row.original.subjectType}: {row.original.subjectId.slice(0, 8)}\u2026\n </span>\n )\n },\n meta: { truncate: true, maxWidth: 240 },\n },\n {\n header: t('forms.inbox.columns.status', { fallback: 'Status' }),\n accessorKey: 'status',\n cell: ({ row }) => {\n const badges = fromSubmission(row.original)\n return (\n <div className=\"flex flex-wrap items-center gap-1\">\n {badges.map((badge, index) => {\n if (badge.kind === 'status') {\n return (\n <Tag key={`status-${index}`} variant={ROW_BADGE_TONE[badge.status]} dot>\n {t(`forms.submission.status.${badge.status}`, { fallback: badge.status })}\n </Tag>\n )\n }\n if (badge.kind === 'version') {\n return (\n <Tag key={`version-${index}`} variant=\"neutral\">\n v{badge.versionNumber}\n </Tag>\n )\n }\n if (badge.kind === 'revision_count') {\n return (\n <Tag key={`rev-${index}`} variant=\"neutral\">\n {badge.count} rev\n </Tag>\n )\n }\n if (badge.kind === 'multi_role') {\n return (\n <Tag key={`multirole-${index}`} variant=\"info\" aria-label={t('forms.inbox.badges.multiple_roles')}>\n <Users className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n if (badge.kind === 'pdf_available') {\n return (\n <Tag key={`pdf-${index}`} variant=\"success\" aria-label={t('forms.inbox.badges.pdf_available')}>\n <FileIcon className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n if (badge.kind === 'anonymized') {\n return (\n <Tag key={`anon-${index}`} variant=\"error\" aria-label={t('forms.submission.status.anonymized')}>\n <Lock className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n return null\n })}\n </div>\n )\n },\n },\n {\n header: t('forms.inbox.columns.submitted_at', { fallback: 'Submitted' }),\n accessorKey: 'submittedAt',\n cell: ({ row }) => formatDate(row.original.submittedAt) || '\u2014',\n },\n {\n header: t('forms.inbox.columns.updated_at', { fallback: 'Updated' }),\n accessorKey: 'updatedAt',\n cell: ({ row }) => formatDate(row.original.updatedAt),\n },\n ],\n [t],\n )\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={\n formSummary?.form?.name\n ? `${formSummary.form.name} \u2014 ${t('forms.inbox.title', { fallback: 'Submissions' })}`\n : t('forms.inbox.title', { fallback: 'Submissions' })\n }\n columns={columns}\n data={rows}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder={t('forms.inbox.search.placeholder', { fallback: 'Search by subject id' })}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={setFilterValues}\n onFiltersClear={() => setFilterValues({})}\n pagination={{\n page,\n pageSize: 20,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n onRowClick={(row) => setOpenSubmissionId(row.id)}\n emptyState={t('forms.inbox.empty', { fallback: 'No submissions yet.' })}\n />\n {openSubmissionId ? (\n <SubmissionDrawer\n formId={formId}\n submissionId={openSubmissionId}\n formVersionRoles={formVersionRoles}\n onClose={() => setOpenSubmissionId(null)}\n onMutated={reload}\n />\n ) : null}\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { File as FileIcon, Lock, Users } from 'lucide-react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport {\n fromSubmission,\n ROW_BADGE_TONE,\n type SubmissionInboxRow,\n} from './components/RowBadges'\nimport { SubmissionDrawer } from './components/SubmissionDrawer'\nimport { resolvePublishedVersionRoles, type FormSummaryResponse } from './components/formSummary'\n\ntype InboxResponse = {\n items: Array<SubmissionInboxRow & {\n subjectType: string\n subjectId: string\n submittedAt: string | null\n updatedAt: string\n locale: string | null\n }>\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nfunction formatDate(value: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleString()\n}\n\nexport default function FormSubmissionInboxPage({ params }: { params?: { id?: string } }) {\n const formId = params?.id ?? ''\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n\n const [rows, setRows] = React.useState<InboxResponse['items']>([])\n const [total, setTotal] = React.useState(0)\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [openSubmissionId, setOpenSubmissionId] = React.useState<string | null>(null)\n const [formSummary, setFormSummary] = React.useState<FormSummaryResponse | null>(null)\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'status',\n label: t('forms.inbox.filter.status', { fallback: 'Status' }),\n type: 'select',\n options: [\n { value: 'draft', label: t('forms.submission.status.draft', { fallback: 'Draft' }) },\n { value: 'submitted', label: t('forms.submission.status.submitted', { fallback: 'Submitted' }) },\n { value: 'reopened', label: t('forms.submission.status.reopened', { fallback: 'Reopened' }) },\n { value: 'archived', label: t('forms.submission.status.archived', { fallback: 'Archived' }) },\n { value: 'anonymized', label: t('forms.submission.status.anonymized', { fallback: 'Anonymized' }) },\n ],\n multi: true,\n },\n ],\n [t],\n )\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '20')\n if (search.trim()) params.set('q', search.trim())\n const statusValue = filterValues.status\n if (Array.isArray(statusValue) && statusValue.length > 0) {\n params.set('status', statusValue.join(','))\n } else if (typeof statusValue === 'string' && statusValue) {\n params.set('status', statusValue)\n }\n try {\n const [listResp, summaryResp] = await Promise.all([\n apiCall<InboxResponse>(`/api/forms/${encodeURIComponent(formId)}/submissions?${params.toString()}`),\n apiCall<FormSummaryResponse>(`/api/forms/${encodeURIComponent(formId)}`),\n ])\n if (cancelled) return\n if (!listResp.ok || !listResp.result) {\n flash(t('forms.errors.internal'), 'error')\n return\n }\n setRows(listResp.result.items)\n setTotal(listResp.result.total)\n setTotalPages(listResp.result.totalPages || 1)\n if (summaryResp.ok && summaryResp.result) {\n setFormSummary(summaryResp.result)\n }\n } catch {\n if (!cancelled) {\n flash(t('forms.errors.internal'), 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n void load()\n return () => {\n cancelled = true\n }\n }, [filterValues, formId, page, reloadToken, scopeVersion, search, t])\n\n const reload = React.useCallback(() => setReloadToken((token) => token + 1), [])\n\n const formVersionRoles = React.useMemo(() => resolvePublishedVersionRoles(formSummary), [formSummary])\n\n const columns = React.useMemo<ColumnDef<InboxResponse['items'][number]>[]>(\n () => [\n {\n header: t('forms.inbox.columns.subject', { fallback: 'Subject' }),\n accessorKey: 'subjectId',\n cell: ({ row }) => {\n const isAnonymized = !!row.original.anonymizedAt\n return (\n <span className={`font-mono text-xs ${isAnonymized ? 'italic text-muted-foreground' : 'text-foreground'}`}>\n {row.original.subjectType}: {row.original.subjectId.slice(0, 8)}\u2026\n </span>\n )\n },\n meta: { truncate: true, maxWidth: 240 },\n },\n {\n header: t('forms.inbox.columns.status', { fallback: 'Status' }),\n accessorKey: 'status',\n cell: ({ row }) => {\n const badges = fromSubmission(row.original)\n return (\n <div className=\"flex flex-wrap items-center gap-1\">\n {badges.map((badge, index) => {\n if (badge.kind === 'status') {\n return (\n <Tag key={`status-${index}`} variant={ROW_BADGE_TONE[badge.status]} dot>\n {t(`forms.submission.status.${badge.status}`, { fallback: badge.status })}\n </Tag>\n )\n }\n if (badge.kind === 'version') {\n return (\n <Tag key={`version-${index}`} variant=\"neutral\">\n v{badge.versionNumber}\n </Tag>\n )\n }\n if (badge.kind === 'revision_count') {\n return (\n <Tag key={`rev-${index}`} variant=\"neutral\">\n {badge.count} rev\n </Tag>\n )\n }\n if (badge.kind === 'multi_role') {\n return (\n <Tag key={`multirole-${index}`} variant=\"info\" aria-label={t('forms.inbox.badges.multiple_roles')}>\n <Users className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n if (badge.kind === 'pdf_available') {\n return (\n <Tag key={`pdf-${index}`} variant=\"success\" aria-label={t('forms.inbox.badges.pdf_available')}>\n <FileIcon className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n if (badge.kind === 'anonymized') {\n return (\n <Tag key={`anon-${index}`} variant=\"error\" aria-label={t('forms.submission.status.anonymized')}>\n <Lock className=\"h-3 w-3\" aria-hidden=\"true\" />\n </Tag>\n )\n }\n return null\n })}\n </div>\n )\n },\n },\n {\n header: t('forms.inbox.columns.submitted_at', { fallback: 'Submitted' }),\n accessorKey: 'submittedAt',\n cell: ({ row }) => formatDate(row.original.submittedAt) || '\u2014',\n },\n {\n header: t('forms.inbox.columns.updated_at', { fallback: 'Updated' }),\n accessorKey: 'updatedAt',\n cell: ({ row }) => formatDate(row.original.updatedAt),\n },\n ],\n [t],\n )\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={\n formSummary?.name\n ? `${formSummary.name} \u2014 ${t('forms.inbox.title', { fallback: 'Submissions' })}`\n : t('forms.inbox.title', { fallback: 'Submissions' })\n }\n columns={columns}\n data={rows}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder={t('forms.inbox.search.placeholder', { fallback: 'Search by subject id' })}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={setFilterValues}\n onFiltersClear={() => setFilterValues({})}\n pagination={{\n page,\n pageSize: 20,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n onRowClick={(row) => setOpenSubmissionId(row.id)}\n emptyState={t('forms.inbox.empty', { fallback: 'No submissions yet.' })}\n />\n {openSubmissionId ? (\n <SubmissionDrawer\n formId={formId}\n submissionId={openSubmissionId}\n formVersionRoles={formVersionRoles}\n onClose={() => setOpenSubmissionId(null)}\n onMutated={reload}\n />\n ) : null}\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["params"]
|
|
7
7
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
2
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
2
3
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
4
|
import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
4
5
|
import { FormDistribution } from "../data/entities.js";
|
|
@@ -160,9 +161,10 @@ const createDistributionCommand = {
|
|
|
160
161
|
return distribution ? serializeDistributionSnapshot(distribution) : null;
|
|
161
162
|
},
|
|
162
163
|
buildLog: async ({ result, snapshots }) => {
|
|
164
|
+
const { translate } = await resolveTranslations();
|
|
163
165
|
const after = snapshots.after;
|
|
164
166
|
return {
|
|
165
|
-
actionLabel: "forms.audit.distribution.create",
|
|
167
|
+
actionLabel: translate("forms.audit.distribution.create", "Create distribution"),
|
|
166
168
|
resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,
|
|
167
169
|
resourceId: result.distributionId,
|
|
168
170
|
tenantId: after?.tenantId ?? null,
|
|
@@ -284,10 +286,11 @@ const updateDistributionCommand = {
|
|
|
284
286
|
return distribution ? serializeDistributionSnapshot(distribution) : null;
|
|
285
287
|
},
|
|
286
288
|
buildLog: async ({ result, snapshots }) => {
|
|
289
|
+
const { translate } = await resolveTranslations();
|
|
287
290
|
const before = snapshots.before;
|
|
288
291
|
const after = snapshots.after;
|
|
289
292
|
return {
|
|
290
|
-
actionLabel: "forms.audit.distribution.update",
|
|
293
|
+
actionLabel: translate("forms.audit.distribution.update", "Update distribution"),
|
|
291
294
|
resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,
|
|
292
295
|
resourceId: result.distributionId,
|
|
293
296
|
tenantId: after?.tenantId ?? before?.tenantId ?? null,
|
|
@@ -372,10 +375,11 @@ const closeDistributionCommand = {
|
|
|
372
375
|
return distribution ? serializeDistributionSnapshot(distribution) : null;
|
|
373
376
|
},
|
|
374
377
|
buildLog: async ({ result, snapshots }) => {
|
|
378
|
+
const { translate } = await resolveTranslations();
|
|
375
379
|
const before = snapshots.before;
|
|
376
380
|
const after = snapshots.after;
|
|
377
381
|
return {
|
|
378
|
-
actionLabel: "forms.audit.distribution.close",
|
|
382
|
+
actionLabel: translate("forms.audit.distribution.close", "Close distribution"),
|
|
379
383
|
resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,
|
|
380
384
|
resourceId: result.distributionId,
|
|
381
385
|
tenantId: after?.tenantId ?? before?.tenantId ?? null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/forms/commands/distribution.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { FormDistribution } from '../data/entities'\nimport {\n distributionCloseCommandSchema,\n distributionCreateCommandSchema,\n distributionUpdateCommandSchema,\n type FormDistributionCloseCommandInput,\n type FormDistributionCreateCommandInput,\n type FormDistributionUpdateCommandInput,\n} from '../data/validators'\nimport { generatePublicSlug } from '../services/distribution-token'\nimport {\n FORMS_CACHE_TAGS,\n emitForms,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n findFormInScope,\n invalidateFormsCacheTags,\n resolveActorUserId,\n resolveEntityManager,\n} from './shared'\n\nexport const FORM_DISTRIBUTION_RESOURCE_KIND = 'forms.distribution'\n\n// ----------------------------------------------------------------------------\n// Snapshot + undo payloads\n// ----------------------------------------------------------------------------\n\nexport type FormDistributionSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n formId: string\n pinnedVersionId: string | null\n mode: 'open' | 'personal'\n publicSlug: string | null\n status: 'active' | 'paused' | 'closed'\n title: string | null\n defaultLocale: string\n requireCustomerAuth: boolean\n allowMultipleSubmissions: boolean\n maxResponses: number | null\n responseCount: number\n opensAt: string | null\n closesAt: string | null\n redirectUrl: string | null\n settings: Record<string, unknown> | null\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\ntype FormDistributionUndoPayload = {\n before?: FormDistributionSnapshot | null\n after?: FormDistributionSnapshot | null\n}\n\nexport function serializeDistributionSnapshot(distribution: FormDistribution): FormDistributionSnapshot {\n return {\n id: distribution.id,\n organizationId: distribution.organizationId,\n tenantId: distribution.tenantId,\n formId: distribution.formId,\n pinnedVersionId: distribution.pinnedVersionId ?? null,\n mode: distribution.mode,\n publicSlug: distribution.publicSlug ?? null,\n status: distribution.status,\n title: distribution.title ?? null,\n defaultLocale: distribution.defaultLocale,\n requireCustomerAuth: distribution.requireCustomerAuth,\n allowMultipleSubmissions: distribution.allowMultipleSubmissions,\n maxResponses: distribution.maxResponses ?? null,\n responseCount: distribution.responseCount,\n opensAt: distribution.opensAt ? distribution.opensAt.toISOString() : null,\n closesAt: distribution.closesAt ? distribution.closesAt.toISOString() : null,\n redirectUrl: distribution.redirectUrl ?? null,\n settings: distribution.settings ?? null,\n createdBy: distribution.createdBy,\n createdAt: distribution.createdAt.toISOString(),\n updatedAt: distribution.updatedAt.toISOString(),\n }\n}\n\n// ----------------------------------------------------------------------------\n// Cache tags + helpers\n// ----------------------------------------------------------------------------\n\nconst DISTRIBUTION_CACHE_TAGS = {\n distribution: (distributionId: string) => `forms.distribution:${distributionId}`,\n distributionList: (formId: string) => `forms.distribution.list:${formId}`,\n} as const\n\nfunction isUniqueViolation(error: unknown, constraint: string): boolean {\n if (!error || typeof error !== 'object') return false\n const candidate = error as Record<string, unknown>\n const code = candidate.code\n const detail = typeof candidate.detail === 'string' ? candidate.detail : ''\n const constraintName = typeof candidate.constraint === 'string' ? candidate.constraint : ''\n return code === '23505' && (constraintName === constraint || detail.includes(constraint))\n}\n\nasync function findDistributionInScope(\n em: EntityManager,\n distributionId: string,\n tenantId: string,\n organizationId: string,\n): Promise<FormDistribution> {\n const distribution = await em.findOne(FormDistribution, {\n id: distributionId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (!distribution) {\n throw new CrudHttpError(404, { error: 'forms.errors.distribution_not_found' })\n }\n return distribution\n}\n\nfunction parseIsoDate(value: string | null | undefined): Date | null {\n if (value === undefined || value === null) return null\n return new Date(value)\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.create\n// ----------------------------------------------------------------------------\n\nconst createDistributionCommand: CommandHandler<FormDistributionCreateCommandInput, { distributionId: string }> = {\n id: 'forms.distribution.create',\n async execute(rawInput, ctx) {\n const parsed = distributionCreateCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const actorUserId = resolveActorUserId(ctx)\n if (!actorUserId) {\n throw new CrudHttpError(401, { error: 'forms.errors.unauthorized' })\n }\n\n const em = resolveEntityManager(ctx).fork()\n await findFormInScope(em, parsed.formId, parsed.tenantId, parsed.organizationId)\n\n const now = new Date()\n const buildDistribution = (publicSlug: string | null): FormDistribution =>\n em.create(FormDistribution, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n formId: parsed.formId,\n pinnedVersionId: parsed.pinnedVersionId ?? null,\n mode: parsed.mode,\n publicSlug,\n status: 'active',\n title: parsed.title?.trim() ?? null,\n defaultLocale: parsed.defaultLocale,\n requireCustomerAuth: parsed.requireCustomerAuth ?? false,\n allowMultipleSubmissions: parsed.allowMultipleSubmissions ?? false,\n maxResponses: parsed.maxResponses ?? null,\n responseCount: 0,\n opensAt: parseIsoDate(parsed.opensAt),\n closesAt: parseIsoDate(parsed.closesAt),\n redirectUrl: parsed.redirectUrl ?? null,\n settings: parsed.settings ?? null,\n createdBy: actorUserId,\n createdAt: now,\n updatedAt: now,\n } as never)\n\n let distribution = buildDistribution(parsed.mode === 'open' ? generatePublicSlug() : null)\n em.persist(distribution)\n\n try {\n await em.flush()\n } catch (error) {\n if (parsed.mode === 'open' && isUniqueViolation(error, 'forms_distribution_org_public_slug_unique')) {\n const retryEm = resolveEntityManager(ctx).fork()\n distribution = retryEm.create(FormDistribution, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n formId: parsed.formId,\n pinnedVersionId: parsed.pinnedVersionId ?? null,\n mode: parsed.mode,\n publicSlug: generatePublicSlug(),\n status: 'active',\n title: parsed.title?.trim() ?? null,\n defaultLocale: parsed.defaultLocale,\n requireCustomerAuth: parsed.requireCustomerAuth ?? false,\n allowMultipleSubmissions: parsed.allowMultipleSubmissions ?? false,\n maxResponses: parsed.maxResponses ?? null,\n responseCount: 0,\n opensAt: parseIsoDate(parsed.opensAt),\n closesAt: parseIsoDate(parsed.closesAt),\n redirectUrl: parsed.redirectUrl ?? null,\n settings: parsed.settings ?? null,\n createdBy: actorUserId,\n createdAt: now,\n updatedAt: now,\n } as never)\n retryEm.persist(distribution)\n await retryEm.flush()\n } else {\n throw error\n }\n }\n\n await emitForms('forms.distribution.created', {\n distributionId: distribution.id,\n organizationId: distribution.organizationId,\n formId: distribution.formId,\n })\n\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n FORMS_CACHE_TAGS.form(distribution.formId),\n ])\n\n return { distributionId: distribution.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: 'forms.audit.distribution.create',\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotAfter: after ?? null,\n payload: { undo: { after: after ?? null } satisfies FormDistributionUndoPayload },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: after.id })\n if (!distribution) return\n em.remove(distribution)\n await em.flush()\n await emitForms('forms.distribution.closed', { distributionId: after.id })\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distributionList(after.formId),\n DISTRIBUTION_CACHE_TAGS.distribution(after.id),\n ])\n },\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.update\n// ----------------------------------------------------------------------------\n\nconst updateDistributionCommand: CommandHandler<FormDistributionUpdateCommandInput, { distributionId: string; updatedAt: string }> = {\n id: 'forms.distribution.update',\n async prepare(rawInput, ctx) {\n const parsed = distributionUpdateCommandSchema.parse(rawInput)\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, {\n id: parsed.distributionId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!distribution) return {}\n return { before: serializeDistributionSnapshot(distribution) }\n },\n async execute(rawInput, ctx) {\n const parsed = distributionUpdateCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = resolveEntityManager(ctx).fork()\n const distribution = await findDistributionInScope(\n em,\n parsed.distributionId,\n parsed.tenantId,\n parsed.organizationId,\n )\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: distribution.id,\n current: distribution.updatedAt,\n request: ctx.request,\n })\n\n const previousStatus = distribution.status\n let touched = false\n\n if (parsed.status !== undefined && parsed.status !== distribution.status) {\n distribution.status = parsed.status\n touched = true\n }\n if (parsed.title !== undefined) {\n const next = parsed.title?.trim() ?? null\n if (next !== distribution.title) {\n distribution.title = next\n touched = true\n }\n }\n if (parsed.maxResponses !== undefined) {\n const next = parsed.maxResponses ?? null\n if (next !== distribution.maxResponses) {\n distribution.maxResponses = next\n touched = true\n }\n }\n if (parsed.opensAt !== undefined) {\n distribution.opensAt = parseIsoDate(parsed.opensAt)\n touched = true\n }\n if (parsed.closesAt !== undefined) {\n distribution.closesAt = parseIsoDate(parsed.closesAt)\n touched = true\n }\n if (parsed.redirectUrl !== undefined) {\n const next = parsed.redirectUrl ?? null\n if (next !== distribution.redirectUrl) {\n distribution.redirectUrl = next\n touched = true\n }\n }\n if (parsed.allowMultipleSubmissions !== undefined && parsed.allowMultipleSubmissions !== distribution.allowMultipleSubmissions) {\n distribution.allowMultipleSubmissions = parsed.allowMultipleSubmissions\n touched = true\n }\n if (parsed.requireCustomerAuth !== undefined && parsed.requireCustomerAuth !== distribution.requireCustomerAuth) {\n distribution.requireCustomerAuth = parsed.requireCustomerAuth\n touched = true\n }\n if (parsed.settings !== undefined) {\n distribution.settings = parsed.settings ?? null\n touched = true\n }\n\n if (touched) {\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n }\n\n if (distribution.status === 'closed' && previousStatus !== 'closed') {\n await emitForms('forms.distribution.closed', { distributionId: distribution.id })\n }\n\n return { distributionId: distribution.id, updatedAt: distribution.updatedAt.toISOString() }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const before = snapshots.before as FormDistributionSnapshot | undefined\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: 'forms.audit.distribution.update',\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies FormDistributionUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: before.id })\n if (!distribution) return\n distribution.status = before.status\n distribution.title = before.title\n distribution.maxResponses = before.maxResponses\n distribution.opensAt = before.opensAt ? new Date(before.opensAt) : null\n distribution.closesAt = before.closesAt ? new Date(before.closesAt) : null\n distribution.redirectUrl = before.redirectUrl\n distribution.allowMultipleSubmissions = before.allowMultipleSubmissions\n distribution.requireCustomerAuth = before.requireCustomerAuth\n distribution.settings = before.settings\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n },\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.close\n// ----------------------------------------------------------------------------\n\nconst closeDistributionCommand: CommandHandler<FormDistributionCloseCommandInput, { distributionId: string; updatedAt: string }> = {\n id: 'forms.distribution.close',\n async prepare(rawInput, ctx) {\n const parsed = distributionCloseCommandSchema.parse(rawInput)\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, {\n id: parsed.distributionId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!distribution) return {}\n return { before: serializeDistributionSnapshot(distribution) }\n },\n async execute(rawInput, ctx) {\n const parsed = distributionCloseCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = resolveEntityManager(ctx).fork()\n const distribution = await findDistributionInScope(\n em,\n parsed.distributionId,\n parsed.tenantId,\n parsed.organizationId,\n )\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: distribution.id,\n current: distribution.updatedAt,\n request: ctx.request,\n })\n\n if (distribution.status !== 'closed') {\n distribution.status = 'closed'\n distribution.updatedAt = new Date()\n await em.flush()\n await emitForms('forms.distribution.closed', { distributionId: distribution.id })\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n }\n\n return { distributionId: distribution.id, updatedAt: distribution.updatedAt.toISOString() }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const before = snapshots.before as FormDistributionSnapshot | undefined\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: 'forms.audit.distribution.close',\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies FormDistributionUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: before.id })\n if (!distribution) return\n distribution.status = before.status\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n },\n}\n\nregisterCommand(createDistributionCommand)\nregisterCommand(updateDistributionCommand)\nregisterCommand(closeDistributionCommand)\n\nexport {\n DISTRIBUTION_CACHE_TAGS,\n createDistributionCommand,\n updateDistributionCommand,\n closeDistributionCommand,\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,uBAAuB;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { FormDistribution } from '../data/entities'\nimport {\n distributionCloseCommandSchema,\n distributionCreateCommandSchema,\n distributionUpdateCommandSchema,\n type FormDistributionCloseCommandInput,\n type FormDistributionCreateCommandInput,\n type FormDistributionUpdateCommandInput,\n} from '../data/validators'\nimport { generatePublicSlug } from '../services/distribution-token'\nimport {\n FORMS_CACHE_TAGS,\n emitForms,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n findFormInScope,\n invalidateFormsCacheTags,\n resolveActorUserId,\n resolveEntityManager,\n} from './shared'\n\nexport const FORM_DISTRIBUTION_RESOURCE_KIND = 'forms.distribution'\n\n// ----------------------------------------------------------------------------\n// Snapshot + undo payloads\n// ----------------------------------------------------------------------------\n\nexport type FormDistributionSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n formId: string\n pinnedVersionId: string | null\n mode: 'open' | 'personal'\n publicSlug: string | null\n status: 'active' | 'paused' | 'closed'\n title: string | null\n defaultLocale: string\n requireCustomerAuth: boolean\n allowMultipleSubmissions: boolean\n maxResponses: number | null\n responseCount: number\n opensAt: string | null\n closesAt: string | null\n redirectUrl: string | null\n settings: Record<string, unknown> | null\n createdBy: string\n createdAt: string\n updatedAt: string\n}\n\ntype FormDistributionUndoPayload = {\n before?: FormDistributionSnapshot | null\n after?: FormDistributionSnapshot | null\n}\n\nexport function serializeDistributionSnapshot(distribution: FormDistribution): FormDistributionSnapshot {\n return {\n id: distribution.id,\n organizationId: distribution.organizationId,\n tenantId: distribution.tenantId,\n formId: distribution.formId,\n pinnedVersionId: distribution.pinnedVersionId ?? null,\n mode: distribution.mode,\n publicSlug: distribution.publicSlug ?? null,\n status: distribution.status,\n title: distribution.title ?? null,\n defaultLocale: distribution.defaultLocale,\n requireCustomerAuth: distribution.requireCustomerAuth,\n allowMultipleSubmissions: distribution.allowMultipleSubmissions,\n maxResponses: distribution.maxResponses ?? null,\n responseCount: distribution.responseCount,\n opensAt: distribution.opensAt ? distribution.opensAt.toISOString() : null,\n closesAt: distribution.closesAt ? distribution.closesAt.toISOString() : null,\n redirectUrl: distribution.redirectUrl ?? null,\n settings: distribution.settings ?? null,\n createdBy: distribution.createdBy,\n createdAt: distribution.createdAt.toISOString(),\n updatedAt: distribution.updatedAt.toISOString(),\n }\n}\n\n// ----------------------------------------------------------------------------\n// Cache tags + helpers\n// ----------------------------------------------------------------------------\n\nconst DISTRIBUTION_CACHE_TAGS = {\n distribution: (distributionId: string) => `forms.distribution:${distributionId}`,\n distributionList: (formId: string) => `forms.distribution.list:${formId}`,\n} as const\n\nfunction isUniqueViolation(error: unknown, constraint: string): boolean {\n if (!error || typeof error !== 'object') return false\n const candidate = error as Record<string, unknown>\n const code = candidate.code\n const detail = typeof candidate.detail === 'string' ? candidate.detail : ''\n const constraintName = typeof candidate.constraint === 'string' ? candidate.constraint : ''\n return code === '23505' && (constraintName === constraint || detail.includes(constraint))\n}\n\nasync function findDistributionInScope(\n em: EntityManager,\n distributionId: string,\n tenantId: string,\n organizationId: string,\n): Promise<FormDistribution> {\n const distribution = await em.findOne(FormDistribution, {\n id: distributionId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (!distribution) {\n throw new CrudHttpError(404, { error: 'forms.errors.distribution_not_found' })\n }\n return distribution\n}\n\nfunction parseIsoDate(value: string | null | undefined): Date | null {\n if (value === undefined || value === null) return null\n return new Date(value)\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.create\n// ----------------------------------------------------------------------------\n\nconst createDistributionCommand: CommandHandler<FormDistributionCreateCommandInput, { distributionId: string }> = {\n id: 'forms.distribution.create',\n async execute(rawInput, ctx) {\n const parsed = distributionCreateCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const actorUserId = resolveActorUserId(ctx)\n if (!actorUserId) {\n throw new CrudHttpError(401, { error: 'forms.errors.unauthorized' })\n }\n\n const em = resolveEntityManager(ctx).fork()\n await findFormInScope(em, parsed.formId, parsed.tenantId, parsed.organizationId)\n\n const now = new Date()\n const buildDistribution = (publicSlug: string | null): FormDistribution =>\n em.create(FormDistribution, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n formId: parsed.formId,\n pinnedVersionId: parsed.pinnedVersionId ?? null,\n mode: parsed.mode,\n publicSlug,\n status: 'active',\n title: parsed.title?.trim() ?? null,\n defaultLocale: parsed.defaultLocale,\n requireCustomerAuth: parsed.requireCustomerAuth ?? false,\n allowMultipleSubmissions: parsed.allowMultipleSubmissions ?? false,\n maxResponses: parsed.maxResponses ?? null,\n responseCount: 0,\n opensAt: parseIsoDate(parsed.opensAt),\n closesAt: parseIsoDate(parsed.closesAt),\n redirectUrl: parsed.redirectUrl ?? null,\n settings: parsed.settings ?? null,\n createdBy: actorUserId,\n createdAt: now,\n updatedAt: now,\n } as never)\n\n let distribution = buildDistribution(parsed.mode === 'open' ? generatePublicSlug() : null)\n em.persist(distribution)\n\n try {\n await em.flush()\n } catch (error) {\n if (parsed.mode === 'open' && isUniqueViolation(error, 'forms_distribution_org_public_slug_unique')) {\n const retryEm = resolveEntityManager(ctx).fork()\n distribution = retryEm.create(FormDistribution, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n formId: parsed.formId,\n pinnedVersionId: parsed.pinnedVersionId ?? null,\n mode: parsed.mode,\n publicSlug: generatePublicSlug(),\n status: 'active',\n title: parsed.title?.trim() ?? null,\n defaultLocale: parsed.defaultLocale,\n requireCustomerAuth: parsed.requireCustomerAuth ?? false,\n allowMultipleSubmissions: parsed.allowMultipleSubmissions ?? false,\n maxResponses: parsed.maxResponses ?? null,\n responseCount: 0,\n opensAt: parseIsoDate(parsed.opensAt),\n closesAt: parseIsoDate(parsed.closesAt),\n redirectUrl: parsed.redirectUrl ?? null,\n settings: parsed.settings ?? null,\n createdBy: actorUserId,\n createdAt: now,\n updatedAt: now,\n } as never)\n retryEm.persist(distribution)\n await retryEm.flush()\n } else {\n throw error\n }\n }\n\n await emitForms('forms.distribution.created', {\n distributionId: distribution.id,\n organizationId: distribution.organizationId,\n formId: distribution.formId,\n })\n\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n FORMS_CACHE_TAGS.form(distribution.formId),\n ])\n\n return { distributionId: distribution.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: translate('forms.audit.distribution.create', 'Create distribution'),\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotAfter: after ?? null,\n payload: { undo: { after: after ?? null } satisfies FormDistributionUndoPayload },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: after.id })\n if (!distribution) return\n em.remove(distribution)\n await em.flush()\n await emitForms('forms.distribution.closed', { distributionId: after.id })\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distributionList(after.formId),\n DISTRIBUTION_CACHE_TAGS.distribution(after.id),\n ])\n },\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.update\n// ----------------------------------------------------------------------------\n\nconst updateDistributionCommand: CommandHandler<FormDistributionUpdateCommandInput, { distributionId: string; updatedAt: string }> = {\n id: 'forms.distribution.update',\n async prepare(rawInput, ctx) {\n const parsed = distributionUpdateCommandSchema.parse(rawInput)\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, {\n id: parsed.distributionId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!distribution) return {}\n return { before: serializeDistributionSnapshot(distribution) }\n },\n async execute(rawInput, ctx) {\n const parsed = distributionUpdateCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = resolveEntityManager(ctx).fork()\n const distribution = await findDistributionInScope(\n em,\n parsed.distributionId,\n parsed.tenantId,\n parsed.organizationId,\n )\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: distribution.id,\n current: distribution.updatedAt,\n request: ctx.request,\n })\n\n const previousStatus = distribution.status\n let touched = false\n\n if (parsed.status !== undefined && parsed.status !== distribution.status) {\n distribution.status = parsed.status\n touched = true\n }\n if (parsed.title !== undefined) {\n const next = parsed.title?.trim() ?? null\n if (next !== distribution.title) {\n distribution.title = next\n touched = true\n }\n }\n if (parsed.maxResponses !== undefined) {\n const next = parsed.maxResponses ?? null\n if (next !== distribution.maxResponses) {\n distribution.maxResponses = next\n touched = true\n }\n }\n if (parsed.opensAt !== undefined) {\n distribution.opensAt = parseIsoDate(parsed.opensAt)\n touched = true\n }\n if (parsed.closesAt !== undefined) {\n distribution.closesAt = parseIsoDate(parsed.closesAt)\n touched = true\n }\n if (parsed.redirectUrl !== undefined) {\n const next = parsed.redirectUrl ?? null\n if (next !== distribution.redirectUrl) {\n distribution.redirectUrl = next\n touched = true\n }\n }\n if (parsed.allowMultipleSubmissions !== undefined && parsed.allowMultipleSubmissions !== distribution.allowMultipleSubmissions) {\n distribution.allowMultipleSubmissions = parsed.allowMultipleSubmissions\n touched = true\n }\n if (parsed.requireCustomerAuth !== undefined && parsed.requireCustomerAuth !== distribution.requireCustomerAuth) {\n distribution.requireCustomerAuth = parsed.requireCustomerAuth\n touched = true\n }\n if (parsed.settings !== undefined) {\n distribution.settings = parsed.settings ?? null\n touched = true\n }\n\n if (touched) {\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n }\n\n if (distribution.status === 'closed' && previousStatus !== 'closed') {\n await emitForms('forms.distribution.closed', { distributionId: distribution.id })\n }\n\n return { distributionId: distribution.id, updatedAt: distribution.updatedAt.toISOString() }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as FormDistributionSnapshot | undefined\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: translate('forms.audit.distribution.update', 'Update distribution'),\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies FormDistributionUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: before.id })\n if (!distribution) return\n distribution.status = before.status\n distribution.title = before.title\n distribution.maxResponses = before.maxResponses\n distribution.opensAt = before.opensAt ? new Date(before.opensAt) : null\n distribution.closesAt = before.closesAt ? new Date(before.closesAt) : null\n distribution.redirectUrl = before.redirectUrl\n distribution.allowMultipleSubmissions = before.allowMultipleSubmissions\n distribution.requireCustomerAuth = before.requireCustomerAuth\n distribution.settings = before.settings\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n },\n}\n\n// ----------------------------------------------------------------------------\n// forms.distribution.close\n// ----------------------------------------------------------------------------\n\nconst closeDistributionCommand: CommandHandler<FormDistributionCloseCommandInput, { distributionId: string; updatedAt: string }> = {\n id: 'forms.distribution.close',\n async prepare(rawInput, ctx) {\n const parsed = distributionCloseCommandSchema.parse(rawInput)\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, {\n id: parsed.distributionId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n deletedAt: null,\n })\n if (!distribution) return {}\n return { before: serializeDistributionSnapshot(distribution) }\n },\n async execute(rawInput, ctx) {\n const parsed = distributionCloseCommandSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = resolveEntityManager(ctx).fork()\n const distribution = await findDistributionInScope(\n em,\n parsed.distributionId,\n parsed.tenantId,\n parsed.organizationId,\n )\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: distribution.id,\n current: distribution.updatedAt,\n request: ctx.request,\n })\n\n if (distribution.status !== 'closed') {\n distribution.status = 'closed'\n distribution.updatedAt = new Date()\n await em.flush()\n await emitForms('forms.distribution.closed', { distributionId: distribution.id })\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n }\n\n return { distributionId: distribution.id, updatedAt: distribution.updatedAt.toISOString() }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveEntityManager(ctx)\n const distribution = await em.findOne(FormDistribution, { id: result.distributionId })\n return distribution ? serializeDistributionSnapshot(distribution) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as FormDistributionSnapshot | undefined\n const after = snapshots.after as FormDistributionSnapshot | undefined\n return {\n actionLabel: translate('forms.audit.distribution.close', 'Close distribution'),\n resourceKind: FORM_DISTRIBUTION_RESOURCE_KIND,\n resourceId: result.distributionId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies FormDistributionUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<FormDistributionUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = resolveEntityManager(ctx).fork()\n const distribution = await em.findOne(FormDistribution, { id: before.id })\n if (!distribution) return\n distribution.status = before.status\n distribution.updatedAt = new Date()\n await em.flush()\n await invalidateFormsCacheTags(ctx, [\n DISTRIBUTION_CACHE_TAGS.distribution(distribution.id),\n DISTRIBUTION_CACHE_TAGS.distributionList(distribution.formId),\n ])\n },\n}\n\nregisterCommand(createDistributionCommand)\nregisterCommand(updateDistributionCommand)\nregisterCommand(closeDistributionCommand)\n\nexport {\n DISTRIBUTION_CACHE_TAGS,\n createDistributionCommand,\n updateDistributionCommand,\n closeDistributionCommand,\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
2
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
2
3
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
4
|
import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
4
5
|
import { Form, FormVersion } from "../data/entities.js";
|
|
@@ -173,9 +174,10 @@ const forkDraftCommand = {
|
|
|
173
174
|
return version ? serializeFormVersionSnapshot(version) : null;
|
|
174
175
|
},
|
|
175
176
|
buildLog: async ({ result, snapshots }) => {
|
|
177
|
+
const { translate } = await resolveTranslations();
|
|
176
178
|
const after = snapshots.after;
|
|
177
179
|
return {
|
|
178
|
-
actionLabel: "forms.audit.form_version.fork_draft",
|
|
180
|
+
actionLabel: translate("forms.audit.form_version.fork_draft", "Create draft form version"),
|
|
179
181
|
resourceKind: FORM_VERSION_RESOURCE_KIND,
|
|
180
182
|
resourceId: result.versionId,
|
|
181
183
|
parentResourceKind: "forms.form",
|
|
@@ -300,10 +302,11 @@ const updateDraftCommand = {
|
|
|
300
302
|
return version ? serializeFormVersionSnapshot(version) : null;
|
|
301
303
|
},
|
|
302
304
|
buildLog: async ({ result, snapshots }) => {
|
|
305
|
+
const { translate } = await resolveTranslations();
|
|
303
306
|
const before = snapshots.before;
|
|
304
307
|
const after = snapshots.after;
|
|
305
308
|
return {
|
|
306
|
-
actionLabel: "forms.audit.form_version.update_draft",
|
|
309
|
+
actionLabel: translate("forms.audit.form_version.update_draft", "Update draft form version"),
|
|
307
310
|
resourceKind: FORM_VERSION_RESOURCE_KIND,
|
|
308
311
|
resourceId: result.versionId,
|
|
309
312
|
parentResourceKind: "forms.form",
|
|
@@ -469,11 +472,12 @@ const publishVersionCommand = {
|
|
|
469
472
|
return version ? serializeFormVersionSnapshot(version) : null;
|
|
470
473
|
},
|
|
471
474
|
buildLog: async ({ result, snapshots, ctx }) => {
|
|
475
|
+
const { translate } = await resolveTranslations();
|
|
472
476
|
const before = snapshots.before;
|
|
473
477
|
const after = snapshots.after;
|
|
474
478
|
const meta = ctx.__formsPublishMeta;
|
|
475
479
|
return {
|
|
476
|
-
actionLabel: "forms.audit.form_version.publish",
|
|
480
|
+
actionLabel: translate("forms.audit.form_version.publish", "Publish form version"),
|
|
477
481
|
resourceKind: FORM_VERSION_RESOURCE_KIND,
|
|
478
482
|
resourceId: result.versionId,
|
|
479
483
|
parentResourceKind: "forms.form",
|
|
@@ -587,10 +591,11 @@ const archiveVersionCommand = {
|
|
|
587
591
|
return version ? serializeFormVersionSnapshot(version) : null;
|
|
588
592
|
},
|
|
589
593
|
buildLog: async ({ result, snapshots }) => {
|
|
594
|
+
const { translate } = await resolveTranslations();
|
|
590
595
|
const before = snapshots.before;
|
|
591
596
|
const after = snapshots.after;
|
|
592
597
|
return {
|
|
593
|
-
actionLabel: "forms.audit.form_version.archive",
|
|
598
|
+
actionLabel: translate("forms.audit.form_version.archive", "Archive form version"),
|
|
594
599
|
resourceKind: FORM_VERSION_RESOURCE_KIND,
|
|
595
600
|
resourceId: result.versionId,
|
|
596
601
|
parentResourceKind: "forms.form",
|