@open-mercato/core 0.10.1-develop.8004.1.a8a79ee43f → 0.10.1-develop.8022.1.b6753de892
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/audit_logs/components/AuditLogsActions.js +80 -50
- package/dist/modules/audit_logs/components/AuditLogsActions.js.map +3 -3
- package/dist/modules/audit_logs/lib/rowPresentation.js +82 -0
- package/dist/modules/audit_logs/lib/rowPresentation.js.map +7 -0
- package/dist/modules/catalog/commands/variants.js +89 -16
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/seed/examples.js +7 -0
- package/dist/modules/catalog/seed/examples.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +2 -2
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +10 -21
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260825140000_customers.js +17 -10
- package/dist/modules/customers/migrations/Migration20260825140000_customers.js.map +2 -2
- package/dist/modules/entities/cli.js +138 -66
- package/dist/modules/entities/cli.js.map +3 -3
- package/dist/modules/promotions/commands/codes.js +18 -3
- package/dist/modules/promotions/commands/codes.js.map +2 -2
- package/dist/modules/query_index/cli.js +17 -1
- package/dist/modules/query_index/cli.js.map +2 -2
- package/dist/modules/query_index/lib/reindexer.js +23 -5
- package/dist/modules/query_index/lib/reindexer.js.map +2 -2
- package/dist/modules/sales/commands/configuration.js +30 -16
- package/dist/modules/sales/commands/configuration.js.map +2 -2
- package/dist/modules/sales/components/documents/AddressesSection.js +36 -15
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/seed/examples.js +35 -6
- package/dist/modules/sales/seed/examples.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/audit_logs/components/AuditLogsActions.tsx +85 -48
- package/src/modules/audit_logs/i18n/de.json +16 -0
- package/src/modules/audit_logs/i18n/en.json +16 -0
- package/src/modules/audit_logs/i18n/es.json +16 -0
- package/src/modules/audit_logs/i18n/ko.json +16 -0
- package/src/modules/audit_logs/i18n/pl.json +16 -0
- package/src/modules/audit_logs/lib/rowPresentation.ts +92 -0
- package/src/modules/catalog/commands/variants.ts +96 -17
- package/src/modules/catalog/seed/examples.ts +7 -0
- package/src/modules/customers/api/interactions/route.ts +2 -2
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +9 -12
- package/src/modules/customers/i18n/de.json +2 -1
- package/src/modules/customers/i18n/en.json +2 -1
- package/src/modules/customers/i18n/es.json +2 -1
- package/src/modules/customers/i18n/ko.json +2 -1
- package/src/modules/customers/i18n/pl.json +2 -1
- package/src/modules/customers/migrations/Migration20260825140000_customers.ts +21 -10
- package/src/modules/entities/cli.ts +144 -65
- package/src/modules/promotions/commands/codes.ts +19 -4
- package/src/modules/query_index/cli.ts +17 -1
- package/src/modules/query_index/lib/reindexer.ts +55 -6
- package/src/modules/sales/commands/configuration.ts +35 -16
- package/src/modules/sales/components/documents/AddressesSection.tsx +51 -20
- package/src/modules/sales/i18n/de.json +3 -0
- package/src/modules/sales/i18n/en.json +3 -0
- package/src/modules/sales/i18n/es.json +3 -0
- package/src/modules/sales/i18n/ko.json +3 -0
- package/src/modules/sales/i18n/pl.json +4 -1
- package/src/modules/sales/seed/examples.ts +41 -6
|
@@ -259,16 +259,12 @@ function AssignRoleDialog({
|
|
|
259
259
|
selectedUser.email ? /* @__PURE__ */ jsx("span", { children: selectedUser.email }) : null,
|
|
260
260
|
selectedUser.teamName ? /* @__PURE__ */ jsx("span", { children: selectedUser.teamName }) : null
|
|
261
261
|
] }),
|
|
262
|
-
selectedUserConflict?.length ? /* @__PURE__ */
|
|
263
|
-
Badge,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
roles: selectedUserConflict.join(", ")
|
|
269
|
-
})
|
|
270
|
-
}
|
|
271
|
-
) }) : null
|
|
262
|
+
selectedUserConflict?.length ? /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-1.5", children: [
|
|
263
|
+
/* @__PURE__ */ jsx(Badge, { variant: "info", children: t("customers.roles.dialog.alreadyAssigned", "Already assigned: {{roles}}", {
|
|
264
|
+
roles: selectedUserConflict.join(", ")
|
|
265
|
+
}) }),
|
|
266
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("customers.roles.dialog.multipleRolesHint", "One person can hold several roles on this record.") })
|
|
267
|
+
] }) : null
|
|
272
268
|
] })
|
|
273
269
|
] })
|
|
274
270
|
] }) : null;
|
|
@@ -463,7 +459,7 @@ function AssignRoleDialog({
|
|
|
463
459
|
type: "button",
|
|
464
460
|
variant: "ghost",
|
|
465
461
|
onClick: () => setSelectedUser(user),
|
|
466
|
-
className: `h-auto flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition-colors ${isSelected ? "border-foreground bg-background shadow-sm" : "border-border/70 bg-background hover:bg-accent/40"}`,
|
|
462
|
+
className: `h-auto flex w-full items-center gap-3 whitespace-normal break-words rounded-lg border px-4 py-3 text-left transition-colors ${isSelected ? "border-foreground bg-background shadow-sm" : "border-border/70 bg-background hover:bg-accent/40"}`,
|
|
467
463
|
children: [
|
|
468
464
|
/* @__PURE__ */ jsx(Avatar, { label: user.displayName, size: "lg", variant: "monochrome" }),
|
|
469
465
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -477,16 +473,9 @@ function AssignRoleDialog({
|
|
|
477
473
|
children: user.teamName
|
|
478
474
|
}
|
|
479
475
|
) : null,
|
|
480
|
-
userConflicts.length ? /* @__PURE__ */ jsx(
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
variant: "outline",
|
|
484
|
-
className: "rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text",
|
|
485
|
-
children: t("customers.roles.dialog.conflict", "Conflict: {{roles}}", {
|
|
486
|
-
roles: userConflicts.join(", ")
|
|
487
|
-
})
|
|
488
|
-
}
|
|
489
|
-
) : null
|
|
476
|
+
userConflicts.length ? /* @__PURE__ */ jsx(Badge, { variant: "info", children: t("customers.roles.dialog.alreadyAssigned", "Already assigned: {{roles}}", {
|
|
477
|
+
roles: userConflicts.join(", ")
|
|
478
|
+
}) }) : null
|
|
490
479
|
] }),
|
|
491
480
|
user.email ? /* @__PURE__ */ jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: user.email }) : null
|
|
492
481
|
] }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/AssignRoleDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Search, Check, CheckCheck, Settings2 } from 'lucide-react'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { StepIndicator, type StepIndicatorStep } from '@open-mercato/ui/primitives/step-indicator'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { hasMoreFromPage } from '@open-mercato/shared/lib/pagination/load-more'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport type { RoleAssignment } from './RoleAssignmentRow'\nimport { useCurrentOrganization } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { fetchAssignableStaffMembersPage } from './assignableStaff'\n\nconst MANAGE_ROLE_TYPES_HREF = '/backend/config/customers'\n\ntype StaffMember = {\n id: string\n displayName: string\n email: string | null\n teamName: string | null\n}\n\ninterface AssignRoleDialogProps {\n open: boolean\n onClose: () => void\n onAssign: (roleType: string, userId: string) => Promise<void>\n roleTypes: DictionaryEntryOption[]\n entityName: string\n existingRoleTypes?: Set<string>\n existingAssignments?: RoleAssignment[]\n initialRoleType?: string | null\n canManageRoleTypes?: boolean\n}\n\ntype StepId = 1 | 2 | 3\n\ntype TeamFilter = {\n id: string\n label: string\n count: number\n}\n\nconst ASSIGNABLE_STAFF_PAGE_SIZE = 24\n\nexport function AssignRoleDialog({\n open,\n onClose,\n onAssign,\n roleTypes,\n entityName,\n existingRoleTypes,\n existingAssignments = [],\n initialRoleType = null,\n canManageRoleTypes = false,\n}: AssignRoleDialogProps) {\n const t = useT()\n const activeOrgId = useCurrentOrganization()?.id ?? null\n const [step, setStep] = React.useState<StepId>(1)\n const [selectedRoleType, setSelectedRoleType] = React.useState('')\n const [selectedUser, setSelectedUser] = React.useState<StaffMember | null>(null)\n const [searchQuery, setSearchQuery] = React.useState('')\n const [users, setUsers] = React.useState<StaffMember[]>([])\n const [loading, setLoading] = React.useState(false)\n const [loadingMore, setLoadingMore] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [activeTeam, setActiveTeam] = React.useState('all')\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [totalUsers, setTotalUsers] = React.useState(0)\n // Short-page termination instead of `users.length >= totalUsers` \u2014 see\n // `hasMoreFromPage`. Measured on the served count, not on `users`, which is\n // deduped by id.\n const [hasMore, setHasMore] = React.useState(false)\n const [currentPage, setCurrentPage] = React.useState(1)\n const deferredSearchQuery = React.useDeferredValue(searchQuery)\n const requestSequenceRef = React.useRef(0)\n\n const availableRoleTypes = React.useMemo(\n () => roleTypes.filter((roleType) => !existingRoleTypes?.has(roleType.value)),\n [existingRoleTypes, roleTypes],\n )\n\n React.useEffect(() => {\n if (!open) {\n setStep(1)\n setSelectedRoleType('')\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n return\n }\n const resolvedInitialRoleType =\n typeof initialRoleType === 'string' && initialRoleType.trim().length > 0\n ? initialRoleType.trim()\n : ''\n setStep(resolvedInitialRoleType ? 2 : 1)\n setSelectedRoleType(resolvedInitialRoleType)\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n }, [initialRoleType, open])\n\n const searchUsers = React.useCallback(\n async ({\n query,\n page,\n append,\n }: {\n query: string\n page: number\n append: boolean\n }) => {\n const requestId = append ? requestSequenceRef.current : requestSequenceRef.current + 1\n requestSequenceRef.current = requestId\n\n if (append) {\n setLoadingMore(true)\n } else {\n setLoading(true)\n }\n\n try {\n const result = await fetchAssignableStaffMembersPage(query, {\n page,\n pageSize: ASSIGNABLE_STAFF_PAGE_SIZE,\n activeOrgId,\n })\n if (requestSequenceRef.current !== requestId) return\n\n const nextUsers = result.items.map((member) => ({\n id: member.userId,\n displayName: member.displayName,\n email: member.email,\n teamName: member.teamName,\n }))\n\n setUsers((current) => {\n if (!append) return nextUsers\n const merged = new Map(current.map((user) => [user.id, user]))\n nextUsers.forEach((user) => merged.set(user.id, user))\n return Array.from(merged.values())\n })\n setTotalUsers(result.total)\n setHasMore(hasMoreFromPage(result.servedCount, ASSIGNABLE_STAFF_PAGE_SIZE))\n setCurrentPage(result.page)\n setLoadError(null)\n } catch {\n if (requestSequenceRef.current !== requestId) return\n if (!append) {\n setUsers([])\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n }\n setLoadError(\n t(\n 'customers.assignableStaff.loadError',\n 'Unable to load team members. Check your permissions and try again.',\n ),\n )\n } finally {\n if (requestSequenceRef.current !== requestId) return\n if (append) {\n setLoadingMore(false)\n } else {\n setLoading(false)\n }\n }\n },\n [activeOrgId, t],\n )\n\n React.useEffect(() => {\n if (step >= 2) {\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({ query: deferredSearchQuery, page: 1, append: false }).catch(() => {})\n }\n }, [deferredSearchQuery, searchUsers, step])\n\n const handleLoadMore = React.useCallback(() => {\n if (loading || loadingMore || !hasMore) return\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({\n query: deferredSearchQuery,\n page: currentPage + 1,\n append: true,\n }).catch(() => {})\n }, [currentPage, deferredSearchQuery, hasMore, loading, loadingMore, searchUsers])\n\n const selectedRole = React.useMemo(\n () => roleTypes.find((roleType) => roleType.value === selectedRoleType) ?? null,\n [roleTypes, selectedRoleType],\n )\n\n const roleTypeLabelMap = React.useMemo(\n () => new Map(roleTypes.map((roleType) => [roleType.value, roleType.label])),\n [roleTypes],\n )\n\n const conflictsByUserId = React.useMemo(() => {\n const next = new Map<string, string[]>()\n existingAssignments.forEach((assignment) => {\n if (!assignment.userId) return\n if (assignment.roleType === selectedRoleType) return\n const label = roleTypeLabelMap.get(assignment.roleType) ?? assignment.roleType\n const current = next.get(assignment.userId) ?? []\n if (!current.includes(label)) {\n current.push(label)\n next.set(assignment.userId, current)\n }\n })\n return next\n }, [existingAssignments, roleTypeLabelMap, selectedRoleType])\n\n const selectedUserConflict = React.useMemo(() => {\n if (!selectedUser) return null\n return conflictsByUserId.get(selectedUser.id) ?? null\n }, [conflictsByUserId, selectedUser])\n\n const teamFilters = React.useMemo<TeamFilter[]>(() => {\n const counts = new Map<string, number>()\n users.forEach((user) => {\n const key =\n user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')\n counts.set(key, (counts.get(key) ?? 0) + 1)\n })\n return [\n { id: 'all', label: t('customers.roles.dialog.team.all', 'All'), count: users.length },\n ...Array.from(counts.entries()).map(([label, count]) => ({ id: label, label, count })),\n ]\n }, [t, users])\n\n const filteredUsers = React.useMemo(() => {\n if (activeTeam === 'all') return users\n return users.filter(\n (user) =>\n (user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')) ===\n activeTeam,\n )\n }, [activeTeam, t, users])\n\n const visibleCountLabel = React.useMemo(() => {\n if (totalUsers <= 0) return null\n return t(\n 'customers.roles.dialog.visibleCount',\n 'Showing {{shown}} of {{total}} team members',\n {\n shown: String(users.length),\n total: String(totalUsers),\n },\n )\n }, [t, totalUsers, users.length])\n\n const canLoadMore = hasMore\n\n const handleAssign = React.useCallback(async () => {\n if (!selectedRoleType || !selectedUser) return\n setSaving(true)\n try {\n await onAssign(selectedRoleType, selectedUser.id)\n onClose()\n } finally {\n setSaving(false)\n }\n }, [onAssign, onClose, selectedRoleType, selectedUser])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n if (step === 3 && !saving && selectedUser && selectedRoleType) {\n handleAssign()\n } else if (step === 1 && selectedRoleType && availableRoleTypes.length) {\n setStep(2)\n } else if (step === 2 && selectedUser) {\n setStep(3)\n }\n }\n },\n [availableRoleTypes.length, handleAssign, saving, selectedRoleType, selectedUser, step],\n )\n\n const previewCard =\n selectedUser && selectedRole ? (\n <div className=\"rounded-lg border border-border/70 bg-muted/30 px-4 py-4\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.preview', 'Assignment preview')}\n </p>\n <div className=\"mt-3 flex items-center gap-3\">\n <Avatar\n label={selectedUser.displayName}\n size=\"lg\"\n className=\"bg-background text-foreground\"\n />\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm font-semibold text-foreground\">\n <span>{selectedUser.displayName}</span>\n <span className=\"text-muted-foreground\">\u2192</span>\n <span>{selectedRole.label}</span>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {selectedUser.email ? <span>{selectedUser.email}</span> : null}\n {selectedUser.teamName ? <span>{selectedUser.teamName}</span> : null}\n </div>\n {selectedUserConflict?.length ? (\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: selectedUserConflict.join(', '),\n })}\n </Badge>\n </div>\n ) : null}\n </div>\n </div>\n </div>\n ) : null\n\n return (\n <Dialog\n open={open}\n onOpenChange={(nextOpen) => {\n if (!nextOpen) onClose()\n }}\n >\n <DialogContent\n className=\"min-h-0 max-h-[min(90vh,760px)] overflow-hidden p-0 sm:max-w-[580px]\"\n onKeyDown={handleKeyDown}\n >\n <DialogHeader className=\"border-b border-border/70 px-6 py-5\">\n <DialogTitle className=\"text-2xl font-semibold leading-none\">\n {t('customers.roles.dialog.title', 'Assign role')}\n </DialogTitle>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('customers.roles.dialog.subtitle', 'Multi-role assignment for {{name}}', {\n name: entityName,\n })}\n </p>\n </DialogHeader>\n\n <div className=\"border-b border-border/70 px-6 py-4\">\n <StepIndicator\n className=\"justify-center\"\n steps={[\n {\n id: '1',\n label: t('customers.roles.dialog.step1', 'Role type'),\n status: step > 1 ? 'complete' : step === 1 ? 'current' : 'pending',\n },\n {\n id: '2',\n label: t('customers.roles.dialog.step2', 'Select person'),\n status: step > 2 ? 'complete' : step === 2 ? 'current' : 'pending',\n },\n {\n id: '3',\n label: t('customers.roles.dialog.step3', 'Confirm'),\n status: step === 3 ? 'current' : 'pending',\n },\n ] satisfies StepIndicatorStep[]}\n />\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n <div className=\"space-y-5 px-6 py-5\">\n {step === 1 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <p\n id=\"assign-role-dialog-type-label\"\n className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\"\n >\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <select\n aria-labelledby=\"assign-role-dialog-type-label\"\n value={selectedRoleType}\n onChange={(event) => setSelectedRoleType(event.target.value)}\n className=\"h-10 w-full rounded-md border border-border bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring/30\"\n >\n <option value=\"\">\n {t('customers.roles.selectRoleType', 'Select role type...')}\n </option>\n {availableRoleTypes.map((roleType) => (\n <option key={roleType.id} value={roleType.value}>\n {roleType.label}\n </option>\n ))}\n </select>\n {selectedRole ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n ) : null}\n </div>\n {canManageRoleTypes ? (\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n className=\"mt-3 inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline\"\n data-testid=\"assign-role-dialog-manage-role-types\"\n >\n <Settings2 className=\"size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n ) : null}\n </div>\n\n {!availableRoleTypes.length ? (\n <EmptyState\n size=\"sm\"\n icon={<CheckCheck className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t(\n 'customers.roles.dialog.noAvailableRoles',\n 'All available role types are already assigned.',\n )}\n />\n ) : null}\n </div>\n ) : null}\n\n {step === 2 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-1 flex items-center gap-2\">\n <span className=\"text-lg font-semibold text-foreground\">\n {selectedRole?.label ?? selectedRoleType}\n </span>\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {canManageRoleTypes ? (\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n data-testid=\"assign-role-dialog-manage-role-types-step2\"\n >\n <Settings2 className=\"mr-1 size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setStep(1)}\n >\n {t('customers.roles.dialog.change', 'Change')}\n </Button>\n </div>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.teamLabel', 'Select a team member')}\n </p>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n placeholder={t(\n 'customers.roles.dialog.searchPlaceholder',\n 'Search by name, e-mail or team...',\n )}\n className=\"h-10 rounded-md border-border/80 pl-9 shadow-none\"\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {teamFilters.map((teamFilter) => {\n const isActive = activeTeam === teamFilter.id\n return (\n <Button\n key={teamFilter.id}\n type=\"button\"\n variant={isActive ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setActiveTeam(teamFilter.id)}\n className=\"h-7 rounded-md px-2.5 text-xs\"\n >\n {teamFilter.label}\n <span className=\"rounded-full bg-background/80 px-1 text-xs text-muted-foreground\">\n {teamFilter.count}\n </span>\n </Button>\n )\n })}\n </div>\n\n <div className=\"space-y-2\">\n {visibleCountLabel ? (\n <p className=\"text-xs text-muted-foreground\">{visibleCountLabel}</p>\n ) : null}\n {loading ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t('customers.roles.loading', 'Loading...')}\n </div>\n ) : loadError ? (\n <div className=\"rounded-lg border border-dashed border-status-error-border bg-status-error-bg/70 px-4 py-8 text-center text-sm text-status-error-text\">\n {loadError}\n </div>\n ) : filteredUsers.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.noResults',\n 'No matching team members found.',\n )}\n </div>\n ) : (\n filteredUsers.map((user) => {\n const isSelected = selectedUser?.id === user.id\n const userConflicts = conflictsByUserId.get(user.id) ?? []\n return (\n <Button\n key={user.id}\n type=\"button\"\n variant=\"ghost\"\n onClick={() => setSelectedUser(user)}\n className={`h-auto flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition-colors ${\n isSelected\n ? 'border-foreground bg-background shadow-sm'\n : 'border-border/70 bg-background hover:bg-accent/40'\n }`}\n >\n <Avatar label={user.displayName} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">\n {user.displayName}\n </span>\n {user.teamName ? (\n <Badge\n variant=\"muted\"\n className=\"rounded-md px-2 py-0.5 text-xs font-medium\"\n >\n {user.teamName}\n </Badge>\n ) : null}\n {userConflicts.length ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: userConflicts.join(', '),\n })}\n </Badge>\n ) : null}\n </div>\n {user.email ? (\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {user.email}\n </div>\n ) : null}\n </div>\n <span\n className={`flex size-6 shrink-0 items-center justify-center rounded-full border ${\n isSelected\n ? 'border-foreground bg-foreground text-background'\n : 'border-border/80 bg-background text-transparent'\n }`}\n >\n <Check className=\"size-3.5\" />\n </span>\n </Button>\n )\n })\n )}\n {canLoadMore && !loadError ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"w-full\"\n >\n {loadingMore\n ? t('customers.roles.dialog.loadingMore', 'Loading more...')\n : t('customers.roles.dialog.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n\n {previewCard}\n </div>\n ) : null}\n\n {step === 3 ? (\n <div className=\"space-y-4\">\n {previewCard}\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.constraint',\n 'One person per role. The assignment can be changed at any time.',\n )}\n </p>\n </div>\n ) : null}\n </div>\n </div>\n\n <DialogFooter\n bordered={false}\n className=\"shrink-0 border-t border-border/70 px-6 py-4\"\n leading={\n <p className=\"text-xs text-muted-foreground sm:max-w-2xs\">\n {t(\n 'customers.roles.dialog.footerNote',\n 'One person per role \u00B7 can be changed at any time',\n )}\n </p>\n }\n >\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('customers.roles.cancelAdd', 'Cancel')}\n </Button>\n {step === 1 ? (\n <Button\n type=\"button\"\n onClick={() => setStep(2)}\n disabled={!selectedRoleType || !availableRoleTypes.length}\n >\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 2 ? (\n <Button type=\"button\" onClick={() => setStep(3)} disabled={!selectedUser}>\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 3 ? (\n <Button\n type=\"button\"\n onClick={handleAssign}\n disabled={saving || !selectedUser || !selectedRoleType}\n >\n {saving\n ? t('customers.roles.assigning', 'Assigning...')\n : t('customers.roles.dialog.assign', 'Assign role')}\n </Button>\n ) : null}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Search, Check, CheckCheck, Settings2 } from 'lucide-react'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { StepIndicator, type StepIndicatorStep } from '@open-mercato/ui/primitives/step-indicator'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { hasMoreFromPage } from '@open-mercato/shared/lib/pagination/load-more'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport type { RoleAssignment } from './RoleAssignmentRow'\nimport { useCurrentOrganization } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { fetchAssignableStaffMembersPage } from './assignableStaff'\n\nconst MANAGE_ROLE_TYPES_HREF = '/backend/config/customers'\n\ntype StaffMember = {\n id: string\n displayName: string\n email: string | null\n teamName: string | null\n}\n\ninterface AssignRoleDialogProps {\n open: boolean\n onClose: () => void\n onAssign: (roleType: string, userId: string) => Promise<void>\n roleTypes: DictionaryEntryOption[]\n entityName: string\n existingRoleTypes?: Set<string>\n existingAssignments?: RoleAssignment[]\n initialRoleType?: string | null\n canManageRoleTypes?: boolean\n}\n\ntype StepId = 1 | 2 | 3\n\ntype TeamFilter = {\n id: string\n label: string\n count: number\n}\n\nconst ASSIGNABLE_STAFF_PAGE_SIZE = 24\n\nexport function AssignRoleDialog({\n open,\n onClose,\n onAssign,\n roleTypes,\n entityName,\n existingRoleTypes,\n existingAssignments = [],\n initialRoleType = null,\n canManageRoleTypes = false,\n}: AssignRoleDialogProps) {\n const t = useT()\n const activeOrgId = useCurrentOrganization()?.id ?? null\n const [step, setStep] = React.useState<StepId>(1)\n const [selectedRoleType, setSelectedRoleType] = React.useState('')\n const [selectedUser, setSelectedUser] = React.useState<StaffMember | null>(null)\n const [searchQuery, setSearchQuery] = React.useState('')\n const [users, setUsers] = React.useState<StaffMember[]>([])\n const [loading, setLoading] = React.useState(false)\n const [loadingMore, setLoadingMore] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [activeTeam, setActiveTeam] = React.useState('all')\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [totalUsers, setTotalUsers] = React.useState(0)\n // Short-page termination instead of `users.length >= totalUsers` \u2014 see\n // `hasMoreFromPage`. Measured on the served count, not on `users`, which is\n // deduped by id.\n const [hasMore, setHasMore] = React.useState(false)\n const [currentPage, setCurrentPage] = React.useState(1)\n const deferredSearchQuery = React.useDeferredValue(searchQuery)\n const requestSequenceRef = React.useRef(0)\n\n const availableRoleTypes = React.useMemo(\n () => roleTypes.filter((roleType) => !existingRoleTypes?.has(roleType.value)),\n [existingRoleTypes, roleTypes],\n )\n\n React.useEffect(() => {\n if (!open) {\n setStep(1)\n setSelectedRoleType('')\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n return\n }\n const resolvedInitialRoleType =\n typeof initialRoleType === 'string' && initialRoleType.trim().length > 0\n ? initialRoleType.trim()\n : ''\n setStep(resolvedInitialRoleType ? 2 : 1)\n setSelectedRoleType(resolvedInitialRoleType)\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n }, [initialRoleType, open])\n\n const searchUsers = React.useCallback(\n async ({\n query,\n page,\n append,\n }: {\n query: string\n page: number\n append: boolean\n }) => {\n const requestId = append ? requestSequenceRef.current : requestSequenceRef.current + 1\n requestSequenceRef.current = requestId\n\n if (append) {\n setLoadingMore(true)\n } else {\n setLoading(true)\n }\n\n try {\n const result = await fetchAssignableStaffMembersPage(query, {\n page,\n pageSize: ASSIGNABLE_STAFF_PAGE_SIZE,\n activeOrgId,\n })\n if (requestSequenceRef.current !== requestId) return\n\n const nextUsers = result.items.map((member) => ({\n id: member.userId,\n displayName: member.displayName,\n email: member.email,\n teamName: member.teamName,\n }))\n\n setUsers((current) => {\n if (!append) return nextUsers\n const merged = new Map(current.map((user) => [user.id, user]))\n nextUsers.forEach((user) => merged.set(user.id, user))\n return Array.from(merged.values())\n })\n setTotalUsers(result.total)\n setHasMore(hasMoreFromPage(result.servedCount, ASSIGNABLE_STAFF_PAGE_SIZE))\n setCurrentPage(result.page)\n setLoadError(null)\n } catch {\n if (requestSequenceRef.current !== requestId) return\n if (!append) {\n setUsers([])\n setTotalUsers(0)\n setHasMore(false)\n setCurrentPage(1)\n }\n setLoadError(\n t(\n 'customers.assignableStaff.loadError',\n 'Unable to load team members. Check your permissions and try again.',\n ),\n )\n } finally {\n if (requestSequenceRef.current !== requestId) return\n if (append) {\n setLoadingMore(false)\n } else {\n setLoading(false)\n }\n }\n },\n [activeOrgId, t],\n )\n\n React.useEffect(() => {\n if (step >= 2) {\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({ query: deferredSearchQuery, page: 1, append: false }).catch(() => {})\n }\n }, [deferredSearchQuery, searchUsers, step])\n\n const handleLoadMore = React.useCallback(() => {\n if (loading || loadingMore || !hasMore) return\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({\n query: deferredSearchQuery,\n page: currentPage + 1,\n append: true,\n }).catch(() => {})\n }, [currentPage, deferredSearchQuery, hasMore, loading, loadingMore, searchUsers])\n\n const selectedRole = React.useMemo(\n () => roleTypes.find((roleType) => roleType.value === selectedRoleType) ?? null,\n [roleTypes, selectedRoleType],\n )\n\n const roleTypeLabelMap = React.useMemo(\n () => new Map(roleTypes.map((roleType) => [roleType.value, roleType.label])),\n [roleTypes],\n )\n\n const conflictsByUserId = React.useMemo(() => {\n const next = new Map<string, string[]>()\n existingAssignments.forEach((assignment) => {\n if (!assignment.userId) return\n if (assignment.roleType === selectedRoleType) return\n const label = roleTypeLabelMap.get(assignment.roleType) ?? assignment.roleType\n const current = next.get(assignment.userId) ?? []\n if (!current.includes(label)) {\n current.push(label)\n next.set(assignment.userId, current)\n }\n })\n return next\n }, [existingAssignments, roleTypeLabelMap, selectedRoleType])\n\n const selectedUserConflict = React.useMemo(() => {\n if (!selectedUser) return null\n return conflictsByUserId.get(selectedUser.id) ?? null\n }, [conflictsByUserId, selectedUser])\n\n const teamFilters = React.useMemo<TeamFilter[]>(() => {\n const counts = new Map<string, number>()\n users.forEach((user) => {\n const key =\n user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')\n counts.set(key, (counts.get(key) ?? 0) + 1)\n })\n return [\n { id: 'all', label: t('customers.roles.dialog.team.all', 'All'), count: users.length },\n ...Array.from(counts.entries()).map(([label, count]) => ({ id: label, label, count })),\n ]\n }, [t, users])\n\n const filteredUsers = React.useMemo(() => {\n if (activeTeam === 'all') return users\n return users.filter(\n (user) =>\n (user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')) ===\n activeTeam,\n )\n }, [activeTeam, t, users])\n\n const visibleCountLabel = React.useMemo(() => {\n if (totalUsers <= 0) return null\n return t(\n 'customers.roles.dialog.visibleCount',\n 'Showing {{shown}} of {{total}} team members',\n {\n shown: String(users.length),\n total: String(totalUsers),\n },\n )\n }, [t, totalUsers, users.length])\n\n const canLoadMore = hasMore\n\n const handleAssign = React.useCallback(async () => {\n if (!selectedRoleType || !selectedUser) return\n setSaving(true)\n try {\n await onAssign(selectedRoleType, selectedUser.id)\n onClose()\n } finally {\n setSaving(false)\n }\n }, [onAssign, onClose, selectedRoleType, selectedUser])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n if (step === 3 && !saving && selectedUser && selectedRoleType) {\n handleAssign()\n } else if (step === 1 && selectedRoleType && availableRoleTypes.length) {\n setStep(2)\n } else if (step === 2 && selectedUser) {\n setStep(3)\n }\n }\n },\n [availableRoleTypes.length, handleAssign, saving, selectedRoleType, selectedUser, step],\n )\n\n const previewCard =\n selectedUser && selectedRole ? (\n <div className=\"rounded-lg border border-border/70 bg-muted/30 px-4 py-4\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.preview', 'Assignment preview')}\n </p>\n <div className=\"mt-3 flex items-center gap-3\">\n <Avatar\n label={selectedUser.displayName}\n size=\"lg\"\n className=\"bg-background text-foreground\"\n />\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm font-semibold text-foreground\">\n <span>{selectedUser.displayName}</span>\n <span className=\"text-muted-foreground\">\u2192</span>\n <span>{selectedRole.label}</span>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {selectedUser.email ? <span>{selectedUser.email}</span> : null}\n {selectedUser.teamName ? <span>{selectedUser.teamName}</span> : null}\n </div>\n {selectedUserConflict?.length ? (\n <div className=\"mt-2 space-y-1.5\">\n <Badge variant=\"info\">\n {t('customers.roles.dialog.alreadyAssigned', 'Already assigned: {{roles}}', {\n roles: selectedUserConflict.join(', '),\n })}\n </Badge>\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.roles.dialog.multipleRolesHint', 'One person can hold several roles on this record.')}\n </p>\n </div>\n ) : null}\n </div>\n </div>\n </div>\n ) : null\n\n return (\n <Dialog\n open={open}\n onOpenChange={(nextOpen) => {\n if (!nextOpen) onClose()\n }}\n >\n <DialogContent\n className=\"min-h-0 max-h-[min(90vh,760px)] overflow-hidden p-0 sm:max-w-[580px]\"\n onKeyDown={handleKeyDown}\n >\n <DialogHeader className=\"border-b border-border/70 px-6 py-5\">\n <DialogTitle className=\"text-2xl font-semibold leading-none\">\n {t('customers.roles.dialog.title', 'Assign role')}\n </DialogTitle>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('customers.roles.dialog.subtitle', 'Multi-role assignment for {{name}}', {\n name: entityName,\n })}\n </p>\n </DialogHeader>\n\n <div className=\"border-b border-border/70 px-6 py-4\">\n <StepIndicator\n className=\"justify-center\"\n steps={[\n {\n id: '1',\n label: t('customers.roles.dialog.step1', 'Role type'),\n status: step > 1 ? 'complete' : step === 1 ? 'current' : 'pending',\n },\n {\n id: '2',\n label: t('customers.roles.dialog.step2', 'Select person'),\n status: step > 2 ? 'complete' : step === 2 ? 'current' : 'pending',\n },\n {\n id: '3',\n label: t('customers.roles.dialog.step3', 'Confirm'),\n status: step === 3 ? 'current' : 'pending',\n },\n ] satisfies StepIndicatorStep[]}\n />\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n <div className=\"space-y-5 px-6 py-5\">\n {step === 1 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <p\n id=\"assign-role-dialog-type-label\"\n className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\"\n >\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <select\n aria-labelledby=\"assign-role-dialog-type-label\"\n value={selectedRoleType}\n onChange={(event) => setSelectedRoleType(event.target.value)}\n className=\"h-10 w-full rounded-md border border-border bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring/30\"\n >\n <option value=\"\">\n {t('customers.roles.selectRoleType', 'Select role type...')}\n </option>\n {availableRoleTypes.map((roleType) => (\n <option key={roleType.id} value={roleType.value}>\n {roleType.label}\n </option>\n ))}\n </select>\n {selectedRole ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n ) : null}\n </div>\n {canManageRoleTypes ? (\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n className=\"mt-3 inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline\"\n data-testid=\"assign-role-dialog-manage-role-types\"\n >\n <Settings2 className=\"size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n ) : null}\n </div>\n\n {!availableRoleTypes.length ? (\n <EmptyState\n size=\"sm\"\n icon={<CheckCheck className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t(\n 'customers.roles.dialog.noAvailableRoles',\n 'All available role types are already assigned.',\n )}\n />\n ) : null}\n </div>\n ) : null}\n\n {step === 2 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-1 flex items-center gap-2\">\n <span className=\"text-lg font-semibold text-foreground\">\n {selectedRole?.label ?? selectedRoleType}\n </span>\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {canManageRoleTypes ? (\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n data-testid=\"assign-role-dialog-manage-role-types-step2\"\n >\n <Settings2 className=\"mr-1 size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setStep(1)}\n >\n {t('customers.roles.dialog.change', 'Change')}\n </Button>\n </div>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.teamLabel', 'Select a team member')}\n </p>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n placeholder={t(\n 'customers.roles.dialog.searchPlaceholder',\n 'Search by name, e-mail or team...',\n )}\n className=\"h-10 rounded-md border-border/80 pl-9 shadow-none\"\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {teamFilters.map((teamFilter) => {\n const isActive = activeTeam === teamFilter.id\n return (\n <Button\n key={teamFilter.id}\n type=\"button\"\n variant={isActive ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setActiveTeam(teamFilter.id)}\n className=\"h-7 rounded-md px-2.5 text-xs\"\n >\n {teamFilter.label}\n <span className=\"rounded-full bg-background/80 px-1 text-xs text-muted-foreground\">\n {teamFilter.count}\n </span>\n </Button>\n )\n })}\n </div>\n\n <div className=\"space-y-2\">\n {visibleCountLabel ? (\n <p className=\"text-xs text-muted-foreground\">{visibleCountLabel}</p>\n ) : null}\n {loading ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t('customers.roles.loading', 'Loading...')}\n </div>\n ) : loadError ? (\n <div className=\"rounded-lg border border-dashed border-status-error-border bg-status-error-bg/70 px-4 py-8 text-center text-sm text-status-error-text\">\n {loadError}\n </div>\n ) : filteredUsers.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.noResults',\n 'No matching team members found.',\n )}\n </div>\n ) : (\n filteredUsers.map((user) => {\n const isSelected = selectedUser?.id === user.id\n const userConflicts = conflictsByUserId.get(user.id) ?? []\n return (\n <Button\n key={user.id}\n type=\"button\"\n variant=\"ghost\"\n onClick={() => setSelectedUser(user)}\n className={`h-auto flex w-full items-center gap-3 whitespace-normal break-words rounded-lg border px-4 py-3 text-left transition-colors ${\n isSelected\n ? 'border-foreground bg-background shadow-sm'\n : 'border-border/70 bg-background hover:bg-accent/40'\n }`}\n >\n <Avatar label={user.displayName} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">\n {user.displayName}\n </span>\n {user.teamName ? (\n <Badge\n variant=\"muted\"\n className=\"rounded-md px-2 py-0.5 text-xs font-medium\"\n >\n {user.teamName}\n </Badge>\n ) : null}\n {userConflicts.length ? (\n <Badge variant=\"info\">\n {t('customers.roles.dialog.alreadyAssigned', 'Already assigned: {{roles}}', {\n roles: userConflicts.join(', '),\n })}\n </Badge>\n ) : null}\n </div>\n {user.email ? (\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {user.email}\n </div>\n ) : null}\n </div>\n <span\n className={`flex size-6 shrink-0 items-center justify-center rounded-full border ${\n isSelected\n ? 'border-foreground bg-foreground text-background'\n : 'border-border/80 bg-background text-transparent'\n }`}\n >\n <Check className=\"size-3.5\" />\n </span>\n </Button>\n )\n })\n )}\n {canLoadMore && !loadError ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"w-full\"\n >\n {loadingMore\n ? t('customers.roles.dialog.loadingMore', 'Loading more...')\n : t('customers.roles.dialog.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n\n {previewCard}\n </div>\n ) : null}\n\n {step === 3 ? (\n <div className=\"space-y-4\">\n {previewCard}\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.constraint',\n 'One person per role. The assignment can be changed at any time.',\n )}\n </p>\n </div>\n ) : null}\n </div>\n </div>\n\n <DialogFooter\n bordered={false}\n className=\"shrink-0 border-t border-border/70 px-6 py-4\"\n leading={\n <p className=\"text-xs text-muted-foreground sm:max-w-2xs\">\n {t(\n 'customers.roles.dialog.footerNote',\n 'One person per role \u00B7 can be changed at any time',\n )}\n </p>\n }\n >\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('customers.roles.cancelAdd', 'Cancel')}\n </Button>\n {step === 1 ? (\n <Button\n type=\"button\"\n onClick={() => setStep(2)}\n disabled={!selectedRoleType || !availableRoleTypes.length}\n >\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 2 ? (\n <Button type=\"button\" onClick={() => setStep(3)} disabled={!selectedUser}>\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 3 ? (\n <Button\n type=\"button\"\n onClick={handleAssign}\n disabled={saving || !selectedUser || !selectedRoleType}\n >\n {saving\n ? t('customers.roles.assigning', 'Assigning...')\n : t('customers.roles.dialog.assign', 'Assign role')}\n </Button>\n ) : null}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,16 +3,23 @@ class Migration20260825140000_customers extends Migration {
|
|
|
3
3
|
async up() {
|
|
4
4
|
this.addSql(`alter table "customer_interactions" add column "channel_id" uuid null;`);
|
|
5
5
|
this.addSql(`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
do $$
|
|
7
|
+
begin
|
|
8
|
+
if to_regclass('message_channel_links') is not null
|
|
9
|
+
and to_regclass('external_conversations') is not null then
|
|
10
|
+
update "customer_interactions" ci
|
|
11
|
+
set "channel_id" = ec."channel_id"
|
|
12
|
+
from "message_channel_links" mcl
|
|
13
|
+
join "external_conversations" ec
|
|
14
|
+
on ec."id" = mcl."external_conversation_id"
|
|
15
|
+
and ec."tenant_id" = mcl."tenant_id"
|
|
16
|
+
where ci."external_message_id" = mcl."id"
|
|
17
|
+
and ci."tenant_id" = mcl."tenant_id"
|
|
18
|
+
and ci."interaction_type" = 'email'
|
|
19
|
+
and ci."channel_id" is null;
|
|
20
|
+
end if;
|
|
21
|
+
end
|
|
22
|
+
$$;
|
|
16
23
|
`);
|
|
17
24
|
this.addSql(`create index "customer_interactions_email_channel_idx" on "customer_interactions" ("channel_id", "entity_id") where "interaction_type" = 'email' and "channel_id" is not null and "deleted_at" is null;`);
|
|
18
25
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/migrations/Migration20260825140000_customers.ts"],
|
|
4
|
-
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260825140000_customers extends Migration {\n\n override async up(): Promise<void> {\n this.addSql(`alter table \"customer_interactions\" add column \"channel_id\" uuid null;`);\n\n // Backfill the denormalized channel id for pre-existing email rows.\n //\n // This is the only available chain \u2014 `message_channel_links` carries no\n // channel id of its own:\n // customer_interactions.external_message_id -> message_channel_links.id\n // message_channel_links.external_conversation_id -> external_conversations.id\n // external_conversations.channel_id -> communication_channels.id\n //\n // Rows whose chain cannot be resolved keep channel_id NULL, which every read\n // predicate treats as \"not shared\" (fail closed). Scoped by tenant on both\n // joins so a backfill can never attribute an interaction to another tenant's\n // channel.\n this.addSql(`\n update \"customer_interactions\" ci\n
|
|
5
|
-
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,0CAA0C,UAAU;AAAA,EAE/D,MAAe,KAAoB;AACjC,SAAK,OAAO,wEAAwE;
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260825140000_customers extends Migration {\n\n override async up(): Promise<void> {\n this.addSql(`alter table \"customer_interactions\" add column \"channel_id\" uuid null;`);\n\n // Backfill the denormalized channel id for pre-existing email rows.\n //\n // This is the only available chain \u2014 `message_channel_links` carries no\n // channel id of its own:\n // customer_interactions.external_message_id -> message_channel_links.id\n // message_channel_links.external_conversation_id -> external_conversations.id\n // external_conversations.channel_id -> communication_channels.id\n //\n // Rows whose chain cannot be resolved keep channel_id NULL, which every read\n // predicate treats as \"not shared\" (fail closed). Scoped by tenant on both\n // joins so a backfill can never attribute an interaction to another tenant's\n // channel.\n //\n // Both source tables belong to the optional communication_channels module. Apps\n // that do not enable it (e.g. the wms starter preset) have neither table and no\n // email rows to backfill, so the backfill runs only when both tables exist.\n this.addSql(`\n do $$\n begin\n if to_regclass('message_channel_links') is not null\n and to_regclass('external_conversations') is not null then\n update \"customer_interactions\" ci\n set \"channel_id\" = ec.\"channel_id\"\n from \"message_channel_links\" mcl\n join \"external_conversations\" ec\n on ec.\"id\" = mcl.\"external_conversation_id\"\n and ec.\"tenant_id\" = mcl.\"tenant_id\"\n where ci.\"external_message_id\" = mcl.\"id\"\n and ci.\"tenant_id\" = mcl.\"tenant_id\"\n and ci.\"interaction_type\" = 'email'\n and ci.\"channel_id\" is null;\n end if;\n end\n $$;\n `);\n\n this.addSql(`create index \"customer_interactions_email_channel_idx\" on \"customer_interactions\" (\"channel_id\", \"entity_id\") where \"interaction_type\" = 'email' and \"channel_id\" is not null and \"deleted_at\" is null;`);\n }\n\n override async down(): Promise<void> {\n this.addSql(`drop index if exists \"customer_interactions_email_channel_idx\";`);\n this.addSql(`alter table \"customer_interactions\" drop column if exists \"channel_id\";`);\n }\n\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,0CAA0C,UAAU;AAAA,EAE/D,MAAe,KAAoB;AACjC,SAAK,OAAO,wEAAwE;AAkBpF,SAAK,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAkBX;AAED,SAAK,OAAO,yMAAyM;AAAA,EACvN;AAAA,EAEA,MAAe,OAAsB;AACnC,SAAK,OAAO,iEAAiE;AAC7E,SAAK,OAAO,yEAAyE;AAAA,EACvF;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|