@open-mercato/core 0.6.8-develop.6987.1.02f619470c → 0.6.8-develop.6992.1.00c90fecff
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/customers/components/detail/AssignRoleDialog.js +9 -3
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +2 -3
- package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
- package/dist/modules/customers/components/detail/assignableStaff.js +2 -1
- package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js +11 -12
- package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +7 -4
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +9 -1
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +9 -1
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/lib/adapter-registry.js +10 -1
- package/dist/modules/data_sync/lib/adapter-registry.js.map +2 -2
- package/dist/modules/data_sync/lib/start-cursor.js +17 -0
- package/dist/modules/data_sync/lib/start-cursor.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-engine.js +5 -7
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +86 -10
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/data_sync/workers/sync-scheduled.js +9 -6
- package/dist/modules/data_sync/workers/sync-scheduled.js.map +2 -2
- package/dist/modules/sales/api/channels/route.js +1 -1
- package/dist/modules/sales/api/channels/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +12 -3
- package/src/modules/customers/components/detail/DealsSection.tsx +6 -11
- package/src/modules/customers/components/detail/assignableStaff.ts +9 -1
- package/src/modules/customers/components/detail/schedule/LinkedEntitiesField.tsx +16 -13
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +11 -4
- package/src/modules/data_sync/AGENTS.md +4 -1
- package/src/modules/data_sync/api/run.ts +9 -1
- package/src/modules/data_sync/api/runs/[id]/retry.ts +9 -1
- package/src/modules/data_sync/lib/adapter-registry.ts +15 -0
- package/src/modules/data_sync/lib/adapter.ts +18 -0
- package/src/modules/data_sync/lib/start-cursor.ts +35 -0
- package/src/modules/data_sync/lib/sync-engine.ts +5 -7
- package/src/modules/data_sync/lib/sync-run-service.ts +118 -10
- package/src/modules/data_sync/workers/sync-scheduled.ts +9 -6
- package/src/modules/sales/api/channels/route.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -7,6 +7,7 @@ import { Avatar } from "@open-mercato/ui/primitives/avatar";
|
|
|
7
7
|
import { EmptyState } from "@open-mercato/ui/primitives/empty-state";
|
|
8
8
|
import { StepIndicator } from "@open-mercato/ui/primitives/step-indicator";
|
|
9
9
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
10
|
+
import { hasMoreFromPage } from "@open-mercato/shared/lib/pagination/load-more";
|
|
10
11
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
11
12
|
import { Badge } from "@open-mercato/ui/primitives/badge";
|
|
12
13
|
import { Input } from "@open-mercato/ui/primitives/input";
|
|
@@ -43,6 +44,7 @@ function AssignRoleDialog({
|
|
|
43
44
|
const [activeTeam, setActiveTeam] = React.useState("all");
|
|
44
45
|
const [loadError, setLoadError] = React.useState(null);
|
|
45
46
|
const [totalUsers, setTotalUsers] = React.useState(0);
|
|
47
|
+
const [hasMore, setHasMore] = React.useState(false);
|
|
46
48
|
const [currentPage, setCurrentPage] = React.useState(1);
|
|
47
49
|
const deferredSearchQuery = React.useDeferredValue(searchQuery);
|
|
48
50
|
const requestSequenceRef = React.useRef(0);
|
|
@@ -60,6 +62,7 @@ function AssignRoleDialog({
|
|
|
60
62
|
setActiveTeam("all");
|
|
61
63
|
setLoadError(null);
|
|
62
64
|
setTotalUsers(0);
|
|
65
|
+
setHasMore(false);
|
|
63
66
|
setCurrentPage(1);
|
|
64
67
|
requestSequenceRef.current = 0;
|
|
65
68
|
return;
|
|
@@ -73,6 +76,7 @@ function AssignRoleDialog({
|
|
|
73
76
|
setActiveTeam("all");
|
|
74
77
|
setLoadError(null);
|
|
75
78
|
setTotalUsers(0);
|
|
79
|
+
setHasMore(false);
|
|
76
80
|
setCurrentPage(1);
|
|
77
81
|
requestSequenceRef.current = 0;
|
|
78
82
|
}, [initialRoleType, open]);
|
|
@@ -108,6 +112,7 @@ function AssignRoleDialog({
|
|
|
108
112
|
return Array.from(merged.values());
|
|
109
113
|
});
|
|
110
114
|
setTotalUsers(result.total);
|
|
115
|
+
setHasMore(hasMoreFromPage(result.servedCount, ASSIGNABLE_STAFF_PAGE_SIZE));
|
|
111
116
|
setCurrentPage(result.page);
|
|
112
117
|
setLoadError(null);
|
|
113
118
|
} catch {
|
|
@@ -115,6 +120,7 @@ function AssignRoleDialog({
|
|
|
115
120
|
if (!append) {
|
|
116
121
|
setUsers([]);
|
|
117
122
|
setTotalUsers(0);
|
|
123
|
+
setHasMore(false);
|
|
118
124
|
setCurrentPage(1);
|
|
119
125
|
}
|
|
120
126
|
setLoadError(
|
|
@@ -141,14 +147,14 @@ function AssignRoleDialog({
|
|
|
141
147
|
}
|
|
142
148
|
}, [deferredSearchQuery, searchUsers, step]);
|
|
143
149
|
const handleLoadMore = React.useCallback(() => {
|
|
144
|
-
if (loading || loadingMore ||
|
|
150
|
+
if (loading || loadingMore || !hasMore) return;
|
|
145
151
|
searchUsers({
|
|
146
152
|
query: deferredSearchQuery,
|
|
147
153
|
page: currentPage + 1,
|
|
148
154
|
append: true
|
|
149
155
|
}).catch(() => {
|
|
150
156
|
});
|
|
151
|
-
}, [currentPage, deferredSearchQuery, loading, loadingMore, searchUsers
|
|
157
|
+
}, [currentPage, deferredSearchQuery, hasMore, loading, loadingMore, searchUsers]);
|
|
152
158
|
const selectedRole = React.useMemo(
|
|
153
159
|
() => roleTypes.find((roleType) => roleType.value === selectedRoleType) ?? null,
|
|
154
160
|
[roleTypes, selectedRoleType]
|
|
@@ -203,7 +209,7 @@ function AssignRoleDialog({
|
|
|
203
209
|
}
|
|
204
210
|
);
|
|
205
211
|
}, [t, totalUsers, users.length]);
|
|
206
|
-
const canLoadMore =
|
|
212
|
+
const canLoadMore = hasMore;
|
|
207
213
|
const handleAssign = React.useCallback(async () => {
|
|
208
214
|
if (!selectedRoleType || !selectedUser) return;
|
|
209
215
|
setSaving(true);
|
|
@@ -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 { 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 { 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 [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 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 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 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 })\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 setCurrentPage(result.page)\n setLoadError(null)\n } catch {\n if (requestSequenceRef.current !== requestId) return\n if (!append) {\n setUsers([])\n setTotalUsers(0)\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 [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 || users.length >= totalUsers) 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, loading, loadingMore, searchUsers, totalUsers, users.length])\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 = users.length < totalUsers\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 className=\"shrink-0 border-t border-border/70 px-6 py-4 sm:justify-between\">\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'customers.roles.dialog.footerNote',\n 'One person per role \u00B7 can be changed at any time',\n )}\n </p>\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 { 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 [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 })\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 [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 className=\"shrink-0 border-t border-border/70 px-6 py-4 sm:justify-between\">\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'customers.roles.dialog.footerNote',\n 'One person per role \u00B7 can be changed at any time',\n )}\n </p>\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": ";AAiTQ,cAUI,YAVJ;AA/SR,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,QAAQ,OAAO,YAAY,iBAAiB;AACrD,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,qBAA6C;AACtD,SAAS,YAAY;AACrB,SAAS,uBAAuB;AAChC,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,uCAAuC;AAEhD,MAAM,yBAAyB;AA6B/B,MAAM,6BAA6B;AAE5B,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB,CAAC;AAAA,EACvB,kBAAkB;AAAA,EAClB,qBAAqB;AACvB,GAA0B;AACxB,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAiB,CAAC;AAChD,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAS,EAAE;AACjE,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAA6B,IAAI;AAC/E,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AACvD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,CAAC,CAAC;AAC1D,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,KAAK;AAC1D,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AACxD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAwB,IAAI;AACpE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AAIpD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,CAAC;AACtD,QAAM,sBAAsB,MAAM,iBAAiB,WAAW;AAC9D,QAAM,qBAAqB,MAAM,OAAO,CAAC;AAEzC,QAAM,qBAAqB,MAAM;AAAA,IAC/B,MAAM,UAAU,OAAO,CAAC,aAAa,CAAC,mBAAmB,IAAI,SAAS,KAAK,CAAC;AAAA,IAC5E,CAAC,mBAAmB,SAAS;AAAA,EAC/B;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,MAAM;AACT,cAAQ,CAAC;AACT,0BAAoB,EAAE;AACtB,sBAAgB,IAAI;AACpB,qBAAe,EAAE;AACjB,eAAS,CAAC,CAAC;AACX,oBAAc,KAAK;AACnB,mBAAa,IAAI;AACjB,oBAAc,CAAC;AACf,iBAAW,KAAK;AAChB,qBAAe,CAAC;AAChB,yBAAmB,UAAU;AAC7B;AAAA,IACF;AACA,UAAM,0BACJ,OAAO,oBAAoB,YAAY,gBAAgB,KAAK,EAAE,SAAS,IACnE,gBAAgB,KAAK,IACrB;AACN,YAAQ,0BAA0B,IAAI,CAAC;AACvC,wBAAoB,uBAAuB;AAC3C,oBAAgB,IAAI;AACpB,mBAAe,EAAE;AACjB,aAAS,CAAC,CAAC;AACX,kBAAc,KAAK;AACnB,iBAAa,IAAI;AACjB,kBAAc,CAAC;AACf,eAAW,KAAK;AAChB,mBAAe,CAAC;AAChB,uBAAmB,UAAU;AAAA,EAC/B,GAAG,CAAC,iBAAiB,IAAI,CAAC;AAE1B,QAAM,cAAc,MAAM;AAAA,IACxB,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAIM;AACJ,YAAM,YAAY,SAAS,mBAAmB,UAAU,mBAAmB,UAAU;AACrF,yBAAmB,UAAU;AAE7B,UAAI,QAAQ;AACV,uBAAe,IAAI;AAAA,MACrB,OAAO;AACL,mBAAW,IAAI;AAAA,MACjB;AAEA,UAAI;AACF,cAAM,SAAS,MAAM,gCAAgC,OAAO;AAAA,UAC1D;AAAA,UACA,UAAU;AAAA,QACZ,CAAC;AACD,YAAI,mBAAmB,YAAY,UAAW;AAE9C,cAAM,YAAY,OAAO,MAAM,IAAI,CAAC,YAAY;AAAA,UAC9C,IAAI,OAAO;AAAA,UACX,aAAa,OAAO;AAAA,UACpB,OAAO,OAAO;AAAA,UACd,UAAU,OAAO;AAAA,QACnB,EAAE;AAEF,iBAAS,CAAC,YAAY;AACpB,cAAI,CAAC,OAAQ,QAAO;AACpB,gBAAM,SAAS,IAAI,IAAI,QAAQ,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;AAC7D,oBAAU,QAAQ,CAAC,SAAS,OAAO,IAAI,KAAK,IAAI,IAAI,CAAC;AACrD,iBAAO,MAAM,KAAK,OAAO,OAAO,CAAC;AAAA,QACnC,CAAC;AACD,sBAAc,OAAO,KAAK;AAC1B,mBAAW,gBAAgB,OAAO,aAAa,0BAA0B,CAAC;AAC1E,uBAAe,OAAO,IAAI;AAC1B,qBAAa,IAAI;AAAA,MACnB,QAAQ;AACN,YAAI,mBAAmB,YAAY,UAAW;AAC9C,YAAI,CAAC,QAAQ;AACX,mBAAS,CAAC,CAAC;AACX,wBAAc,CAAC;AACf,qBAAW,KAAK;AAChB,yBAAe,CAAC;AAAA,QAClB;AACA;AAAA,UACE;AAAA,YACE;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF,UAAE;AACA,YAAI,mBAAmB,YAAY,UAAW;AAC9C,YAAI,QAAQ;AACV,yBAAe,KAAK;AAAA,QACtB,OAAO;AACL,qBAAW,KAAK;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,QAAQ,GAAG;AAEb,kBAAY,EAAE,OAAO,qBAAqB,MAAM,GAAG,QAAQ,MAAM,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACpF;AAAA,EACF,GAAG,CAAC,qBAAqB,aAAa,IAAI,CAAC;AAE3C,QAAM,iBAAiB,MAAM,YAAY,MAAM;AAC7C,QAAI,WAAW,eAAe,CAAC,QAAS;AAExC,gBAAY;AAAA,MACV,OAAO;AAAA,MACP,MAAM,cAAc;AAAA,MACpB,QAAQ;AAAA,IACV,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnB,GAAG,CAAC,aAAa,qBAAqB,SAAS,SAAS,aAAa,WAAW,CAAC;AAEjF,QAAM,eAAe,MAAM;AAAA,IACzB,MAAM,UAAU,KAAK,CAAC,aAAa,SAAS,UAAU,gBAAgB,KAAK;AAAA,IAC3E,CAAC,WAAW,gBAAgB;AAAA,EAC9B;AAEA,QAAM,mBAAmB,MAAM;AAAA,IAC7B,MAAM,IAAI,IAAI,UAAU,IAAI,CAAC,aAAa,CAAC,SAAS,OAAO,SAAS,KAAK,CAAC,CAAC;AAAA,IAC3E,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAC5C,UAAM,OAAO,oBAAI,IAAsB;AACvC,wBAAoB,QAAQ,CAAC,eAAe;AAC1C,UAAI,CAAC,WAAW,OAAQ;AACxB,UAAI,WAAW,aAAa,iBAAkB;AAC9C,YAAM,QAAQ,iBAAiB,IAAI,WAAW,QAAQ,KAAK,WAAW;AACtE,YAAM,UAAU,KAAK,IAAI,WAAW,MAAM,KAAK,CAAC;AAChD,UAAI,CAAC,QAAQ,SAAS,KAAK,GAAG;AAC5B,gBAAQ,KAAK,KAAK;AAClB,aAAK,IAAI,WAAW,QAAQ,OAAO;AAAA,MACrC;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT,GAAG,CAAC,qBAAqB,kBAAkB,gBAAgB,CAAC;AAE5D,QAAM,uBAAuB,MAAM,QAAQ,MAAM;AAC/C,QAAI,CAAC,aAAc,QAAO;AAC1B,WAAO,kBAAkB,IAAI,aAAa,EAAE,KAAK;AAAA,EACnD,GAAG,CAAC,mBAAmB,YAAY,CAAC;AAEpC,QAAM,cAAc,MAAM,QAAsB,MAAM;AACpD,UAAM,SAAS,oBAAI,IAAoB;AACvC,UAAM,QAAQ,CAAC,SAAS;AACtB,YAAM,MACJ,KAAK,UAAU,KAAK,KAAK,EAAE,0CAA0C,SAAS;AAChF,aAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,IAC5C,CAAC;AACD,WAAO;AAAA,MACL,EAAE,IAAI,OAAO,OAAO,EAAE,mCAAmC,KAAK,GAAG,OAAO,MAAM,OAAO;AAAA,MACrF,GAAG,MAAM,KAAK,OAAO,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,EAAE,IAAI,OAAO,OAAO,MAAM,EAAE;AAAA,IACvF;AAAA,EACF,GAAG,CAAC,GAAG,KAAK,CAAC;AAEb,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AACxC,QAAI,eAAe,MAAO,QAAO;AACjC,WAAO,MAAM;AAAA,MACX,CAAC,UACE,KAAK,UAAU,KAAK,KAAK,EAAE,0CAA0C,SAAS,OAC/E;AAAA,IACJ;AAAA,EACF,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC;AAEzB,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAC5C,QAAI,cAAc,EAAG,QAAO;AAC5B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,OAAO,MAAM,MAAM;AAAA,QAC1B,OAAO,OAAO,UAAU;AAAA,MAC1B;AAAA,IACF;AAAA,EACF,GAAG,CAAC,GAAG,YAAY,MAAM,MAAM,CAAC;AAEhC,QAAM,cAAc;AAEpB,QAAM,eAAe,MAAM,YAAY,YAAY;AACjD,QAAI,CAAC,oBAAoB,CAAC,aAAc;AACxC,cAAU,IAAI;AACd,QAAI;AACF,YAAM,SAAS,kBAAkB,aAAa,EAAE;AAChD,cAAQ;AAAA,IACV,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,UAAU,SAAS,kBAAkB,YAAY,CAAC;AAEtD,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,MAA2B;AAC1B,UAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,UAAU;AACjD,UAAE,eAAe;AACjB,YAAI,SAAS,KAAK,CAAC,UAAU,gBAAgB,kBAAkB;AAC7D,uBAAa;AAAA,QACf,WAAW,SAAS,KAAK,oBAAoB,mBAAmB,QAAQ;AACtE,kBAAQ,CAAC;AAAA,QACX,WAAW,SAAS,KAAK,cAAc;AACrC,kBAAQ,CAAC;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,mBAAmB,QAAQ,cAAc,QAAQ,kBAAkB,cAAc,IAAI;AAAA,EACxF;AAEA,QAAM,cACJ,gBAAgB,eACd,qBAAC,SAAI,WAAU,4DACb;AAAA,wBAAC,OAAE,WAAU,iFACV,YAAE,kCAAkC,oBAAoB,GAC3D;AAAA,IACA,qBAAC,SAAI,WAAU,gCACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,aAAa;AAAA,UACpB,MAAK;AAAA,UACL,WAAU;AAAA;AAAA,MACZ;AAAA,MACA,qBAAC,SAAI,WAAU,WACb;AAAA,6BAAC,SAAI,WAAU,2EACb;AAAA,8BAAC,UAAM,uBAAa,aAAY;AAAA,UAChC,oBAAC,UAAK,WAAU,yBAAwB,oBAAC;AAAA,UACzC,oBAAC,UAAM,uBAAa,OAAM;AAAA,WAC5B;AAAA,QACA,qBAAC,SAAI,WAAU,wEACZ;AAAA,uBAAa,QAAQ,oBAAC,UAAM,uBAAa,OAAM,IAAU;AAAA,UACzD,aAAa,WAAW,oBAAC,UAAM,uBAAa,UAAS,IAAU;AAAA,WAClE;AAAA,QACC,sBAAsB,SACrB,oBAAC,SAAI,WAAU,0CACb;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,WAAU;AAAA,YAET,YAAE,mCAAmC,uBAAuB;AAAA,cAC3D,OAAO,qBAAqB,KAAK,IAAI;AAAA,YACvC,CAAC;AAAA;AAAA,QACH,GACF,IACE;AAAA,SACN;AAAA,OACF;AAAA,KACF,IACE;AAEN,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,cAAc,CAAC,aAAa;AAC1B,YAAI,CAAC,SAAU,SAAQ;AAAA,MACzB;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,WAAW;AAAA,UAEX;AAAA,iCAAC,gBAAa,WAAU,uCACtB;AAAA,kCAAC,eAAY,WAAU,uCACpB,YAAE,gCAAgC,aAAa,GAClD;AAAA,cACA,oBAAC,OAAE,WAAU,sCACV,YAAE,mCAAmC,sCAAsC;AAAA,gBAC1E,MAAM;AAAA,cACR,CAAC,GACH;AAAA,eACF;AAAA,YAEA,oBAAC,SAAI,WAAU,uCACb;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO;AAAA,kBACL;AAAA,oBACE,IAAI;AAAA,oBACJ,OAAO,EAAE,gCAAgC,WAAW;AAAA,oBACpD,QAAQ,OAAO,IAAI,aAAa,SAAS,IAAI,YAAY;AAAA,kBAC3D;AAAA,kBACA;AAAA,oBACE,IAAI;AAAA,oBACJ,OAAO,EAAE,gCAAgC,eAAe;AAAA,oBACxD,QAAQ,OAAO,IAAI,aAAa,SAAS,IAAI,YAAY;AAAA,kBAC3D;AAAA,kBACA;AAAA,oBACE,IAAI;AAAA,oBACJ,OAAO,EAAE,gCAAgC,SAAS;AAAA,oBAClD,QAAQ,SAAS,IAAI,YAAY;AAAA,kBACnC;AAAA,gBACF;AAAA;AAAA,YACF,GACF;AAAA,YAEA,oBAAC,SAAI,WAAU,kCACb,+BAAC,SAAI,WAAU,uBACZ;AAAA,uBAAS,IACR,qBAAC,SAAI,WAAU,aACb;AAAA,qCAAC,SAAI,WAAU,oCACb;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,IAAG;AAAA,sBACH,WAAU;AAAA,sBAET,YAAE,wCAAwC,WAAW;AAAA;AAAA,kBACxD;AAAA,kBACA,qBAAC,SAAI,WAAU,gCACb;AAAA;AAAA,sBAAC;AAAA;AAAA,wBACC,mBAAgB;AAAA,wBAChB,OAAO;AAAA,wBACP,UAAU,CAAC,UAAU,oBAAoB,MAAM,OAAO,KAAK;AAAA,wBAC3D,WAAU;AAAA,wBAEV;AAAA,8CAAC,YAAO,OAAM,IACX,YAAE,kCAAkC,qBAAqB,GAC5D;AAAA,0BACC,mBAAmB,IAAI,CAAC,aACvB,oBAAC,YAAyB,OAAO,SAAS,OACvC,mBAAS,SADC,SAAS,EAEtB,CACD;AAAA;AAAA;AAAA,oBACH;AAAA,oBACC,eACC;AAAA,sBAAC;AAAA;AAAA,wBACC,SAAQ;AAAA,wBACR,WAAU;AAAA,wBAET,YAAE,iDAAiD,YAAY;AAAA;AAAA,oBAClE,IACE;AAAA,qBACN;AAAA,kBACC,qBACC;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAM;AAAA,sBACN,WAAU;AAAA,sBACV,eAAY;AAAA,sBAEZ;AAAA,4CAAC,aAAU,WAAU,YAAW,eAAY,QAAO;AAAA,wBAClD,EAAE,0CAA0C,mBAAmB;AAAA;AAAA;AAAA,kBAClE,IACE;AAAA,mBACN;AAAA,gBAEC,CAAC,mBAAmB,SACnB;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,MAAM,oBAAC,cAAW,WAAU,WAAU,eAAY,QAAO;AAAA,oBACzD,OAAO;AAAA,sBACL;AAAA,sBACA;AAAA,oBACF;AAAA;AAAA,gBACF,IACE;AAAA,iBACN,IACE;AAAA,cAEH,SAAS,IACR,qBAAC,SAAI,WAAU,aACb;AAAA,oCAAC,SAAI,WAAU,oCACb,+BAAC,SAAI,WAAU,2CACb;AAAA,uCAAC,SACC;AAAA,wCAAC,OAAE,WAAU,iFACV,YAAE,wCAAwC,WAAW,GACxD;AAAA,oBACA,qBAAC,SAAI,WAAU,gCACb;AAAA,0CAAC,UAAK,WAAU,yCACb,wBAAc,SAAS,kBAC1B;AAAA,sBACA;AAAA,wBAAC;AAAA;AAAA,0BACC,SAAQ;AAAA,0BACR,WAAU;AAAA,0BAET,YAAE,iDAAiD,YAAY;AAAA;AAAA,sBAClE;AAAA,uBACF;AAAA,qBACF;AAAA,kBACA,qBAAC,SAAI,WAAU,2BACZ;AAAA,yCACC,oBAAC,UAAO,SAAO,MAAC,SAAQ,SAAQ,MAAK,MACnC;AAAA,sBAAC;AAAA;AAAA,wBACC,MAAM;AAAA,wBACN,eAAY;AAAA,wBAEZ;AAAA,8CAAC,aAAU,WAAU,iBAAgB,eAAY,QAAO;AAAA,0BACvD,EAAE,0CAA0C,mBAAmB;AAAA;AAAA;AAAA,oBAClE,GACF,IACE;AAAA,oBACJ;AAAA,sBAAC;AAAA;AAAA,wBACC,MAAK;AAAA,wBACL,SAAQ;AAAA,wBACR,MAAK;AAAA,wBACL,SAAS,MAAM,QAAQ,CAAC;AAAA,wBAEvB,YAAE,iCAAiC,QAAQ;AAAA;AAAA,oBAC9C;AAAA,qBACF;AAAA,mBACF,GACF;AAAA,gBAEA,qBAAC,SAAI,WAAU,aACb;AAAA,sCAAC,OAAE,WAAU,iFACV,YAAE,oCAAoC,sBAAsB,GAC/D;AAAA,kBACA,qBAAC,SAAI,WAAU,YACb;AAAA,wCAAC,UAAO,WAAU,yEAAwE;AAAA,oBAC1F;AAAA,sBAAC;AAAA;AAAA,wBACC,OAAO;AAAA,wBACP,UAAU,CAAC,UAAU,eAAe,MAAM,OAAO,KAAK;AAAA,wBACtD,aAAa;AAAA,0BACX;AAAA,0BACA;AAAA,wBACF;AAAA,wBACA,WAAU;AAAA;AAAA,oBACZ;AAAA,qBACF;AAAA,mBACF;AAAA,gBAEA,oBAAC,SAAI,WAAU,qCACZ,sBAAY,IAAI,CAAC,eAAe;AAC/B,wBAAM,WAAW,eAAe,WAAW;AAC3C,yBACE;AAAA,oBAAC;AAAA;AAAA,sBAEC,MAAK;AAAA,sBACL,SAAS,WAAW,YAAY;AAAA,sBAChC,MAAK;AAAA,sBACL,SAAS,MAAM,cAAc,WAAW,EAAE;AAAA,sBAC1C,WAAU;AAAA,sBAET;AAAA,mCAAW;AAAA,wBACZ,oBAAC,UAAK,WAAU,oEACb,qBAAW,OACd;AAAA;AAAA;AAAA,oBAVK,WAAW;AAAA,kBAWlB;AAAA,gBAEJ,CAAC,GACH;AAAA,gBAEA,qBAAC,SAAI,WAAU,aACZ;AAAA,sCACC,oBAAC,OAAE,WAAU,iCAAiC,6BAAkB,IAC9D;AAAA,kBACH,UACC,oBAAC,SAAI,WAAU,wGACZ,YAAE,2BAA2B,YAAY,GAC5C,IACE,YACF,oBAAC,SAAI,WAAU,yIACZ,qBACH,IACE,cAAc,WAAW,IAC3B,oBAAC,SAAI,WAAU,wGACZ;AAAA,oBACC;AAAA,oBACA;AAAA,kBACF,GACF,IAEA,cAAc,IAAI,CAAC,SAAS;AAC1B,0BAAM,aAAa,cAAc,OAAO,KAAK;AAC7C,0BAAM,gBAAgB,kBAAkB,IAAI,KAAK,EAAE,KAAK,CAAC;AACzD,2BACE;AAAA,sBAAC;AAAA;AAAA,wBAEC,MAAK;AAAA,wBACL,SAAQ;AAAA,wBACR,SAAS,MAAM,gBAAgB,IAAI;AAAA,wBACnC,WAAW,iGACT,aACI,8CACA,mDACN;AAAA,wBAEA;AAAA,8CAAC,UAAO,OAAO,KAAK,aAAa,MAAK,MAAK,SAAQ,cAAa;AAAA,0BAChE,qBAAC,SAAI,WAAU,kBACb;AAAA,iDAAC,SAAI,WAAU,qCACb;AAAA,kDAAC,UAAK,WAAU,yCACb,eAAK,aACR;AAAA,8BACC,KAAK,WACJ;AAAA,gCAAC;AAAA;AAAA,kCACC,SAAQ;AAAA,kCACR,WAAU;AAAA,kCAET,eAAK;AAAA;AAAA,8BACR,IACE;AAAA,8BACH,cAAc,SACb;AAAA,gCAAC;AAAA;AAAA,kCACC,SAAQ;AAAA,kCACR,WAAU;AAAA,kCAET,YAAE,mCAAmC,uBAAuB;AAAA,oCAC3D,OAAO,cAAc,KAAK,IAAI;AAAA,kCAChC,CAAC;AAAA;AAAA,8BACH,IACE;AAAA,+BACN;AAAA,4BACC,KAAK,QACJ,oBAAC,SAAI,WAAU,sCACZ,eAAK,OACR,IACE;AAAA,6BACN;AAAA,0BACA;AAAA,4BAAC;AAAA;AAAA,8BACC,WAAW,wEACT,aACI,oDACA,iDACN;AAAA,8BAEA,8BAAC,SAAM,WAAU,YAAW;AAAA;AAAA,0BAC9B;AAAA;AAAA;AAAA,sBAjDK,KAAK;AAAA,oBAkDZ;AAAA,kBAEJ,CAAC;AAAA,kBAEF,eAAe,CAAC,YACf;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,SAAQ;AAAA,sBACR,SAAS;AAAA,sBACT,UAAU;AAAA,sBACV,WAAU;AAAA,sBAET,wBACG,EAAE,sCAAsC,iBAAiB,IACzD,EAAE,mCAAmC,WAAW;AAAA;AAAA,kBACtD,IACE;AAAA,mBACN;AAAA,gBAEC;AAAA,iBACH,IACE;AAAA,cAEH,SAAS,IACR,qBAAC,SAAI,WAAU,aACZ;AAAA;AAAA,gBACD,oBAAC,OAAE,WAAU,iCACV;AAAA,kBACC;AAAA,kBACA;AAAA,gBACF,GACF;AAAA,iBACF,IACE;AAAA,eACN,GACF;AAAA,YAEA,qBAAC,gBAAa,WAAU,mEACtB;AAAA,kCAAC,OAAE,WAAU,iCACV;AAAA,gBACC;AAAA,gBACA;AAAA,cACF,GACF;AAAA,cACA,qBAAC,SAAI,WAAU,2BACb;AAAA,oCAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,SAC9C,YAAE,6BAA6B,QAAQ,GAC1C;AAAA,gBACC,SAAS,IACR;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAS,MAAM,QAAQ,CAAC;AAAA,oBACxB,UAAU,CAAC,oBAAoB,CAAC,mBAAmB;AAAA,oBAElD,YAAE,+BAA+B,MAAM;AAAA;AAAA,gBAC1C,IACE;AAAA,gBACH,SAAS,IACR,oBAAC,UAAO,MAAK,UAAS,SAAS,MAAM,QAAQ,CAAC,GAAG,UAAU,CAAC,cACzD,YAAE,+BAA+B,MAAM,GAC1C,IACE;AAAA,gBACH,SAAS,IACR;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAS;AAAA,oBACT,UAAU,UAAU,CAAC,gBAAgB,CAAC;AAAA,oBAErC,mBACG,EAAE,6BAA6B,cAAc,IAC7C,EAAE,iCAAiC,aAAa;AAAA;AAAA,gBACtD,IACE;AAAA,iBACN;AAAA,eACF;AAAA;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,6 +13,7 @@ import { createDealLinkAdapter } from "../linking/adapters/dealAdapter.js";
|
|
|
13
13
|
import { LoadingMessage, TabEmptyState } from "@open-mercato/ui/backend/detail";
|
|
14
14
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
15
15
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
16
|
+
import { hasMoreFromPage } from "@open-mercato/shared/lib/pagination/load-more";
|
|
16
17
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
17
18
|
import { E } from "../../../../generated/entities.ids.generated.js";
|
|
18
19
|
import { createTranslatorWithFallback } from "@open-mercato/shared/lib/i18n/translate";
|
|
@@ -367,9 +368,7 @@ function DealsSection({
|
|
|
367
368
|
return [...updatedPrev, ...appended];
|
|
368
369
|
});
|
|
369
370
|
pageRef.current = nextPage;
|
|
370
|
-
const
|
|
371
|
-
const totalPages = typeof totalPagesRaw === "number" ? totalPagesRaw : typeof totalPagesRaw === "string" && totalPagesRaw.trim().length ? Number(totalPagesRaw) : null;
|
|
372
|
-
const nextHasMore = totalPages && Number.isFinite(totalPages) ? nextPage < totalPages : mapped.length === DEALS_PAGE_SIZE;
|
|
371
|
+
const nextHasMore = hasMoreFromPage(rawItems.length, DEALS_PAGE_SIZE);
|
|
373
372
|
hasMoreRef.current = nextHasMore;
|
|
374
373
|
setHasMore(nextHasMore);
|
|
375
374
|
setLoadError(null);
|