@open-mercato/core 0.6.1-develop.3256.1.fe3dec2464 → 0.6.1-develop.3260.1.dc719cac84
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/helpers/integration/authFixtures.js.map +1 -1
- package/dist/helpers/integration/authUi.js +10 -2
- package/dist/helpers/integration/authUi.js.map +2 -2
- package/dist/helpers/integration/salesUi.js +3 -1
- package/dist/helpers/integration/salesUi.js.map +2 -2
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js +10 -1
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
- package/dist/modules/audit_logs/lib/display-helpers.js +28 -27
- package/dist/modules/audit_logs/lib/display-helpers.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +21 -3
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +6 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +4 -1
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +56 -2
- package/dist/modules/auth/backend/users/page.js.map +3 -3
- package/dist/modules/auth/commands/users.js +14 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/validators.js +8 -0
- package/dist/modules/auth/data/validators.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -9
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +26 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +1 -1
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +108 -99
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +13 -5
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +10 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js +15 -8
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/RolesSection.js +35 -22
- package/dist/modules/customers/components/detail/RolesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +73 -64
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
- package/dist/modules/customers/components/linking/LinkEntityDialog.js +11 -2
- package/dist/modules/customers/components/linking/LinkEntityDialog.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +2 -9
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +14 -29
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +10 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +79 -95
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js +49 -70
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRuleSetForm.js +1 -1
- package/dist/modules/planner/components/AvailabilityRuleSetForm.js.map +1 -1
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +19 -19
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/resources/components/ResourceCrudForm.js +1 -1
- package/dist/modules/resources/components/ResourceCrudForm.js.map +1 -1
- package/dist/modules/resources/components/ResourceTypeCrudForm.js +1 -1
- package/dist/modules/resources/components/ResourceTypeCrudForm.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnDialog.js +11 -8
- package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
- package/dist/modules/staff/backend/staff/profile/create/page.js +1 -1
- package/dist/modules/staff/backend/staff/profile/create/page.js.map +2 -2
- package/dist/modules/staff/components/TeamForm.js +1 -1
- package/dist/modules/staff/components/TeamForm.js.map +2 -2
- package/dist/modules/staff/components/TeamMemberForm.js +1 -1
- package/dist/modules/staff/components/TeamMemberForm.js.map +1 -1
- package/dist/modules/staff/components/TeamRoleForm.js +1 -1
- package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
- package/package.json +5 -5
- package/src/helpers/integration/authFixtures.ts +1 -1
- package/src/helpers/integration/authUi.ts +11 -2
- package/src/helpers/integration/salesUi.ts +10 -1
- package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +7 -3
- package/src/modules/audit_logs/lib/display-helpers.tsx +22 -14
- package/src/modules/auth/api/users/route.ts +22 -3
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +9 -1
- package/src/modules/auth/backend/users/create/page.tsx +6 -2
- package/src/modules/auth/backend/users/page.tsx +60 -3
- package/src/modules/auth/commands/users.ts +15 -0
- package/src/modules/auth/data/validators.ts +8 -0
- package/src/modules/auth/i18n/de.json +4 -0
- package/src/modules/auth/i18n/en.json +4 -0
- package/src/modules/auth/i18n/es.json +4 -0
- package/src/modules/auth/i18n/pl.json +4 -0
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -1
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +12 -13
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +17 -13
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +1 -1
- package/src/modules/customers/components/PipelineSettings.tsx +95 -95
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +7 -4
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +13 -8
- package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +16 -12
- package/src/modules/customers/components/detail/RolesSection.tsx +34 -31
- package/src/modules/customers/components/detail/TasksSection.tsx +16 -8
- package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +77 -51
- package/src/modules/customers/components/linking/LinkEntityDialog.tsx +7 -3
- package/src/modules/data_sync/backend/data-sync/page.tsx +4 -7
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +19 -54
- package/src/modules/dictionaries/components/DictionariesManager.tsx +7 -4
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +101 -146
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +60 -103
- package/src/modules/planner/components/AvailabilityRuleSetForm.tsx +1 -1
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +17 -17
- package/src/modules/resources/components/ResourceCrudForm.tsx +1 -1
- package/src/modules/resources/components/ResourceTypeCrudForm.tsx +1 -1
- package/src/modules/sales/components/documents/ReturnDialog.tsx +10 -7
- package/src/modules/sales/i18n/de.json +2 -0
- package/src/modules/sales/i18n/en.json +2 -0
- package/src/modules/sales/i18n/es.json +2 -0
- package/src/modules/sales/i18n/pl.json +2 -0
- package/src/modules/staff/backend/staff/profile/create/page.tsx +1 -1
- package/src/modules/staff/components/TeamForm.tsx +1 -1
- package/src/modules/staff/components/TeamMemberForm.tsx +1 -1
- package/src/modules/staff/components/TeamRoleForm.tsx +1 -1
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
|
-
import { Plus } from "lucide-react";
|
|
5
|
+
import { Plus, Settings, UserCheck } from "lucide-react";
|
|
6
|
+
import { EmptyState } from "@open-mercato/ui/primitives/empty-state";
|
|
6
7
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
7
8
|
import { apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
8
9
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
@@ -185,27 +186,39 @@ function RolesSection({ entityType, entityId, entityName }) {
|
|
|
185
186
|
] }, entry.roleType)
|
|
186
187
|
)
|
|
187
188
|
}
|
|
188
|
-
) : hasConfiguredRoleTypes ? /* @__PURE__ */
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
{
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
189
|
+
) : hasConfiguredRoleTypes ? /* @__PURE__ */ jsx(
|
|
190
|
+
EmptyState,
|
|
191
|
+
{
|
|
192
|
+
size: "sm",
|
|
193
|
+
icon: /* @__PURE__ */ jsx(UserCheck, { className: "h-8 w-8", "aria-hidden": "true" }),
|
|
194
|
+
title: t("customers.roles.emptyState", "No roles assigned yet. Click below to assign a person."),
|
|
195
|
+
actions: /* @__PURE__ */ jsxs(
|
|
196
|
+
Button,
|
|
197
|
+
{
|
|
198
|
+
type: "button",
|
|
199
|
+
variant: "outline",
|
|
200
|
+
size: "sm",
|
|
201
|
+
onClick: () => openDialog(null),
|
|
202
|
+
children: [
|
|
203
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
204
|
+
t("customers.roles.choosePerson", "Choose person")
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
) : /* @__PURE__ */ jsx(
|
|
210
|
+
EmptyState,
|
|
211
|
+
{
|
|
212
|
+
size: "sm",
|
|
213
|
+
icon: /* @__PURE__ */ jsx(Settings, { className: "h-8 w-8", "aria-hidden": "true" }),
|
|
214
|
+
title: t("customers.roles.noRoleTypesTitle", "No role types configured"),
|
|
215
|
+
description: t(
|
|
216
|
+
"customers.roles.noRoleTypesDescription",
|
|
217
|
+
"Create role types in Customers config before assigning owners here."
|
|
218
|
+
),
|
|
219
|
+
actions: /* @__PURE__ */ jsx(Button, { asChild: true, type: "button", variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/config/customers", children: t("customers.roles.configureRoleTypes", "Configure role types") }) })
|
|
220
|
+
}
|
|
221
|
+
),
|
|
209
222
|
hasConfiguredRoleTypes ? /* @__PURE__ */ jsxs(
|
|
210
223
|
Button,
|
|
211
224
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/RolesSection.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Plus } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport { RoleAssignmentRow, type RoleAssignment } from './RoleAssignmentRow'\nimport { AssignRoleDialog } from './AssignRoleDialog'\n\ninterface RolesSectionProps {\n entityType: 'company' | 'person'\n entityId: string\n entityName?: string | null\n}\n\ntype GuardedMutationRunner = <T,>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\nexport function RolesSection({ entityType, entityId, entityName }: RolesSectionProps) {\n const t = useT()\n const { payload } = useBackendChrome()\n const canManageRoleTypes = React.useMemo(\n () => hasFeature(payload?.grantedFeatures ?? [], 'customers.settings.manage'),\n [payload?.grantedFeatures],\n )\n const [roles, setRoles] = React.useState<RoleAssignment[]>([])\n const [roleTypes, setRoleTypes] = React.useState<DictionaryEntryOption[]>([])\n const [loading, setLoading] = React.useState(true)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [initialRoleType, setInitialRoleType] = React.useState<string | null>(null)\n const hasConfiguredRoleTypes = roleTypes.length > 0\n\n const basePath = entityType === 'company' ? 'companies' : 'people'\n const resourceKind = entityType === 'company' ? 'customers.company' : 'customers.person'\n const mutationContextId = React.useMemo(\n () => `customer-roles:${entityType}:${entityId}`,\n [entityId, entityType],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n entityType: 'company' | 'person'\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: mutationContextId,\n resourceKind,\n resourceId: entityId,\n entityType,\n retryLastMutation,\n }),\n [entityId, entityType, mutationContextId, resourceKind, retryLastMutation],\n )\n const runMutationWithContext = React.useCallback<GuardedMutationRunner>(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>) => {\n return runMutation({\n operation,\n mutationPayload,\n context: mutationContext,\n })\n },\n [mutationContext, runMutation],\n )\n\n const loadRoles = React.useCallback(async () => {\n try {\n const data = await readApiResultOrThrow<{ items?: RoleAssignment[] }>(\n `/api/customers/${basePath}/${entityId}/roles`,\n )\n setRoles(Array.isArray(data?.items) ? data.items : [])\n } catch (error) {\n console.error('customers.roles.load failed', error)\n flash(t('customers.roles.loadFailed', 'Failed to load role assignments.'), 'error')\n setRoles([])\n }\n setLoading(false)\n }, [basePath, entityId, t])\n\n React.useEffect(() => {\n loadRoles()\n }, [loadRoles])\n\n React.useEffect(() => {\n let active = true\n\n readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n '/api/customers/dictionaries/person-company-roles',\n )\n .then((payload) => {\n if (!active) return\n const entries = (Array.isArray(payload?.items) ? payload.items : [])\n .map((item) => {\n const id = typeof item.id === 'string' ? item.id : null\n const value = typeof item.value === 'string' ? item.value.trim() : ''\n if (!id || value.length === 0) return null\n return {\n id,\n value,\n label:\n typeof item.label === 'string' && item.label.trim().length > 0\n ? item.label.trim()\n : value,\n color: typeof item.color === 'string' ? item.color : null,\n icon: typeof item.icon === 'string' ? item.icon : null,\n } satisfies DictionaryEntryOption\n })\n .filter((entry): entry is DictionaryEntryOption => entry !== null)\n\n setRoleTypes(entries)\n })\n .catch((error) => {\n if (!active) return\n console.error('customers.roles.roleTypes failed', error)\n setRoleTypes([])\n })\n\n return () => {\n active = false\n }\n }, [])\n\n const assignedRoleTypes = React.useMemo(\n () => new Set(roles.map((role) => role.roleType)),\n [roles],\n )\n\n const handleDialogAssign = React.useCallback(async (roleType: string, userId: string) => {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/customers/${basePath}/${entityId}/roles`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ roleType, userId }),\n }),\n { roleType, userId },\n )\n flash(t('customers.roles.assigned', 'Role assigned'), 'success')\n await loadRoles()\n }, [basePath, entityId, loadRoles, runMutationWithContext, t])\n\n const getRoleTypeLabel = React.useCallback(\n (value: string) => roleTypes.find((roleType) => roleType.value === value)?.label ?? value,\n [roleTypes],\n )\n\n const cards = React.useMemo(() => {\n const roleByType = new Map(roles.map((role) => [role.roleType, role]))\n const configuredCards = roleTypes.map((roleType) => ({\n roleType: roleType.value,\n roleTypeLabel: roleType.label,\n role: roleByType.get(roleType.value) ?? null,\n }))\n const configuredRoleTypes = new Set(roleTypes.map((roleType) => roleType.value))\n const extraAssignedCards = roles\n .filter((role) => !configuredRoleTypes.has(role.roleType))\n .map((role) => ({\n roleType: role.roleType,\n roleTypeLabel: getRoleTypeLabel(role.roleType),\n role,\n }))\n\n return [...configuredCards, ...extraAssignedCards]\n }, [getRoleTypeLabel, roleTypes, roles])\n\n const openDialog = React.useCallback((roleType?: string | null) => {\n if (!hasConfiguredRoleTypes) return\n setInitialRoleType(roleType ?? null)\n setDialogOpen(true)\n }, [hasConfiguredRoleTypes])\n\n if (loading) {\n return (\n <div className=\"py-2 text-sm text-muted-foreground\">\n {t('customers.roles.loading', 'Loading roles...')}\n </div>\n )\n }\n\n const resolvedEntityName =\n entityName && entityName.trim().length\n ? entityName.trim()\n : entityType === 'company'\n ? t('customers.roles.defaultEntityName.company', 'this company')\n : t('customers.roles.defaultEntityName.person', 'this person')\n const groupTitle =\n entityType === 'company'\n ? t('customers.roles.groupTitle.company', 'Roles at {{name}}', { name: resolvedEntityName })\n : t('customers.roles.groupTitle.person', 'My roles with {{name}}', { name: resolvedEntityName })\n\n return (\n <div className=\"space-y-4\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-semibold\">{groupTitle}</div>\n <p className=\"text-xs text-muted-foreground\">\n {entityType === 'company'\n ? t('customers.roles.subtitle.company', 'Who is responsible for this company on your side')\n : t('customers.roles.subtitle.person', 'Who owns the relationship on your side')}\n </p>\n </div>\n\n {cards.length > 0 ? (\n <div\n className=\"grid items-start gap-4\"\n style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 18rem), 1fr))' }}\n >\n {cards.map((entry) =>\n entry.role ? (\n <RoleAssignmentRow\n key={entry.role.id}\n role={entry.role}\n roleTypeLabel={entry.roleTypeLabel}\n runMutationWithContext={runMutationWithContext}\n entityType={entityType}\n entityId={entityId}\n onRemoved={loadRoles}\n onUpdated={loadRoles}\n />\n ) : (\n <div key={entry.roleType} className=\"flex h-full min-w-0 flex-col rounded-xl border border-dashed bg-muted/20 p-4\">\n <div className=\"break-words text-overline font-semibold uppercase tracking-[0.08em] text-muted-foreground\">\n {entry.roleTypeLabel}\n </div>\n <div className=\"mt-4 text-sm font-medium text-foreground\">\n {t('customers.roles.emptySlot', 'Not assigned')}\n </div>\n <p className=\"mt-2 text-xs text-muted-foreground\">\n {t('customers.roles.emptyState', 'No roles assigned yet. Click below to assign a person.')}\n </p>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"mt-4 self-start\"\n onClick={() => openDialog(entry.roleType)}\n >\n {t('customers.roles.choosePerson', 'Choose person')}\n </Button>\n </div>\n ),\n )}\n </div>\n ) : hasConfiguredRoleTypes ? (\n <div className=\"rounded-lg border border-dashed p-4 text-center\">\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.roles.emptyState', 'No roles assigned yet. Click below to assign a person.')}\n </p>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"mt-2\"\n onClick={() => openDialog(null)}\n >\n {t('customers.roles.choosePerson', 'Choose person')}\n </Button>\n </div>\n ) : (\n <div className=\"rounded-lg border border-dashed p-4 text-center\">\n <div className=\"text-sm font-medium text-foreground\">\n {t('customers.roles.noRoleTypesTitle', 'No role types configured')}\n </div>\n <p className=\"mt-2 text-xs text-muted-foreground\">\n {t(\n 'customers.roles.noRoleTypesDescription',\n 'Create role types in Customers config before assigning owners here.',\n )}\n </p>\n <Button asChild type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-3\">\n <Link href=\"/backend/config/customers\">\n {t('customers.roles.configureRoleTypes', 'Configure role types')}\n </Link>\n </Button>\n </div>\n )}\n\n {hasConfiguredRoleTypes ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openDialog(null)}\n >\n <Plus className=\"mr-1 size-3.5\" />\n {t('customers.roles.addRole', 'Add role')}\n </Button>\n ) : (\n <Button asChild type=\"button\" variant=\"ghost\" size=\"sm\">\n <Link href=\"/backend/config/customers\">\n {t('customers.roles.configureRoleTypes', 'Configure role types')}\n </Link>\n </Button>\n )}\n\n <AssignRoleDialog\n open={dialogOpen}\n onClose={() => {\n setDialogOpen(false)\n setInitialRoleType(null)\n }}\n onAssign={handleDialogAssign}\n roleTypes={roleTypes}\n entityName={\n entityName && entityName.trim().length\n ? entityName\n : entityType === 'company'\n ? t('customers.roles.dialog.defaultEntity.company', 'this company')\n : t('customers.roles.dialog.defaultEntity.person', 'this person')\n }\n existingRoleTypes={assignedRoleTypes}\n existingAssignments={roles}\n initialRoleType={initialRoleType}\n canManageRoleTypes={canManageRoleTypes}\n />\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Plus, Settings, UserCheck } from 'lucide-react'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport { RoleAssignmentRow, type RoleAssignment } from './RoleAssignmentRow'\nimport { AssignRoleDialog } from './AssignRoleDialog'\n\ninterface RolesSectionProps {\n entityType: 'company' | 'person'\n entityId: string\n entityName?: string | null\n}\n\ntype GuardedMutationRunner = <T,>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\nexport function RolesSection({ entityType, entityId, entityName }: RolesSectionProps) {\n const t = useT()\n const { payload } = useBackendChrome()\n const canManageRoleTypes = React.useMemo(\n () => hasFeature(payload?.grantedFeatures ?? [], 'customers.settings.manage'),\n [payload?.grantedFeatures],\n )\n const [roles, setRoles] = React.useState<RoleAssignment[]>([])\n const [roleTypes, setRoleTypes] = React.useState<DictionaryEntryOption[]>([])\n const [loading, setLoading] = React.useState(true)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [initialRoleType, setInitialRoleType] = React.useState<string | null>(null)\n const hasConfiguredRoleTypes = roleTypes.length > 0\n\n const basePath = entityType === 'company' ? 'companies' : 'people'\n const resourceKind = entityType === 'company' ? 'customers.company' : 'customers.person'\n const mutationContextId = React.useMemo(\n () => `customer-roles:${entityType}:${entityId}`,\n [entityId, entityType],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n entityType: 'company' | 'person'\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: mutationContextId,\n resourceKind,\n resourceId: entityId,\n entityType,\n retryLastMutation,\n }),\n [entityId, entityType, mutationContextId, resourceKind, retryLastMutation],\n )\n const runMutationWithContext = React.useCallback<GuardedMutationRunner>(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>) => {\n return runMutation({\n operation,\n mutationPayload,\n context: mutationContext,\n })\n },\n [mutationContext, runMutation],\n )\n\n const loadRoles = React.useCallback(async () => {\n try {\n const data = await readApiResultOrThrow<{ items?: RoleAssignment[] }>(\n `/api/customers/${basePath}/${entityId}/roles`,\n )\n setRoles(Array.isArray(data?.items) ? data.items : [])\n } catch (error) {\n console.error('customers.roles.load failed', error)\n flash(t('customers.roles.loadFailed', 'Failed to load role assignments.'), 'error')\n setRoles([])\n }\n setLoading(false)\n }, [basePath, entityId, t])\n\n React.useEffect(() => {\n loadRoles()\n }, [loadRoles])\n\n React.useEffect(() => {\n let active = true\n\n readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n '/api/customers/dictionaries/person-company-roles',\n )\n .then((payload) => {\n if (!active) return\n const entries = (Array.isArray(payload?.items) ? payload.items : [])\n .map((item) => {\n const id = typeof item.id === 'string' ? item.id : null\n const value = typeof item.value === 'string' ? item.value.trim() : ''\n if (!id || value.length === 0) return null\n return {\n id,\n value,\n label:\n typeof item.label === 'string' && item.label.trim().length > 0\n ? item.label.trim()\n : value,\n color: typeof item.color === 'string' ? item.color : null,\n icon: typeof item.icon === 'string' ? item.icon : null,\n } satisfies DictionaryEntryOption\n })\n .filter((entry): entry is DictionaryEntryOption => entry !== null)\n\n setRoleTypes(entries)\n })\n .catch((error) => {\n if (!active) return\n console.error('customers.roles.roleTypes failed', error)\n setRoleTypes([])\n })\n\n return () => {\n active = false\n }\n }, [])\n\n const assignedRoleTypes = React.useMemo(\n () => new Set(roles.map((role) => role.roleType)),\n [roles],\n )\n\n const handleDialogAssign = React.useCallback(async (roleType: string, userId: string) => {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/customers/${basePath}/${entityId}/roles`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ roleType, userId }),\n }),\n { roleType, userId },\n )\n flash(t('customers.roles.assigned', 'Role assigned'), 'success')\n await loadRoles()\n }, [basePath, entityId, loadRoles, runMutationWithContext, t])\n\n const getRoleTypeLabel = React.useCallback(\n (value: string) => roleTypes.find((roleType) => roleType.value === value)?.label ?? value,\n [roleTypes],\n )\n\n const cards = React.useMemo(() => {\n const roleByType = new Map(roles.map((role) => [role.roleType, role]))\n const configuredCards = roleTypes.map((roleType) => ({\n roleType: roleType.value,\n roleTypeLabel: roleType.label,\n role: roleByType.get(roleType.value) ?? null,\n }))\n const configuredRoleTypes = new Set(roleTypes.map((roleType) => roleType.value))\n const extraAssignedCards = roles\n .filter((role) => !configuredRoleTypes.has(role.roleType))\n .map((role) => ({\n roleType: role.roleType,\n roleTypeLabel: getRoleTypeLabel(role.roleType),\n role,\n }))\n\n return [...configuredCards, ...extraAssignedCards]\n }, [getRoleTypeLabel, roleTypes, roles])\n\n const openDialog = React.useCallback((roleType?: string | null) => {\n if (!hasConfiguredRoleTypes) return\n setInitialRoleType(roleType ?? null)\n setDialogOpen(true)\n }, [hasConfiguredRoleTypes])\n\n if (loading) {\n return (\n <div className=\"py-2 text-sm text-muted-foreground\">\n {t('customers.roles.loading', 'Loading roles...')}\n </div>\n )\n }\n\n const resolvedEntityName =\n entityName && entityName.trim().length\n ? entityName.trim()\n : entityType === 'company'\n ? t('customers.roles.defaultEntityName.company', 'this company')\n : t('customers.roles.defaultEntityName.person', 'this person')\n const groupTitle =\n entityType === 'company'\n ? t('customers.roles.groupTitle.company', 'Roles at {{name}}', { name: resolvedEntityName })\n : t('customers.roles.groupTitle.person', 'My roles with {{name}}', { name: resolvedEntityName })\n\n return (\n <div className=\"space-y-4\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-semibold\">{groupTitle}</div>\n <p className=\"text-xs text-muted-foreground\">\n {entityType === 'company'\n ? t('customers.roles.subtitle.company', 'Who is responsible for this company on your side')\n : t('customers.roles.subtitle.person', 'Who owns the relationship on your side')}\n </p>\n </div>\n\n {cards.length > 0 ? (\n <div\n className=\"grid items-start gap-4\"\n style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 18rem), 1fr))' }}\n >\n {cards.map((entry) =>\n entry.role ? (\n <RoleAssignmentRow\n key={entry.role.id}\n role={entry.role}\n roleTypeLabel={entry.roleTypeLabel}\n runMutationWithContext={runMutationWithContext}\n entityType={entityType}\n entityId={entityId}\n onRemoved={loadRoles}\n onUpdated={loadRoles}\n />\n ) : (\n <div key={entry.roleType} className=\"flex h-full min-w-0 flex-col rounded-xl border border-dashed bg-muted/20 p-4\">\n <div className=\"break-words text-overline font-semibold uppercase tracking-[0.08em] text-muted-foreground\">\n {entry.roleTypeLabel}\n </div>\n <div className=\"mt-4 text-sm font-medium text-foreground\">\n {t('customers.roles.emptySlot', 'Not assigned')}\n </div>\n <p className=\"mt-2 text-xs text-muted-foreground\">\n {t('customers.roles.emptyState', 'No roles assigned yet. Click below to assign a person.')}\n </p>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"mt-4 self-start\"\n onClick={() => openDialog(entry.roleType)}\n >\n {t('customers.roles.choosePerson', 'Choose person')}\n </Button>\n </div>\n ),\n )}\n </div>\n ) : hasConfiguredRoleTypes ? (\n <EmptyState\n size=\"sm\"\n icon={<UserCheck className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t('customers.roles.emptyState', 'No roles assigned yet. Click below to assign a person.')}\n actions={\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => openDialog(null)}\n >\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {t('customers.roles.choosePerson', 'Choose person')}\n </Button>\n }\n />\n ) : (\n <EmptyState\n size=\"sm\"\n icon={<Settings className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t('customers.roles.noRoleTypesTitle', 'No role types configured')}\n description={t(\n 'customers.roles.noRoleTypesDescription',\n 'Create role types in Customers config before assigning owners here.',\n )}\n actions={\n <Button asChild type=\"button\" variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/config/customers\">\n {t('customers.roles.configureRoleTypes', 'Configure role types')}\n </Link>\n </Button>\n }\n />\n )}\n\n {hasConfiguredRoleTypes ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openDialog(null)}\n >\n <Plus className=\"mr-1 size-3.5\" />\n {t('customers.roles.addRole', 'Add role')}\n </Button>\n ) : (\n <Button asChild type=\"button\" variant=\"ghost\" size=\"sm\">\n <Link href=\"/backend/config/customers\">\n {t('customers.roles.configureRoleTypes', 'Configure role types')}\n </Link>\n </Button>\n )}\n\n <AssignRoleDialog\n open={dialogOpen}\n onClose={() => {\n setDialogOpen(false)\n setInitialRoleType(null)\n }}\n onAssign={handleDialogAssign}\n roleTypes={roleTypes}\n entityName={\n entityName && entityName.trim().length\n ? entityName\n : entityType === 'company'\n ? t('customers.roles.dialog.defaultEntity.company', 'this company')\n : t('customers.roles.dialog.defaultEntity.person', 'this person')\n }\n existingRoleTypes={assignedRoleTypes}\n existingAssignments={roles}\n initialRoleType={initialRoleType}\n canManageRoleTypes={canManageRoleTypes}\n />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["payload"]
|
|
7
7
|
}
|
|
@@ -361,7 +361,7 @@ function TasksSection({
|
|
|
361
361
|
className: "border-0 bg-transparent p-0 py-8 justify-center"
|
|
362
362
|
}
|
|
363
363
|
) : null,
|
|
364
|
-
!isInitialLoading && !hasTasks ? /* @__PURE__ */ jsx(TabEmptyState, { title: emptyState.title }) : null,
|
|
364
|
+
!isInitialLoading && !hasTasks ? /* @__PURE__ */ jsx(TabEmptyState, { title: emptyState.title, children: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", className: "mt-2", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customer-tasks", children: t("customers.people.detail.tasks.viewAll", "View all tasks") }) }) }) : null,
|
|
365
365
|
!isInitialLoading && hasTasks ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
366
366
|
error ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: error }) : null,
|
|
367
367
|
sortedTasks.map((task) => {
|
|
@@ -466,7 +466,7 @@ function TasksSection({
|
|
|
466
466
|
t("customers.people.detail.tasks.loadingMore", "Loading\u2026")
|
|
467
467
|
] }) : null
|
|
468
468
|
] }) : null,
|
|
469
|
-
/* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customer-tasks", children: t("customers.people.detail.tasks.viewAll", "View all tasks") }) }) })
|
|
469
|
+
!isInitialLoading && hasTasks ? /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customer-tasks", children: t("customers.people.detail.tasks.viewAll", "View all tasks") }) }) }) : null
|
|
470
470
|
] }),
|
|
471
471
|
/* @__PURE__ */ jsx(
|
|
472
472
|
TaskDialog,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/TasksSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Loader2, Pencil, Trash2 } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InteractionSummary, SectionAction, TabEmptyStateConfig, TodoLinkSummary, Translator } from './types'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { formatDate, resolveTodoHref } from './utils'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport { TimelineItemHeader } from './TimelineItemHeader'\nimport { TaskDialog } from './TaskDialog'\nimport { usePersonTasks, type TaskFormPayload } from './hooks/usePersonTasks'\nimport { useInteractions, type InteractionCreatePayload } from './hooks/useInteractions'\nimport { mapInteractionRecordToTodoSummary } from '../../lib/interactionCompatibility'\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\ntype TasksSectionProps = {\n entityId: string | null\n initialTasks: TodoLinkSummary[]\n emptyLabel: string\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n translator?: Translator\n entityName?: string | null\n dialogContextKey?: string\n dialogContextFallback?: string\n /** When true, use the canonical interactions API instead of the legacy todos API. */\n useCanonicalInteractions?: boolean\n runGuardedMutation?: GuardedMutationRunner\n}\n\nconst RESERVED_TASK_CUSTOM_KEYS = new Set(['priority', 'description', 'due_at', 'dueAt'])\n\nfunction toTimestamp(value: string | null | undefined): number | null {\n if (!value) return null\n const timestamp = new Date(value).getTime()\n return Number.isNaN(timestamp) ? null : timestamp\n}\n\nfunction sortTaskSummaries(tasks: TodoLinkSummary[]): TodoLinkSummary[] {\n return [...tasks].sort((left, right) => {\n const leftDue = toTimestamp(left.dueAt)\n const rightDue = toTimestamp(right.dueAt)\n if (leftDue !== null || rightDue !== null) {\n if (leftDue === null) return 1\n if (rightDue === null) return -1\n if (leftDue !== rightDue) return leftDue - rightDue\n }\n const leftCreated = toTimestamp(left.createdAt) ?? 0\n const rightCreated = toTimestamp(right.createdAt) ?? 0\n if (leftCreated !== rightCreated) return rightCreated - leftCreated\n return left.id.localeCompare(right.id)\n })\n}\n\nfunction buildInitialFormValues(task: TodoLinkSummary | null): Record<string, unknown> | undefined {\n if (!task) return undefined\n const values: Record<string, unknown> = {\n title: task.title ?? '',\n is_done: task.isDone ?? false,\n description: task.description ?? '',\n priority: task.priority ?? '',\n scheduledAt: task.dueAt ?? '',\n }\n if (task.customValues) {\n for (const [key, value] of Object.entries(task.customValues)) {\n if (RESERVED_TASK_CUSTOM_KEYS.has(key)) continue\n const formKey = `cf_${key}`\n if (values[formKey] === undefined) values[formKey] = value\n }\n }\n return values\n}\n\nexport function TasksSection({\n entityId,\n initialTasks,\n emptyLabel,\n addActionLabel,\n emptyState,\n onActionChange,\n onLoadingChange,\n onDataRefresh,\n translator,\n entityName,\n dialogContextKey,\n dialogContextFallback,\n useCanonicalInteractions = false,\n runGuardedMutation,\n}: TasksSectionProps) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])\n const t: Translator = React.useMemo(() => translator ?? fallbackTranslator, [translator, fallbackTranslator])\n const runWriteMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n if (!runGuardedMutation) {\n return operation()\n }\n return runGuardedMutation(operation, mutationPayload)\n },\n [runGuardedMutation],\n )\n\n // Legacy path: usePersonTasks (default)\n const legacyResult = usePersonTasks({ entityId, initialTasks })\n\n // Canonical path: useInteractions with planned-status filter\n const canonicalResult = useInteractions({\n entityId: useCanonicalInteractions ? entityId : null,\n typeFilter: 'task',\n })\n\n // Map canonical interactions to the TodoLinkSummary shape used by the rendering below\n const canonicalTasks = React.useMemo<TodoLinkSummary[]>(\n () => (useCanonicalInteractions ? canonicalResult.interactions.map(mapInteractionRecordToTodoSummary) : []),\n [useCanonicalInteractions, canonicalResult.interactions],\n )\n\n const canonicalCreateTask = React.useCallback(\n async (payload: TaskFormPayload) => {\n if (!entityId) throw new Error('Task creation requires an entity id')\n const interactionPayload: InteractionCreatePayload = {\n entityId,\n interactionType: 'task',\n title: payload.base.title,\n status: payload.base.is_done ? 'done' : 'planned',\n priority: payload.base.priority ?? null,\n body: payload.base.description ?? null,\n scheduledAt: payload.base.scheduledAt ?? null,\n customValues: payload.custom,\n }\n await canonicalResult.createInteraction(interactionPayload)\n },\n [canonicalResult, entityId],\n )\n\n const canonicalUpdateTask = React.useCallback(\n async (task: TodoLinkSummary, payload: TaskFormPayload) => {\n await canonicalResult.updateInteraction(task.todoId, {\n title: payload.base.title,\n status: payload.base.is_done ? 'done' : 'planned',\n priority: payload.base.priority ?? null,\n body: payload.base.description ?? null,\n scheduledAt: payload.base.scheduledAt ?? null,\n customValues: payload.custom,\n })\n },\n [canonicalResult],\n )\n\n const canonicalToggleTask = React.useCallback(\n async (task: TodoLinkSummary, nextIsDone: boolean) => {\n if (nextIsDone) {\n await canonicalResult.completeInteraction(task.todoId)\n } else {\n // Reopen: set status back to planned via update\n await canonicalResult.updateInteraction(task.todoId, { status: 'planned' })\n }\n },\n [canonicalResult],\n )\n\n const canonicalUnlinkTask = React.useCallback(\n async (task: TodoLinkSummary) => {\n await canonicalResult.deleteInteraction(task.todoId)\n },\n [canonicalResult],\n )\n\n // Unified interface: pick the active data source based on the flag\n const tasks = useCanonicalInteractions ? canonicalTasks : legacyResult.tasks\n const isInitialLoading = useCanonicalInteractions ? canonicalResult.isInitialLoading : legacyResult.isInitialLoading\n const isLoadingMore = useCanonicalInteractions ? canonicalResult.isLoadingMore : legacyResult.isLoadingMore\n const isMutating = useCanonicalInteractions ? canonicalResult.isMutating : legacyResult.isMutating\n const hasMore = useCanonicalInteractions ? canonicalResult.hasMore : legacyResult.hasMore\n const loadMore = useCanonicalInteractions ? canonicalResult.loadMore : legacyResult.loadMore\n const refresh = useCanonicalInteractions ? canonicalResult.refresh : legacyResult.refresh\n const createTask = useCanonicalInteractions ? canonicalCreateTask : legacyResult.createTask\n const updateTask = useCanonicalInteractions ? canonicalUpdateTask : legacyResult.updateTask\n const toggleTask = useCanonicalInteractions ? canonicalToggleTask : legacyResult.toggleTask\n const unlinkTask = useCanonicalInteractions ? canonicalUnlinkTask : legacyResult.unlinkTask\n const pendingTaskId = useCanonicalInteractions ? canonicalResult.pendingId : legacyResult.pendingTaskId\n const error = useCanonicalInteractions ? canonicalResult.error : legacyResult.error\n const sortedTasks = React.useMemo(() => sortTaskSummaries(tasks), [tasks])\n\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [dialogMode, setDialogMode] = React.useState<'create' | 'edit'>('create')\n const [editingTask, setEditingTask] = React.useState<TodoLinkSummary | null>(null)\n const sentinelRef = React.useRef<HTMLDivElement | null>(null)\n\n const dialogContextMessage = React.useMemo(() => {\n if (!dialogContextKey || !entityName) return undefined\n return t(dialogContextKey, dialogContextFallback ?? 'This task will be linked to {{name}}', { name: entityName })\n }, [dialogContextFallback, dialogContextKey, entityName, t])\n\n const openCreateDialog = React.useCallback(() => {\n setEditingTask(null)\n setDialogMode('create')\n setDialogOpen(true)\n }, [])\n\n const openEditDialog = React.useCallback((task: TodoLinkSummary) => {\n setEditingTask(task)\n setDialogMode('edit')\n setDialogOpen(true)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n setEditingTask(null)\n }, [])\n\n React.useEffect(() => {\n if (!onActionChange) return\n if (!entityId) {\n onActionChange(null)\n return\n }\n onActionChange({\n label: addActionLabel,\n onClick: openCreateDialog,\n disabled: isMutating,\n })\n return () => {\n onActionChange(null)\n }\n }, [addActionLabel, entityId, isMutating, onActionChange, openCreateDialog])\n\n React.useEffect(() => {\n if (!onLoadingChange) return\n onLoadingChange(isInitialLoading || isMutating)\n }, [isInitialLoading, isMutating, onLoadingChange])\n\n React.useEffect(() => {\n if (!hasMore) return\n if (typeof IntersectionObserver === 'undefined') return\n const el = sentinelRef.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n loadMore().catch(() => {})\n }\n },\n { rootMargin: '200px 0px 200px 0px' },\n )\n observer.observe(el)\n return () => {\n observer.disconnect()\n }\n }, [hasMore, loadMore])\n\n const handleCreate = React.useCallback(\n async (payload: TaskFormPayload) => {\n try {\n await runWriteMutation(\n () => createTask(payload),\n {\n entityId,\n title: payload.base.title,\n isDone: payload.base.is_done ?? undefined,\n },\n )\n flash(t('customers.people.detail.tasks.createSuccess', 'Task created'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.tasks.error', 'Failed to create task')\n flash(message, 'error')\n throw err\n }\n },\n [createTask, entityId, onDataRefresh, runWriteMutation, t],\n )\n\n const handleUpdate = React.useCallback(\n async (task: TodoLinkSummary, payload: TaskFormPayload) => {\n try {\n await runWriteMutation(\n () => updateTask(task, payload),\n {\n id: task.id,\n todoId: task.todoId,\n title: payload.base.title,\n isDone: payload.base.is_done ?? undefined,\n },\n )\n flash(t('customers.people.detail.tasks.updateSuccess', 'Task updated'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.updateError', 'Failed to update task')\n flash(message, 'error')\n throw err\n }\n },\n [onDataRefresh, runWriteMutation, t, updateTask],\n )\n\n const handleToggle = React.useCallback(\n async (task: TodoLinkSummary, nextIsDone: boolean) => {\n try {\n await runWriteMutation(\n () => toggleTask(task, nextIsDone),\n {\n id: task.id,\n todoId: task.todoId,\n isDone: nextIsDone,\n },\n )\n flash(\n nextIsDone\n ? t('customers.people.detail.tasks.completeSuccess', 'Task marked as done')\n : t('customers.people.detail.tasks.reopenSuccess', 'Task reopened'),\n 'success',\n )\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.toggleError', 'Failed to update task status')\n flash(message, 'error')\n }\n },\n [onDataRefresh, runWriteMutation, t, toggleTask],\n )\n\n const handleDelete = React.useCallback(\n async (task: TodoLinkSummary) => {\n try {\n await runWriteMutation(\n () => unlinkTask(task),\n {\n id: task.id,\n todoId: task.todoId,\n },\n )\n flash(t('customers.people.detail.tasks.deleteSuccess', 'Task removed'), 'success')\n await Promise.resolve(onDataRefresh?.())\n await refresh()\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.deleteError', 'Failed to remove task')\n flash(message, 'error')\n }\n },\n [onDataRefresh, refresh, runWriteMutation, t, unlinkTask],\n )\n\n const handleCancel = React.useCallback(\n async (task: TodoLinkSummary) => {\n if (!useCanonicalInteractions) return\n try {\n await runWriteMutation(\n () => canonicalResult.cancelInteraction(task.todoId),\n { id: task.todoId },\n )\n flash(t('customers.people.detail.tasks.cancelSuccess', 'Task canceled'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.cancelError', 'Failed to cancel task')\n flash(message, 'error')\n }\n },\n [canonicalResult, onDataRefresh, runWriteMutation, t, useCanonicalInteractions],\n )\n\n const renderTaskMeta = React.useCallback(\n (task: TodoLinkSummary) => {\n const meta: string[] = []\n if (task.status === 'canceled') {\n meta.push(t('customers.people.detail.tasks.status.canceled', 'Canceled'))\n }\n if (typeof task.priority === 'number') {\n meta.push(t('customers.people.detail.tasks.priorityLabel', 'Priority {{priority}}', { priority: task.priority }))\n }\n if (task.severity) {\n meta.push(\n t(\n `customers.people.detail.tasks.severity.${task.severity}`,\n task.severity.charAt(0).toUpperCase() + task.severity.slice(1),\n ),\n )\n }\n if (task.dueAt) {\n const dueLabel =\n formatDate(task.dueAt) ??\n formatDateTime(task.dueAt) ??\n t('customers.people.detail.tasks.dueLabel', 'Due {{date}}', { date: task.dueAt })\n meta.push(t('customers.people.detail.tasks.dueLabel', 'Due {{date}}', { date: dueLabel }))\n }\n return meta\n },\n [t],\n )\n\n const handleDialogSubmit = React.useCallback(\n async (payload: TaskFormPayload) => {\n if (dialogMode === 'edit' && editingTask) {\n await handleUpdate(editingTask, payload)\n } else {\n await handleCreate(payload)\n }\n },\n [dialogMode, editingTask, handleCreate, handleUpdate],\n )\n\n const hasTasks = sortedTasks.length > 0\n\n return (\n <div className=\"mt-0 space-y-6\">\n <div className=\"space-y-4\">\n {isInitialLoading ? (\n <LoadingMessage\n label={t('customers.people.detail.tasks.loading', 'Loading tasks\u2026')}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n ) : null}\n\n {!isInitialLoading && !hasTasks ? (\n <TabEmptyState title={emptyState.title} />\n ) : null}\n\n {!isInitialLoading && hasTasks ? (\n <div className=\"space-y-4\">\n {error ? (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n {sortedTasks.map((task) => {\n const todoHref = task.externalHref ?? resolveTodoHref(task.todoSource, task.todoId)\n const createdLabel = formatDateTime(task.createdAt) ?? emptyLabel\n const meta = renderTaskMeta(task)\n const title = task.title ?? t('customers.people.detail.tasks.untitled', 'Untitled task')\n const isDone = task.isDone === true\n const isCanceled = task.status === 'canceled'\n const checkboxId = `person-task-${task.id}`\n const isPendingToggle = pendingTaskId === task.todoId\n return (\n <article key={task.id} className=\"group space-y-3 rounded-lg border bg-card p-4 transition hover:border-border/70\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <TimelineItemHeader\n title={\n <span className=\"inline-flex items-center gap-2\">\n <input\n id={checkboxId}\n type=\"checkbox\"\n checked={isDone}\n onChange={(event) => {\n const next = event.target.checked\n void handleToggle(task, next)\n }}\n disabled={isMutating || isPendingToggle || isCanceled}\n className=\"h-4 w-4 rounded border\"\n />\n <span\n className={cn(\n 'text-sm font-semibold',\n isDone ? 'line-through text-muted-foreground' : undefined,\n isCanceled ? 'text-muted-foreground' : undefined,\n )}\n >\n {title}\n </span>\n </span>\n }\n timestamp={task.createdAt}\n fallbackTimestampLabel={createdLabel}\n />\n <div className=\"flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openEditDialog(task)}\n disabled={isMutating}\n aria-label={t('ui.actions.edit', 'Edit')}\n >\n {isMutating && editingTask?.id === task.id && dialogMode === 'edit' ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Pencil className=\"h-4 w-4\" />\n )}\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => handleDelete(task)}\n disabled={isMutating}\n aria-label={t('ui.actions.delete', 'Delete')}\n >\n {isMutating ? <Loader2 className=\"h-4 w-4 animate-spin text-destructive\" /> : <Trash2 className=\"h-4 w-4\" />}\n </IconButton>\n </div>\n </div>\n {meta.length ? (\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {meta.map((entry) => (\n <span key={`${task.id}-${entry}`} className=\"rounded bg-muted px-2 py-1 text-xs font-medium text-muted-foreground\">\n {entry}\n </span>\n ))}\n </div>\n ) : null}\n {task.description ? (\n <p className=\"text-sm text-muted-foreground whitespace-pre-wrap\">{task.description}</p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-3 text-xs\">\n {useCanonicalInteractions && !isDone && !isCanceled ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 text-xs font-medium text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={() => void handleCancel(task)}\n disabled={isMutating || isPendingToggle}\n >\n {t('customers.people.detail.tasks.cancelAction', 'Cancel task')}\n </Button>\n ) : null}\n {todoHref ? (\n <Link href={todoHref} className=\"text-primary hover:underline\">\n {t('customers.people.detail.tasks.openTask', 'Open task')}\n </Link>\n ) : null}\n </div>\n </article>\n )\n })}\n <div ref={sentinelRef} />\n {hasMore ? (\n <div className=\"flex justify-center\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => loadMore().catch(() => {})} disabled={isLoadingMore}>\n {isLoadingMore ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {t('customers.people.detail.tasks.loadingMore', 'Loading\u2026')}\n </>\n ) : (\n t('customers.people.detail.tasks.loadMore', 'Load more')\n )}\n </Button>\n </div>\n ) : null}\n {isLoadingMore ? (\n <div className=\"flex justify-center text-xs text-muted-foreground\">\n <Loader2 className=\"mr-2 h-3 w-3 animate-spin\" />\n {t('customers.people.detail.tasks.loadingMore', 'Loading\u2026')}\n </div>\n ) : null}\n </div>\n ) : null}\n <div className=\"flex justify-center\">\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customer-tasks\">\n {t('customers.people.detail.tasks.viewAll', 'View all tasks')}\n </Link>\n </Button>\n </div>\n </div>\n\n <TaskDialog\n open={dialogOpen}\n mode={dialogMode}\n onOpenChange={(next) => {\n if (!next) closeDialog()\n else setDialogOpen(true)\n }}\n initialValues={buildInitialFormValues(editingTask)}\n onSubmit={handleDialogSubmit}\n isSubmitting={isMutating}\n contextMessage={dialogContextMessage}\n />\n </div>\n )\n}\n\nexport default TasksSection\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Loader2, Pencil, Trash2 } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InteractionSummary, SectionAction, TabEmptyStateConfig, TodoLinkSummary, Translator } from './types'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { formatDate, resolveTodoHref } from './utils'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport { TimelineItemHeader } from './TimelineItemHeader'\nimport { TaskDialog } from './TaskDialog'\nimport { usePersonTasks, type TaskFormPayload } from './hooks/usePersonTasks'\nimport { useInteractions, type InteractionCreatePayload } from './hooks/useInteractions'\nimport { mapInteractionRecordToTodoSummary } from '../../lib/interactionCompatibility'\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\ntype TasksSectionProps = {\n entityId: string | null\n initialTasks: TodoLinkSummary[]\n emptyLabel: string\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n translator?: Translator\n entityName?: string | null\n dialogContextKey?: string\n dialogContextFallback?: string\n /** When true, use the canonical interactions API instead of the legacy todos API. */\n useCanonicalInteractions?: boolean\n runGuardedMutation?: GuardedMutationRunner\n}\n\nconst RESERVED_TASK_CUSTOM_KEYS = new Set(['priority', 'description', 'due_at', 'dueAt'])\n\nfunction toTimestamp(value: string | null | undefined): number | null {\n if (!value) return null\n const timestamp = new Date(value).getTime()\n return Number.isNaN(timestamp) ? null : timestamp\n}\n\nfunction sortTaskSummaries(tasks: TodoLinkSummary[]): TodoLinkSummary[] {\n return [...tasks].sort((left, right) => {\n const leftDue = toTimestamp(left.dueAt)\n const rightDue = toTimestamp(right.dueAt)\n if (leftDue !== null || rightDue !== null) {\n if (leftDue === null) return 1\n if (rightDue === null) return -1\n if (leftDue !== rightDue) return leftDue - rightDue\n }\n const leftCreated = toTimestamp(left.createdAt) ?? 0\n const rightCreated = toTimestamp(right.createdAt) ?? 0\n if (leftCreated !== rightCreated) return rightCreated - leftCreated\n return left.id.localeCompare(right.id)\n })\n}\n\nfunction buildInitialFormValues(task: TodoLinkSummary | null): Record<string, unknown> | undefined {\n if (!task) return undefined\n const values: Record<string, unknown> = {\n title: task.title ?? '',\n is_done: task.isDone ?? false,\n description: task.description ?? '',\n priority: task.priority ?? '',\n scheduledAt: task.dueAt ?? '',\n }\n if (task.customValues) {\n for (const [key, value] of Object.entries(task.customValues)) {\n if (RESERVED_TASK_CUSTOM_KEYS.has(key)) continue\n const formKey = `cf_${key}`\n if (values[formKey] === undefined) values[formKey] = value\n }\n }\n return values\n}\n\nexport function TasksSection({\n entityId,\n initialTasks,\n emptyLabel,\n addActionLabel,\n emptyState,\n onActionChange,\n onLoadingChange,\n onDataRefresh,\n translator,\n entityName,\n dialogContextKey,\n dialogContextFallback,\n useCanonicalInteractions = false,\n runGuardedMutation,\n}: TasksSectionProps) {\n const tHook = useT()\n const fallbackTranslator = React.useMemo<Translator>(() => createTranslatorWithFallback(tHook), [tHook])\n const t: Translator = React.useMemo(() => translator ?? fallbackTranslator, [translator, fallbackTranslator])\n const runWriteMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n if (!runGuardedMutation) {\n return operation()\n }\n return runGuardedMutation(operation, mutationPayload)\n },\n [runGuardedMutation],\n )\n\n // Legacy path: usePersonTasks (default)\n const legacyResult = usePersonTasks({ entityId, initialTasks })\n\n // Canonical path: useInteractions with planned-status filter\n const canonicalResult = useInteractions({\n entityId: useCanonicalInteractions ? entityId : null,\n typeFilter: 'task',\n })\n\n // Map canonical interactions to the TodoLinkSummary shape used by the rendering below\n const canonicalTasks = React.useMemo<TodoLinkSummary[]>(\n () => (useCanonicalInteractions ? canonicalResult.interactions.map(mapInteractionRecordToTodoSummary) : []),\n [useCanonicalInteractions, canonicalResult.interactions],\n )\n\n const canonicalCreateTask = React.useCallback(\n async (payload: TaskFormPayload) => {\n if (!entityId) throw new Error('Task creation requires an entity id')\n const interactionPayload: InteractionCreatePayload = {\n entityId,\n interactionType: 'task',\n title: payload.base.title,\n status: payload.base.is_done ? 'done' : 'planned',\n priority: payload.base.priority ?? null,\n body: payload.base.description ?? null,\n scheduledAt: payload.base.scheduledAt ?? null,\n customValues: payload.custom,\n }\n await canonicalResult.createInteraction(interactionPayload)\n },\n [canonicalResult, entityId],\n )\n\n const canonicalUpdateTask = React.useCallback(\n async (task: TodoLinkSummary, payload: TaskFormPayload) => {\n await canonicalResult.updateInteraction(task.todoId, {\n title: payload.base.title,\n status: payload.base.is_done ? 'done' : 'planned',\n priority: payload.base.priority ?? null,\n body: payload.base.description ?? null,\n scheduledAt: payload.base.scheduledAt ?? null,\n customValues: payload.custom,\n })\n },\n [canonicalResult],\n )\n\n const canonicalToggleTask = React.useCallback(\n async (task: TodoLinkSummary, nextIsDone: boolean) => {\n if (nextIsDone) {\n await canonicalResult.completeInteraction(task.todoId)\n } else {\n // Reopen: set status back to planned via update\n await canonicalResult.updateInteraction(task.todoId, { status: 'planned' })\n }\n },\n [canonicalResult],\n )\n\n const canonicalUnlinkTask = React.useCallback(\n async (task: TodoLinkSummary) => {\n await canonicalResult.deleteInteraction(task.todoId)\n },\n [canonicalResult],\n )\n\n // Unified interface: pick the active data source based on the flag\n const tasks = useCanonicalInteractions ? canonicalTasks : legacyResult.tasks\n const isInitialLoading = useCanonicalInteractions ? canonicalResult.isInitialLoading : legacyResult.isInitialLoading\n const isLoadingMore = useCanonicalInteractions ? canonicalResult.isLoadingMore : legacyResult.isLoadingMore\n const isMutating = useCanonicalInteractions ? canonicalResult.isMutating : legacyResult.isMutating\n const hasMore = useCanonicalInteractions ? canonicalResult.hasMore : legacyResult.hasMore\n const loadMore = useCanonicalInteractions ? canonicalResult.loadMore : legacyResult.loadMore\n const refresh = useCanonicalInteractions ? canonicalResult.refresh : legacyResult.refresh\n const createTask = useCanonicalInteractions ? canonicalCreateTask : legacyResult.createTask\n const updateTask = useCanonicalInteractions ? canonicalUpdateTask : legacyResult.updateTask\n const toggleTask = useCanonicalInteractions ? canonicalToggleTask : legacyResult.toggleTask\n const unlinkTask = useCanonicalInteractions ? canonicalUnlinkTask : legacyResult.unlinkTask\n const pendingTaskId = useCanonicalInteractions ? canonicalResult.pendingId : legacyResult.pendingTaskId\n const error = useCanonicalInteractions ? canonicalResult.error : legacyResult.error\n const sortedTasks = React.useMemo(() => sortTaskSummaries(tasks), [tasks])\n\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [dialogMode, setDialogMode] = React.useState<'create' | 'edit'>('create')\n const [editingTask, setEditingTask] = React.useState<TodoLinkSummary | null>(null)\n const sentinelRef = React.useRef<HTMLDivElement | null>(null)\n\n const dialogContextMessage = React.useMemo(() => {\n if (!dialogContextKey || !entityName) return undefined\n return t(dialogContextKey, dialogContextFallback ?? 'This task will be linked to {{name}}', { name: entityName })\n }, [dialogContextFallback, dialogContextKey, entityName, t])\n\n const openCreateDialog = React.useCallback(() => {\n setEditingTask(null)\n setDialogMode('create')\n setDialogOpen(true)\n }, [])\n\n const openEditDialog = React.useCallback((task: TodoLinkSummary) => {\n setEditingTask(task)\n setDialogMode('edit')\n setDialogOpen(true)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n setEditingTask(null)\n }, [])\n\n React.useEffect(() => {\n if (!onActionChange) return\n if (!entityId) {\n onActionChange(null)\n return\n }\n onActionChange({\n label: addActionLabel,\n onClick: openCreateDialog,\n disabled: isMutating,\n })\n return () => {\n onActionChange(null)\n }\n }, [addActionLabel, entityId, isMutating, onActionChange, openCreateDialog])\n\n React.useEffect(() => {\n if (!onLoadingChange) return\n onLoadingChange(isInitialLoading || isMutating)\n }, [isInitialLoading, isMutating, onLoadingChange])\n\n React.useEffect(() => {\n if (!hasMore) return\n if (typeof IntersectionObserver === 'undefined') return\n const el = sentinelRef.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n loadMore().catch(() => {})\n }\n },\n { rootMargin: '200px 0px 200px 0px' },\n )\n observer.observe(el)\n return () => {\n observer.disconnect()\n }\n }, [hasMore, loadMore])\n\n const handleCreate = React.useCallback(\n async (payload: TaskFormPayload) => {\n try {\n await runWriteMutation(\n () => createTask(payload),\n {\n entityId,\n title: payload.base.title,\n isDone: payload.base.is_done ?? undefined,\n },\n )\n flash(t('customers.people.detail.tasks.createSuccess', 'Task created'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.people.detail.tasks.error', 'Failed to create task')\n flash(message, 'error')\n throw err\n }\n },\n [createTask, entityId, onDataRefresh, runWriteMutation, t],\n )\n\n const handleUpdate = React.useCallback(\n async (task: TodoLinkSummary, payload: TaskFormPayload) => {\n try {\n await runWriteMutation(\n () => updateTask(task, payload),\n {\n id: task.id,\n todoId: task.todoId,\n title: payload.base.title,\n isDone: payload.base.is_done ?? undefined,\n },\n )\n flash(t('customers.people.detail.tasks.updateSuccess', 'Task updated'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.updateError', 'Failed to update task')\n flash(message, 'error')\n throw err\n }\n },\n [onDataRefresh, runWriteMutation, t, updateTask],\n )\n\n const handleToggle = React.useCallback(\n async (task: TodoLinkSummary, nextIsDone: boolean) => {\n try {\n await runWriteMutation(\n () => toggleTask(task, nextIsDone),\n {\n id: task.id,\n todoId: task.todoId,\n isDone: nextIsDone,\n },\n )\n flash(\n nextIsDone\n ? t('customers.people.detail.tasks.completeSuccess', 'Task marked as done')\n : t('customers.people.detail.tasks.reopenSuccess', 'Task reopened'),\n 'success',\n )\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.toggleError', 'Failed to update task status')\n flash(message, 'error')\n }\n },\n [onDataRefresh, runWriteMutation, t, toggleTask],\n )\n\n const handleDelete = React.useCallback(\n async (task: TodoLinkSummary) => {\n try {\n await runWriteMutation(\n () => unlinkTask(task),\n {\n id: task.id,\n todoId: task.todoId,\n },\n )\n flash(t('customers.people.detail.tasks.deleteSuccess', 'Task removed'), 'success')\n await Promise.resolve(onDataRefresh?.())\n await refresh()\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.deleteError', 'Failed to remove task')\n flash(message, 'error')\n }\n },\n [onDataRefresh, refresh, runWriteMutation, t, unlinkTask],\n )\n\n const handleCancel = React.useCallback(\n async (task: TodoLinkSummary) => {\n if (!useCanonicalInteractions) return\n try {\n await runWriteMutation(\n () => canonicalResult.cancelInteraction(task.todoId),\n { id: task.todoId },\n )\n flash(t('customers.people.detail.tasks.cancelSuccess', 'Task canceled'), 'success')\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n const message =\n err instanceof Error ? err.message : t('customers.people.detail.tasks.cancelError', 'Failed to cancel task')\n flash(message, 'error')\n }\n },\n [canonicalResult, onDataRefresh, runWriteMutation, t, useCanonicalInteractions],\n )\n\n const renderTaskMeta = React.useCallback(\n (task: TodoLinkSummary) => {\n const meta: string[] = []\n if (task.status === 'canceled') {\n meta.push(t('customers.people.detail.tasks.status.canceled', 'Canceled'))\n }\n if (typeof task.priority === 'number') {\n meta.push(t('customers.people.detail.tasks.priorityLabel', 'Priority {{priority}}', { priority: task.priority }))\n }\n if (task.severity) {\n meta.push(\n t(\n `customers.people.detail.tasks.severity.${task.severity}`,\n task.severity.charAt(0).toUpperCase() + task.severity.slice(1),\n ),\n )\n }\n if (task.dueAt) {\n const dueLabel =\n formatDate(task.dueAt) ??\n formatDateTime(task.dueAt) ??\n t('customers.people.detail.tasks.dueLabel', 'Due {{date}}', { date: task.dueAt })\n meta.push(t('customers.people.detail.tasks.dueLabel', 'Due {{date}}', { date: dueLabel }))\n }\n return meta\n },\n [t],\n )\n\n const handleDialogSubmit = React.useCallback(\n async (payload: TaskFormPayload) => {\n if (dialogMode === 'edit' && editingTask) {\n await handleUpdate(editingTask, payload)\n } else {\n await handleCreate(payload)\n }\n },\n [dialogMode, editingTask, handleCreate, handleUpdate],\n )\n\n const hasTasks = sortedTasks.length > 0\n\n return (\n <div className=\"mt-0 space-y-6\">\n <div className=\"space-y-4\">\n {isInitialLoading ? (\n <LoadingMessage\n label={t('customers.people.detail.tasks.loading', 'Loading tasks\u2026')}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n ) : null}\n\n {!isInitialLoading && !hasTasks ? (\n <TabEmptyState title={emptyState.title}>\n <Button asChild variant=\"outline\" size=\"sm\" className=\"mt-2\">\n <Link href=\"/backend/customer-tasks\">\n {t('customers.people.detail.tasks.viewAll', 'View all tasks')}\n </Link>\n </Button>\n </TabEmptyState>\n ) : null}\n\n {!isInitialLoading && hasTasks ? (\n <div className=\"space-y-4\">\n {error ? (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n {sortedTasks.map((task) => {\n const todoHref = task.externalHref ?? resolveTodoHref(task.todoSource, task.todoId)\n const createdLabel = formatDateTime(task.createdAt) ?? emptyLabel\n const meta = renderTaskMeta(task)\n const title = task.title ?? t('customers.people.detail.tasks.untitled', 'Untitled task')\n const isDone = task.isDone === true\n const isCanceled = task.status === 'canceled'\n const checkboxId = `person-task-${task.id}`\n const isPendingToggle = pendingTaskId === task.todoId\n return (\n <article key={task.id} className=\"group space-y-3 rounded-lg border bg-card p-4 transition hover:border-border/70\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <TimelineItemHeader\n title={\n <span className=\"inline-flex items-center gap-2\">\n <input\n id={checkboxId}\n type=\"checkbox\"\n checked={isDone}\n onChange={(event) => {\n const next = event.target.checked\n void handleToggle(task, next)\n }}\n disabled={isMutating || isPendingToggle || isCanceled}\n className=\"h-4 w-4 rounded border\"\n />\n <span\n className={cn(\n 'text-sm font-semibold',\n isDone ? 'line-through text-muted-foreground' : undefined,\n isCanceled ? 'text-muted-foreground' : undefined,\n )}\n >\n {title}\n </span>\n </span>\n }\n timestamp={task.createdAt}\n fallbackTimestampLabel={createdLabel}\n />\n <div className=\"flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openEditDialog(task)}\n disabled={isMutating}\n aria-label={t('ui.actions.edit', 'Edit')}\n >\n {isMutating && editingTask?.id === task.id && dialogMode === 'edit' ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Pencil className=\"h-4 w-4\" />\n )}\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => handleDelete(task)}\n disabled={isMutating}\n aria-label={t('ui.actions.delete', 'Delete')}\n >\n {isMutating ? <Loader2 className=\"h-4 w-4 animate-spin text-destructive\" /> : <Trash2 className=\"h-4 w-4\" />}\n </IconButton>\n </div>\n </div>\n {meta.length ? (\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {meta.map((entry) => (\n <span key={`${task.id}-${entry}`} className=\"rounded bg-muted px-2 py-1 text-xs font-medium text-muted-foreground\">\n {entry}\n </span>\n ))}\n </div>\n ) : null}\n {task.description ? (\n <p className=\"text-sm text-muted-foreground whitespace-pre-wrap\">{task.description}</p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-3 text-xs\">\n {useCanonicalInteractions && !isDone && !isCanceled ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 text-xs font-medium text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={() => void handleCancel(task)}\n disabled={isMutating || isPendingToggle}\n >\n {t('customers.people.detail.tasks.cancelAction', 'Cancel task')}\n </Button>\n ) : null}\n {todoHref ? (\n <Link href={todoHref} className=\"text-primary hover:underline\">\n {t('customers.people.detail.tasks.openTask', 'Open task')}\n </Link>\n ) : null}\n </div>\n </article>\n )\n })}\n <div ref={sentinelRef} />\n {hasMore ? (\n <div className=\"flex justify-center\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => loadMore().catch(() => {})} disabled={isLoadingMore}>\n {isLoadingMore ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {t('customers.people.detail.tasks.loadingMore', 'Loading\u2026')}\n </>\n ) : (\n t('customers.people.detail.tasks.loadMore', 'Load more')\n )}\n </Button>\n </div>\n ) : null}\n {isLoadingMore ? (\n <div className=\"flex justify-center text-xs text-muted-foreground\">\n <Loader2 className=\"mr-2 h-3 w-3 animate-spin\" />\n {t('customers.people.detail.tasks.loadingMore', 'Loading\u2026')}\n </div>\n ) : null}\n </div>\n ) : null}\n {!isInitialLoading && hasTasks ? (\n <div className=\"flex justify-center\">\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customer-tasks\">\n {t('customers.people.detail.tasks.viewAll', 'View all tasks')}\n </Link>\n </Button>\n </div>\n ) : null}\n </div>\n\n <TaskDialog\n open={dialogOpen}\n mode={dialogMode}\n onOpenChange={(next) => {\n if (!next) closeDialog()\n else setDialogOpen(true)\n }}\n initialValues={buildInitialFormValues(editingTask)}\n onSubmit={handleDialogSubmit}\n isSubmitting={isMutating}\n contextMessage={dialogContextMessage}\n />\n </div>\n )\n}\n\nexport default TasksSection\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { Globe, Repeat } from "lucide-react";
|
|
4
4
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
5
5
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
6
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
7
|
+
import { DatePicker } from "@open-mercato/ui/primitives/date-picker";
|
|
8
|
+
import { TimePicker } from "@open-mercato/ui/backend/inputs/TimePicker";
|
|
9
|
+
import {
|
|
10
|
+
Select,
|
|
11
|
+
SelectContent,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue
|
|
15
|
+
} from "@open-mercato/ui/primitives/select";
|
|
7
16
|
import { isVisible, getFieldLabel } from "./fieldConfig.js";
|
|
8
|
-
|
|
17
|
+
function parseIsoDate(value) {
|
|
18
|
+
if (!value) return null;
|
|
19
|
+
const parts = value.split("-");
|
|
20
|
+
if (parts.length !== 3) return null;
|
|
21
|
+
const [y, m, d] = parts.map((p) => parseInt(p, 10));
|
|
22
|
+
if (!Number.isFinite(y) || !Number.isFinite(m) || !Number.isFinite(d)) return null;
|
|
23
|
+
const date = new Date(y, m - 1, d);
|
|
24
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
25
|
+
}
|
|
26
|
+
function formatIsoDate(date) {
|
|
27
|
+
if (!date) return "";
|
|
28
|
+
const y = date.getFullYear();
|
|
29
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
30
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
31
|
+
return `${y}-${m}-${d}`;
|
|
32
|
+
}
|
|
33
|
+
const DURATION_OPTIONS = [
|
|
34
|
+
{ value: 15, key: "customers.schedule.duration.option.15min", fallback: "15 min" },
|
|
35
|
+
{ value: 30, key: "customers.schedule.duration.option.30min", fallback: "30 min" },
|
|
36
|
+
{ value: 45, key: "customers.schedule.duration.option.45min", fallback: "45 min" },
|
|
37
|
+
{ value: 60, key: "customers.schedule.duration.option.1hour", fallback: "1 hour" },
|
|
38
|
+
{ value: 90, key: "customers.schedule.duration.option.1h30m", fallback: "1h 30m" },
|
|
39
|
+
{ value: 120, key: "customers.schedule.duration.option.2hours", fallback: "2 hours" }
|
|
40
|
+
];
|
|
9
41
|
const DAYS_OF_WEEK = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
10
42
|
function DateTimeFields({
|
|
11
43
|
visible,
|
|
@@ -40,90 +72,67 @@ function DateTimeFields({
|
|
|
40
72
|
const dateErrorId = "schedule-date-error";
|
|
41
73
|
const timeErrorId = "schedule-time-error";
|
|
42
74
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
43
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
|
|
44
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-[
|
|
75
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-3", children: [
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-[1.5] flex-col gap-1.5", children: [
|
|
45
77
|
/* @__PURE__ */ jsxs("label", { className: "text-overline font-semibold text-muted-foreground tracking-wider", children: [
|
|
46
78
|
getFieldLabel(activityType, "date", t, "customers.schedule.date", "Date"),
|
|
47
79
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-1 text-status-error-foreground", children: "*" })
|
|
48
80
|
] }),
|
|
49
|
-
/* @__PURE__ */
|
|
50
|
-
|
|
81
|
+
/* @__PURE__ */ jsx(
|
|
82
|
+
DatePicker,
|
|
51
83
|
{
|
|
84
|
+
value: parseIsoDate(date),
|
|
85
|
+
onChange: (next) => setDate(formatIsoDate(next)),
|
|
86
|
+
placeholder: t("customers.schedule.date.placeholder", "Pick a date"),
|
|
87
|
+
required: true,
|
|
88
|
+
"aria-describedby": dateMissing ? dateErrorId : void 0,
|
|
52
89
|
className: cn(
|
|
53
|
-
"
|
|
54
|
-
dateMissing
|
|
55
|
-
)
|
|
56
|
-
children: [
|
|
57
|
-
/* @__PURE__ */ jsx(Calendar, { className: "size-3.5 text-muted-foreground" }),
|
|
58
|
-
/* @__PURE__ */ jsx(
|
|
59
|
-
"input",
|
|
60
|
-
{
|
|
61
|
-
type: "date",
|
|
62
|
-
value: date,
|
|
63
|
-
onChange: (e) => setDate(e.target.value),
|
|
64
|
-
required: true,
|
|
65
|
-
"aria-required": "true",
|
|
66
|
-
"aria-invalid": dateMissing ? true : void 0,
|
|
67
|
-
"aria-describedby": dateMissing ? dateErrorId : void 0,
|
|
68
|
-
className: "flex-1 bg-transparent text-sm text-foreground focus:outline-none"
|
|
69
|
-
}
|
|
70
|
-
)
|
|
71
|
-
]
|
|
90
|
+
"h-10",
|
|
91
|
+
dateMissing && "border-status-error-border"
|
|
92
|
+
)
|
|
72
93
|
}
|
|
73
94
|
),
|
|
74
95
|
dateMissing ? /* @__PURE__ */ jsx("p", { id: dateErrorId, className: "text-xs text-status-error-foreground", children: t("customers.activities.errors.dateRequired", "Date is required") }) : null
|
|
75
96
|
] }),
|
|
76
|
-
showStartTime && /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-1.5", children: [
|
|
97
|
+
showStartTime && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
|
|
77
98
|
/* @__PURE__ */ jsxs("label", { className: "text-overline font-semibold text-muted-foreground tracking-wider", children: [
|
|
78
99
|
getFieldLabel(activityType, "startTime", t, "customers.schedule.start", "Start"),
|
|
79
100
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-1 text-status-error-foreground", children: "*" })
|
|
80
101
|
] }),
|
|
81
|
-
/* @__PURE__ */
|
|
82
|
-
|
|
102
|
+
/* @__PURE__ */ jsx(
|
|
103
|
+
TimePicker,
|
|
83
104
|
{
|
|
105
|
+
value: startTime || null,
|
|
106
|
+
onChange: (next) => setStartTime(next ?? ""),
|
|
107
|
+
disabled: allDay,
|
|
108
|
+
placeholder: t("customers.schedule.start.placeholder", "Pick a time"),
|
|
84
109
|
className: cn(
|
|
85
|
-
"
|
|
86
|
-
timeMissing ? "border-status-error-border" :
|
|
110
|
+
"py-2.5",
|
|
111
|
+
timeMissing ? "border-status-error-border" : void 0
|
|
87
112
|
),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
/* @__PURE__ */ jsx(
|
|
91
|
-
"input",
|
|
92
|
-
{
|
|
93
|
-
type: "time",
|
|
94
|
-
value: startTime,
|
|
95
|
-
onChange: (e) => setStartTime(e.target.value),
|
|
96
|
-
disabled: allDay,
|
|
97
|
-
required: !allDay,
|
|
98
|
-
"aria-required": !allDay,
|
|
99
|
-
"aria-invalid": timeMissing ? true : void 0,
|
|
100
|
-
"aria-describedby": timeMissing ? timeErrorId : void 0,
|
|
101
|
-
className: "flex-1 bg-transparent text-sm text-foreground focus:outline-none disabled:opacity-50"
|
|
102
|
-
}
|
|
103
|
-
)
|
|
104
|
-
]
|
|
113
|
+
showNowButton: true,
|
|
114
|
+
showClearButton: false
|
|
105
115
|
}
|
|
106
116
|
),
|
|
107
117
|
timeMissing ? /* @__PURE__ */ jsx("p", { id: timeErrorId, className: "text-xs text-status-error-foreground", children: t("customers.activities.errors.timeRequired", "Time is required") }) : null
|
|
108
118
|
] }),
|
|
109
|
-
showDuration && /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-1.5", children: [
|
|
119
|
+
showDuration && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
|
|
110
120
|
/* @__PURE__ */ jsx("label", { className: "text-overline font-semibold text-muted-foreground tracking-wider", children: getFieldLabel(activityType, "duration", t, "customers.schedule.duration", "Duration") }),
|
|
111
|
-
/* @__PURE__ */ jsxs(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
] })
|
|
121
|
+
/* @__PURE__ */ jsxs(
|
|
122
|
+
Select,
|
|
123
|
+
{
|
|
124
|
+
value: String(duration),
|
|
125
|
+
onValueChange: (next) => {
|
|
126
|
+
const parsed = Number(next);
|
|
127
|
+
if (Number.isFinite(parsed)) setDuration(parsed);
|
|
128
|
+
},
|
|
129
|
+
disabled: allDay,
|
|
130
|
+
children: [
|
|
131
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-10", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("customers.schedule.duration.placeholder", "Pick duration") }) }),
|
|
132
|
+
/* @__PURE__ */ jsx(SelectContent, { children: DURATION_OPTIONS.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: String(option.value), children: t(option.key, option.fallback) }, option.value)) })
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
)
|
|
127
136
|
] })
|
|
128
137
|
] }),
|
|
129
138
|
showAllDay && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3.5 text-xs text-muted-foreground", children: [
|