@open-mercato/core 0.6.4-develop.4178.1.aad9ddaa95 → 0.6.4-develop.4210.1.d412061cfe
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/api_docs/frontend/docs/api/Explorer.js +14 -14
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/audit_logs/backend/audit-logs/page.js +3 -3
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +3 -7
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +34 -49
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/ChangelogEntryRow.js +2 -2
- package/dist/modules/customers/components/detail/ChangelogEntryRow.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailHeader.js +10 -1
- package/dist/modules/customers/components/detail/CompanyDetailHeader.js.map +2 -2
- package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js +7 -51
- package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js.map +2 -2
- package/dist/modules/customers/components/detail/DetailTabsLayout.js +1 -1
- package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
- package/dist/modules/customers/components/detail/ManageTagsDialog.js +25 -33
- package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCard.js +3 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailHeader.js +3 -2
- package/dist/modules/customers/components/detail/PersonDetailHeader.js.map +2 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -5
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
- package/dist/modules/customers/components/detail/utils.js +0 -7
- package/dist/modules/customers/components/detail/utils.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +3 -8
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/dictionaries/components/AppearanceSelector.js +3 -4
- package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +17 -17
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +1 -1
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +65 -1
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/planner/lib/deleteAvailabilityRuleSet.js +20 -0
- package/dist/modules/planner/lib/deleteAvailabilityRuleSet.js.map +7 -0
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +14 -37
- package/dist/modules/sales/api/quotes/accept/route.js.map +3 -3
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +1 -1
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +1 -1
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +6 -2
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +3 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +1 -1
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/translations/components/TranslationDrawerAction.js +27 -65
- package/dist/modules/translations/components/TranslationDrawerAction.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +2 -2
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +54 -92
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +14 -14
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +2 -2
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +3 -3
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -8
- package/src/modules/customers/components/detail/ActivityCard.tsx +2 -4
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +28 -55
- package/src/modules/customers/components/detail/ChangelogEntryRow.tsx +6 -4
- package/src/modules/customers/components/detail/CompanyDetailHeader.tsx +7 -3
- package/src/modules/customers/components/detail/DealLinkedEntitiesTab.tsx +11 -49
- package/src/modules/customers/components/detail/DetailTabsLayout.tsx +1 -1
- package/src/modules/customers/components/detail/ManageTagsDialog.tsx +27 -36
- package/src/modules/customers/components/detail/PersonCard.tsx +3 -4
- package/src/modules/customers/components/detail/PersonDetailHeader.tsx +3 -4
- package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -7
- package/src/modules/customers/components/detail/utils.ts +0 -7
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +4 -9
- package/src/modules/dictionaries/components/AppearanceSelector.tsx +3 -4
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +21 -21
- package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +1 -1
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +62 -0
- package/src/modules/planner/lib/deleteAvailabilityRuleSet.ts +35 -0
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +2 -2
- package/src/modules/sales/api/quotes/accept/route.ts +22 -38
- package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +1 -1
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +1 -1
- package/src/modules/sales/commands/documents.ts +16 -2
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +3 -2
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +1 -1
- package/src/modules/staff/i18n/de.json +5 -0
- package/src/modules/staff/i18n/en.json +5 -0
- package/src/modules/staff/i18n/es.json +5 -0
- package/src/modules/staff/i18n/pl.json +5 -0
- package/src/modules/translations/components/TranslationDrawerAction.tsx +31 -66
- package/src/modules/translations/components/TranslationManager.tsx +2 -2
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +53 -84
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
async function deleteAvailabilityRuleSet(actions) {
|
|
2
|
+
const ruleSetId = actions.ruleSetId;
|
|
3
|
+
if (!ruleSetId) return "noop";
|
|
4
|
+
const confirmed = await actions.confirmDelete();
|
|
5
|
+
if (!confirmed) return "cancelled";
|
|
6
|
+
try {
|
|
7
|
+
await actions.deleteRuleSet(ruleSetId);
|
|
8
|
+
await actions.clearAssignment();
|
|
9
|
+
await actions.refreshRuleSets();
|
|
10
|
+
actions.onSuccess();
|
|
11
|
+
return "deleted";
|
|
12
|
+
} catch (error) {
|
|
13
|
+
actions.onError(error);
|
|
14
|
+
return "failed";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
deleteAvailabilityRuleSet
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=deleteAvailabilityRuleSet.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/planner/lib/deleteAvailabilityRuleSet.ts"],
|
|
4
|
+
"sourcesContent": ["export type DeleteAvailabilityRuleSetOutcome = 'deleted' | 'cancelled' | 'noop' | 'failed'\n\nexport type DeleteAvailabilityRuleSetActions = {\n ruleSetId: string | null | undefined\n confirmDelete: () => Promise<boolean>\n deleteRuleSet: (ruleSetId: string) => Promise<void>\n clearAssignment: () => Promise<void>\n refreshRuleSets: () => Promise<void>\n onSuccess: () => void\n onError: (error: unknown) => void\n}\n\n/**\n * Orchestrates deleting an availability schedule (rule set) from the availability\n * editor: confirm, delete the schedule, clear it from the current subject so no\n * dangling assignment remains, refresh the selector, and report the outcome.\n */\nexport async function deleteAvailabilityRuleSet(\n actions: DeleteAvailabilityRuleSetActions,\n): Promise<DeleteAvailabilityRuleSetOutcome> {\n const ruleSetId = actions.ruleSetId\n if (!ruleSetId) return 'noop'\n const confirmed = await actions.confirmDelete()\n if (!confirmed) return 'cancelled'\n try {\n await actions.deleteRuleSet(ruleSetId)\n await actions.clearAssignment()\n await actions.refreshRuleSets()\n actions.onSuccess()\n return 'deleted'\n } catch (error) {\n actions.onError(error)\n return 'failed'\n }\n}\n"],
|
|
5
|
+
"mappings": "AAiBA,eAAsB,0BACpB,SAC2C;AAC3C,QAAM,YAAY,QAAQ;AAC1B,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,YAAY,MAAM,QAAQ,cAAc;AAC9C,MAAI,CAAC,UAAW,QAAO;AACvB,MAAI;AACF,UAAM,QAAQ,cAAc,SAAS;AACrC,UAAM,QAAQ,gBAAgB;AAC9B,UAAM,QAAQ,gBAAgB;AAC9B,YAAQ,UAAU;AAClB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,QAAQ,KAAK;AACrB,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -439,7 +439,7 @@ function ResourcesResourceDetailPage({ params }) {
|
|
|
439
439
|
"aria-selected": activeTab === tab.id,
|
|
440
440
|
variant: "ghost",
|
|
441
441
|
size: "sm",
|
|
442
|
-
className: `relative -mb-px h-auto rounded-none border-b-2 px-0 py-2 font-medium ${activeTab === tab.id ? "border-
|
|
442
|
+
className: `relative -mb-px h-auto rounded-none border-b-2 px-0 py-2 font-medium ${activeTab === tab.id ? "border-accent-indigo text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"}`,
|
|
443
443
|
onClick: () => setActiveTab(tab.id),
|
|
444
444
|
children: tab.label
|
|
445
445
|
},
|
|
@@ -454,7 +454,7 @@ function ResourcesResourceDetailPage({ params }) {
|
|
|
454
454
|
type: "button",
|
|
455
455
|
variant: "ghost",
|
|
456
456
|
size: "sm",
|
|
457
|
-
className: `relative -mb-px h-auto rounded-none border-b-2 px-0 py-1 font-medium ${activeDetailTab === tab.id ? "border-
|
|
457
|
+
className: `relative -mb-px h-auto rounded-none border-b-2 px-0 py-1 font-medium ${activeDetailTab === tab.id ? "border-accent-indigo text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"}`,
|
|
458
458
|
onClick: () => setActiveDetailTab(tab.id),
|
|
459
459
|
children: tab.label
|
|
460
460
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/resources/backend/resources/resources/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { ActivitiesSection, NotesSection, RecordNotFoundState, type SectionAction, type TagOption } from '@open-mercato/ui/backend/detail'\nimport { VersionHistoryAction } from '@open-mercato/ui/backend/version-history'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { buildResourceScheduleItems } from '@open-mercato/core/modules/resources/lib/resourceSchedule'\nimport { RESOURCES_RESOURCE_FIELDSET_DEFAULT } from '@open-mercato/core/modules/resources/lib/resourceCustomFields'\nimport type { AvailabilityScheduleItemBuilder } from '@open-mercato/core/modules/planner/components/AvailabilityRulesEditor'\nimport { AvailabilityRulesEditor } from '@open-mercato/core/modules/planner/components/AvailabilityRulesEditor'\nimport { ResourcesResourceForm, useResourcesResourceFormConfig } from '@open-mercato/core/modules/resources/components/ResourceCrudForm'\nimport { renderDictionaryColor, renderDictionaryIcon, ICON_SUGGESTIONS } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { createResourceNotesAdapter } from '@open-mercato/core/modules/resources/components/detail/notesAdapter'\nimport { createResourceActivitiesAdapter } from '@open-mercato/core/modules/resources/components/detail/activitiesAdapter'\nimport {\n createResourceDictionaryEntry,\n loadResourceDictionary,\n type DictionaryEntryOption,\n} from '@open-mercato/core/modules/resources/components/detail/dictionaries'\nimport type { DictionarySelectLabels } from '@open-mercato/core/modules/dictionaries/components/DictionaryEntrySelect'\n\ntype ResourceRecord = {\n id: string\n name: string\n description?: string | null\n resourceTypeId: string | null\n capacity: number | null\n capacityUnitValue: string | null\n capacityUnitName: string | null\n capacityUnitColor: string | null\n capacityUnitIcon: string | null\n tags?: TagOption[] | null\n isActive: boolean\n appearanceIcon?: string | null\n appearanceColor?: string | null\n resource_type_id?: string | null\n capacity_unit_value?: string | null\n capacity_unit_name?: string | null\n capacity_unit_color?: string | null\n capacity_unit_icon?: string | null\n appearance_icon?: string | null\n appearance_color?: string | null\n is_active?: boolean\n availabilityRuleSetId?: string | null\n availability_rule_set_id?: string | null\n} & Record<string, unknown>\n\ntype ResourceResponse = {\n items: ResourceRecord[]\n}\n\nfunction normalizeResourceRecord(record: ResourceRecord): ResourceRecord {\n return {\n ...record,\n resourceTypeId: record.resourceTypeId ?? record.resource_type_id ?? null,\n description: record.description ?? null,\n capacityUnitValue: record.capacityUnitValue ?? record.capacity_unit_value ?? null,\n capacityUnitName: record.capacityUnitName ?? record.capacity_unit_name ?? null,\n capacityUnitColor: record.capacityUnitColor ?? record.capacity_unit_color ?? null,\n capacityUnitIcon: record.capacityUnitIcon ?? record.capacity_unit_icon ?? null,\n appearanceIcon: record.appearanceIcon ?? record.appearance_icon ?? null,\n appearanceColor: record.appearanceColor ?? record.appearance_color ?? null,\n isActive: record.isActive ?? record.is_active ?? true,\n }\n}\n\nexport default function ResourcesResourceDetailPage({ params }: { params?: { id?: string } }) {\n const resourceId = params?.id\n const t = useT()\n const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])\n const router = useRouter()\n const searchParams = useSearchParams()\n const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [tags, setTags] = React.useState<TagOption[]>([])\n const [activeTab, setActiveTab] = React.useState<'details' | 'availability'>('details')\n const [activeDetailTab, setActiveDetailTab] = React.useState<'notes' | 'activities'>('notes')\n const [sectionAction, setSectionAction] = React.useState<SectionAction | null>(null)\n const [availabilityRuleSetId, setAvailabilityRuleSetId] = React.useState<string | null>(null)\n const [activityDictionaryId, setActivityDictionaryId] = React.useState<string | null>(null)\n const [activityTypeEntries, setActivityTypeEntries] = React.useState<DictionaryEntryOption[]>([])\n const flashShownRef = React.useRef(false)\n\n const availabilityMode = 'availability'\n const notesAdapter = React.useMemo(() => createResourceNotesAdapter(detailTranslator), [detailTranslator])\n const activitiesAdapter = React.useMemo(() => createResourceActivitiesAdapter(detailTranslator), [detailTranslator])\n\n const activityTypeLabels = React.useMemo<DictionarySelectLabels>(() => ({\n placeholder: t('resources.resources.detail.activities.dictionary.placeholder', 'Select an activity type'),\n addLabel: t('resources.resources.detail.activities.dictionary.add', 'Add type'),\n addPrompt: t('resources.resources.detail.activities.dictionary.prompt', 'Name the type'),\n dialogTitle: t('resources.resources.detail.activities.dictionary.dialogTitle', 'Add activity type'),\n valueLabel: t('resources.resources.detail.activities.dictionary.valueLabel', 'Name'),\n valuePlaceholder: t('resources.resources.detail.activities.dictionary.valuePlaceholder', 'Name'),\n labelLabel: t('resources.resources.detail.activities.dictionary.labelLabel', 'Label'),\n labelPlaceholder: t('resources.resources.detail.activities.dictionary.labelPlaceholder', 'Display name shown in UI'),\n emptyError: t('resources.resources.detail.activities.dictionary.emptyError', 'Please enter a name'),\n cancelLabel: t('resources.resources.detail.activities.dictionary.cancel', 'Cancel'),\n saveLabel: t('resources.resources.detail.activities.dictionary.save', 'Save'),\n saveShortcutHint: t('resources.resources.detail.activities.dictionary.saveShortcut', '\u2318/Ctrl + Enter'),\n errorLoad: t('resources.resources.detail.activities.dictionary.errorLoad', 'Failed to load options'),\n errorSave: t('resources.resources.detail.activities.dictionary.errorSave', 'Failed to save option'),\n loadingLabel: t('resources.resources.detail.activities.dictionary.loading', 'Loading\u2026'),\n manageTitle: t('resources.resources.detail.activities.dictionary.manage', 'Manage dictionary'),\n }), [t])\n\n const loadActivityOptions = React.useCallback(async () => {\n const { dictionary, entries } = await loadResourceDictionary('activityTypes')\n setActivityDictionaryId(dictionary?.id ?? null)\n setActivityTypeEntries(entries)\n return entries\n }, [])\n\n const createActivityOption = React.useCallback(\n async (input: { value: string; label?: string; color?: string | null; icon?: string | null }) => {\n const entry = await createResourceDictionaryEntry('activityTypes', input)\n if (!entry) {\n throw new Error(t('resources.resources.detail.activities.dictionary.errorSave', 'Failed to save option'))\n }\n return entry\n },\n [t],\n )\n\n React.useEffect(() => {\n loadActivityOptions().catch(() => {})\n }, [loadActivityOptions])\n\n const activityTypeMap = React.useMemo(\n () => new Map(activityTypeEntries.map((entry) => [entry.value, entry])),\n [activityTypeEntries],\n )\n\n const resolveActivityPresentation = React.useCallback(\n (activity: { activityType: string; appearanceIcon?: string | null; appearanceColor?: string | null }) => {\n const entry = activityTypeMap.get(activity.activityType)\n return {\n label: entry?.label ?? activity.activityType,\n icon: entry?.icon ?? activity.appearanceIcon ?? null,\n color: entry?.color ?? activity.appearanceColor ?? null,\n }\n },\n [activityTypeMap],\n )\n\n const manageActivityHref = React.useMemo(() => {\n if (!activityDictionaryId) return '/backend/config/dictionaries'\n return `/backend/config/dictionaries?dictionaryId=${encodeURIComponent(activityDictionaryId)}`\n }, [activityDictionaryId])\n\n const appearanceLabels = React.useMemo(() => ({\n colorLabel: t('resources.resources.detail.activities.appearance.colorLabel', 'Color'),\n colorHelp: t('resources.resources.detail.activities.appearance.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('resources.resources.detail.activities.appearance.colorClear', 'Remove color'),\n iconLabel: t('resources.resources.detail.activities.appearance.iconLabel', 'Icon or emoji'),\n iconPlaceholder: t('resources.resources.detail.activities.appearance.iconPlaceholder', 'Type an emoji or pick one of the suggestions.'),\n iconPickerTriggerLabel: t('resources.resources.detail.activities.appearance.iconBrowse', 'Browse icons and emojis'),\n iconSearchPlaceholder: t('resources.resources.detail.activities.appearance.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('resources.resources.detail.activities.appearance.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('resources.resources.detail.activities.appearance.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('resources.resources.detail.activities.appearance.iconClear', 'Remove icon'),\n previewEmptyLabel: t('resources.resources.detail.activities.appearance.previewEmpty', 'No appearance selected'),\n }), [t])\n\n const renderCustomFields = React.useCallback((activity: { id?: string; customFields?: Array<{ key: string; label?: string | null; value: unknown }> }) => {\n const entries = Array.isArray(activity.customFields) ? activity.customFields : []\n if (!entries.length) return null\n const emptyLabel = t('resources.resources.detail.activities.customFields.empty', 'Not provided')\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {entries.map((entry, index) => {\n const label = entry.label ?? entry.key\n const value = entry.value\n const hasValue = !(value == null || value === '' || (Array.isArray(value) && value.length === 0))\n const content = hasValue\n ? Array.isArray(value)\n ? value.map((item) => String(item)).join(', ')\n : String(value)\n : emptyLabel\n return (\n <div\n key={`activity-${activity.id ?? 'row'}-custom-${index}`}\n className=\"rounded-md border border-border/70 bg-muted/30 px-3 py-2\"\n >\n <div className=\"text-xs font-medium text-muted-foreground\">{label}</div>\n <div className=\"mt-1 text-sm text-foreground\">{content}</div>\n </div>\n )\n })}\n </div>\n )\n }, [t])\n\n React.useEffect(() => {\n if (!searchParams) return\n const tabParam = searchParams.get('tab')\n if (tabParam === 'availability') {\n setActiveTab('availability')\n }\n const created = searchParams.get('created') === '1'\n if (created && !flashShownRef.current) {\n flashShownRef.current = true\n flash(t('resources.resources.flash.createdAvailability', 'Saved. You can now set availability.'), 'success')\n const nextParams = new URLSearchParams(searchParams.toString())\n nextParams.delete('created')\n const nextQuery = nextParams.toString()\n const nextPath = resourceId\n ? `/backend/resources/resources/${encodeURIComponent(resourceId)}${nextQuery ? `?${nextQuery}` : ''}`\n : `/backend/resources/resources${nextQuery ? `?${nextQuery}` : ''}`\n router.replace(nextPath)\n }\n }, [resourceId, router, searchParams, t])\n\n const buildScheduleItems = React.useCallback<AvailabilityScheduleItemBuilder>(\n ({ availabilityRules, translate }) => buildResourceScheduleItems({\n availabilityRules,\n isAvailableByDefault: false,\n translate,\n }),\n [],\n )\n\n const tagLabels = React.useMemo(\n () => ({\n loading: t('resources.resources.tags.loading', 'Loading tags...'),\n placeholder: t('resources.resources.tags.placeholder', 'Type to add tags'),\n empty: t('resources.resources.tags.placeholder', 'No tags yet. Add labels to keep resources organized.'),\n loadError: t('resources.resources.tags.loadError', 'Failed to load tags.'),\n createError: t('resources.resources.tags.createError', 'Failed to create tag.'),\n updateError: t('resources.resources.tags.updateError', 'Failed to update tags.'),\n labelRequired: t('resources.resources.tags.labelRequired', 'Tag name is required.'),\n saveShortcut: t('resources.resources.tags.saveShortcut', 'Save Cmd+Enter / Ctrl+Enter'),\n cancelShortcut: t('resources.resources.tags.cancelShortcut', 'Cancel (Esc)'),\n edit: t('ui.forms.actions.edit', 'Edit'),\n cancel: t('ui.forms.actions.cancel', 'Cancel'),\n success: t('resources.resources.tags.success', 'Tags updated.'),\n }),\n [t],\n )\n\n const handleSubmit = React.useCallback(async (values: Record<string, unknown>) => {\n if (!resourceId) return\n const appearance = values.appearance && typeof values.appearance === 'object'\n ? values.appearance as { icon?: string | null; color?: string | null }\n : {}\n const { appearance: _appearance, ...rest } = values\n const customFieldsetCode = typeof values.customFieldsetCode === 'string' && values.customFieldsetCode.trim().length\n ? values.customFieldsetCode.trim()\n : RESOURCES_RESOURCE_FIELDSET_DEFAULT\n const payload: Record<string, unknown> = {\n ...rest,\n id: resourceId,\n resourceTypeId: values.resourceTypeId || null,\n capacity: values.capacity ? Number(values.capacity) : null,\n capacityUnitValue: values.capacityUnitValue ? String(values.capacityUnitValue) : null,\n appearanceIcon: appearance.icon ?? null,\n appearanceColor: appearance.color ?? null,\n isActive: values.isActive ?? true,\n customFieldsetCode,\n ...collectCustomFieldValues(values),\n }\n if (!payload.name || String(payload.name).trim().length === 0) {\n throw createCrudFormError(t('resources.resources.form.errors.nameRequired', 'Name is required.'))\n }\n await updateCrud('resources/resources', payload, {\n errorMessage: t('resources.resources.form.errors.update', 'Failed to update resource.'),\n })\n flash(t('resources.resources.form.flash.updated', 'Resource updated.'), 'success')\n }, [resourceId, t])\n\n const tabs = React.useMemo(() => ([\n { id: 'details', label: t('resources.resources.tabs.details', 'Details') },\n { id: 'availability', label: t('resources.resources.tabs.availability', 'Availability') },\n ]), [t])\n const detailTabs = React.useMemo(() => ([\n { id: 'notes' as const, label: t('resources.resources.detail.tabs.notes', 'Notes') },\n { id: 'activities' as const, label: t('resources.resources.detail.tabs.activities', 'Activities') },\n ]), [t])\n\n const loadTagOptions = React.useCallback(\n async (query?: string): Promise<TagOption[]> => {\n const params = new URLSearchParams({ pageSize: '100' })\n if (query) params.set('search', query)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/resources/tags?${params.toString()}`,\n undefined,\n { errorMessage: t('resources.resources.tags.loadError', 'Failed to load tags.') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n return items\n .map((item: unknown): TagOption | null => {\n if (!item || typeof item !== 'object') return null\n const raw = item as { id?: unknown; tagId?: unknown; label?: unknown; slug?: unknown; color?: unknown }\n const rawId =\n typeof raw.id === 'string'\n ? raw.id\n : typeof raw.tagId === 'string'\n ? raw.tagId\n : null\n if (!rawId) return null\n const labelValue =\n (typeof raw.label === 'string' && raw.label.trim().length && raw.label.trim()) ||\n (typeof raw.slug === 'string' && raw.slug.trim().length && raw.slug.trim()) ||\n rawId\n const color = typeof raw.color === 'string' && raw.color.trim().length ? raw.color.trim() : null\n return { id: rawId, label: labelValue, color }\n })\n .filter((entry): entry is TagOption => entry !== null)\n },\n [t],\n )\n\n const createTag = React.useCallback(\n async (label: string): Promise<TagOption> => {\n const trimmed = label.trim()\n if (!trimmed.length) {\n throw new Error(t('resources.resources.tags.labelRequired', 'Tag name is required.'))\n }\n const response = await apiCallOrThrow<Record<string, unknown>>(\n '/api/resources/tags',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ label: trimmed }),\n },\n { errorMessage: t('resources.resources.tags.createError', 'Failed to create tag.') },\n )\n const payload = response.result ?? {}\n const id =\n typeof payload?.id === 'string'\n ? payload.id\n : typeof (payload as any)?.tagId === 'string'\n ? (payload as any).tagId\n : ''\n if (!id) throw new Error(t('resources.resources.tags.createError', 'Failed to create tag.'))\n const color = typeof (payload as any)?.color === 'string' && (payload as any).color.trim().length\n ? (payload as any).color.trim()\n : null\n return { id, label: trimmed, color }\n },\n [t],\n )\n\n const assignTag = React.useCallback(async (tagId: string) => {\n if (!resourceId) return\n await apiCallOrThrow(\n '/api/resources/resources/tags/assign',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tagId, resourceId }),\n },\n { errorMessage: t('resources.resources.tags.updateError', 'Failed to update tags.') },\n )\n }, [resourceId, t])\n\n const unassignTag = React.useCallback(async (tagId: string) => {\n if (!resourceId) return\n await apiCallOrThrow(\n '/api/resources/resources/tags/unassign',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tagId, resourceId }),\n },\n { errorMessage: t('resources.resources.tags.updateError', 'Failed to update tags.') },\n )\n }, [resourceId, t])\n\n const handleTagsSave = React.useCallback(\n async ({ next, added, removed }: { next: TagOption[]; added: TagOption[]; removed: TagOption[] }) => {\n if (!resourceId) return\n for (const tag of added) {\n await assignTag(tag.id)\n }\n for (const tag of removed) {\n await unassignTag(tag.id)\n }\n setTags(next)\n flash(t('resources.resources.tags.success', 'Tags updated.'), 'success')\n },\n [assignTag, resourceId, t, unassignTag],\n )\n\n const tagsSection = React.useMemo(\n () => ({\n title: t('resources.resources.tags.title', 'Tags'),\n tags,\n onChange: setTags,\n loadOptions: loadTagOptions,\n createTag,\n onSave: handleTagsSave,\n labels: tagLabels,\n }),\n [createTag, handleTagsSave, loadTagOptions, t, tagLabels, tags],\n )\n\n const formConfig = useResourcesResourceFormConfig({ tagsSection })\n const { resourceTypesLoaded, resolveFieldsetCode } = formConfig\n\n React.useEffect(() => {\n if (!resourceId || !resourceTypesLoaded) return\n setIsNotFound(false)\n let cancelled = false\n async function loadResource() {\n try {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '1')\n if (resourceId) params.set('ids', resourceId)\n const record = await readApiResultOrThrow<ResourceResponse>(`/api/resources/resources?${params.toString()}`)\n const resourceRaw = Array.isArray(record?.items) ? record.items[0] : null\n const resource = resourceRaw ? normalizeResourceRecord(resourceRaw) : null\n if (!resource) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n if (!cancelled) {\n const customValues = extractCustomFieldEntries(resource)\n setTags(Array.isArray(resource.tags) ? resource.tags : [])\n setAvailabilityRuleSetId(\n typeof resource.availabilityRuleSetId === 'string'\n ? resource.availabilityRuleSetId\n : typeof resource.availability_rule_set_id === 'string'\n ? resource.availability_rule_set_id\n : null,\n )\n setInitialValues({\n id: resource.id,\n name: resource.name,\n description: resource.description ?? '',\n resourceTypeId: resource.resourceTypeId || '',\n capacity: resource.capacity ?? '',\n capacityUnitValue: resource.capacityUnitValue ?? '',\n appearance: { icon: resource.appearanceIcon ?? null, color: resource.appearanceColor ?? null },\n isActive: resource.isActive ?? true,\n customFieldsetCode: resource.resourceTypeId\n ? resolveFieldsetCode(resource.resourceTypeId)\n : RESOURCES_RESOURCE_FIELDSET_DEFAULT,\n ...customValues,\n })\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : t('resources.resources.form.errors.load', 'Failed to load resource.')\n flash(message, 'error')\n }\n }\n loadResource()\n return () => { cancelled = true }\n }, [resourceId, resourceTypesLoaded, resolveFieldsetCode, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!resourceId) return\n await deleteCrud('resources/resources', resourceId, {\n errorMessage: t('resources.resources.form.errors.delete', 'Failed to delete resource.'),\n })\n flash(t('resources.resources.form.flash.deleted', 'Resource deleted.'), 'success')\n router.push('/backend/resources/resources')\n }, [resourceId, router, t])\n\n const handleRulesetChange = React.useCallback(async (nextId: string | null) => {\n if (!resourceId) return\n await updateCrud('resources/resources', { id: resourceId, availabilityRuleSetId: nextId }, {\n errorMessage: t('resources.resources.availability.ruleset.updateError', 'Failed to update schedule.'),\n })\n setAvailabilityRuleSetId(nextId)\n flash(t('resources.resources.availability.ruleset.updateSuccess', 'Schedule updated.'), 'success')\n }, [resourceId, t])\n\n const resourceTitle =\n typeof initialValues?.name === 'string' && initialValues.name.trim().length > 0\n ? initialValues.name.trim()\n : t('resources.resources.detail.untitled', 'Unnamed resource')\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('resources.resources.form.errors.notFound', 'Resource not found.')}\n backHref=\"/backend/resources/resources\"\n backLabel={t('resources.resources.detail.back', 'Back to resources')}\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/resources/resources\"\n backLabel={t('resources.resources.detail.back', 'Back to resources')}\n utilityActions={(\n <>\n {resourceId ? (\n <SendObjectMessageDialog\n object={{\n entityModule: 'resources',\n entityType: 'resource',\n entityId: resourceId,\n previewData: {\n title: resourceTitle,\n },\n }}\n viewHref={`/backend/resources/resources/${resourceId}`}\n />\n ) : null}\n <VersionHistoryAction\n config={resourceId ? { resourceKind: 'resources.resource', resourceId, includeRelated: true } : null}\n t={t}\n />\n </>\n )}\n title={resourceTitle}\n subtitle={t('resources.resources.detail.subtitle', 'Resource profile and activity')}\n />\n\n <div className=\"border-b\">\n <nav className=\"flex flex-wrap items-center gap-5 text-sm\" aria-label={t('resources.resources.tabs.label', 'Resource sections')}>\n {tabs.map((tab) => (\n <Button\n key={tab.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === tab.id}\n variant=\"ghost\"\n size=\"sm\"\n className={`relative -mb-px h-auto rounded-none border-b-2 px-0 py-2 font-medium ${\n activeTab === tab.id\n ? 'border-primary text-foreground'\n : 'border-transparent text-muted-foreground hover:text-foreground'\n }`}\n onClick={() => setActiveTab(tab.id as 'details' | 'availability')}\n >\n {tab.label}\n </Button>\n ))}\n </nav>\n </div>\n\n {activeTab === 'details' ? (\n <>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex gap-2\">\n {detailTabs.map((tab) => (\n <Button\n key={tab.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={`relative -mb-px h-auto rounded-none border-b-2 px-0 py-1 font-medium ${\n activeDetailTab === tab.id\n ? 'border-primary text-foreground'\n : 'border-transparent text-muted-foreground hover:text-foreground'\n }`}\n onClick={() => setActiveDetailTab(tab.id)}\n >\n {tab.label}\n </Button>\n ))}\n </div>\n {sectionAction ? (\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={sectionAction.disabled}\n onClick={() => sectionAction.onClick()}\n >\n {sectionAction.icon ?? null}\n {sectionAction.label}\n </Button>\n ) : null}\n </div>\n {activeDetailTab === 'notes' ? (\n <NotesSection\n entityId={resourceId ?? null}\n emptyLabel={t('resources.resources.detail.notes.empty', 'No notes yet.')}\n viewerUserId={null}\n viewerName={null}\n viewerEmail={null}\n addActionLabel={t('resources.resources.detail.notes.add', 'Add note')}\n emptyState={{\n title: t('resources.resources.detail.notes.emptyTitle', 'Keep everyone in the loop'),\n actionLabel: t('resources.resources.detail.notes.emptyAction', 'Add a note'),\n }}\n onActionChange={setSectionAction}\n translator={detailTranslator}\n labelPrefix=\"resources.resources.detail.notes\"\n inlineLabelPrefix=\"resources.resources.detail.inline\"\n dataAdapter={notesAdapter}\n renderIcon={renderDictionaryIcon}\n renderColor={renderDictionaryColor}\n iconSuggestions={ICON_SUGGESTIONS}\n />\n ) : null}\n {activeDetailTab === 'activities' ? (\n <ActivitiesSection\n entityId={resourceId ?? null}\n addActionLabel={t('resources.resources.detail.activities.add', 'Log activity')}\n emptyState={{\n title: t('resources.resources.detail.activities.emptyTitle', 'No activities yet'),\n actionLabel: t('resources.resources.detail.activities.emptyAction', 'Add an activity'),\n }}\n onActionChange={setSectionAction}\n dataAdapter={activitiesAdapter}\n activityTypeLabels={activityTypeLabels}\n loadActivityOptions={loadActivityOptions}\n createActivityOption={createActivityOption}\n resolveActivityPresentation={resolveActivityPresentation}\n renderCustomFields={renderCustomFields}\n labelPrefix=\"resources.resources.detail.activities\"\n renderIcon={renderDictionaryIcon}\n renderColor={renderDictionaryColor}\n appearanceLabels={appearanceLabels}\n manageHref={manageActivityHref}\n customFieldEntityIds={['resources:resources_resource_activity']}\n />\n ) : null}\n </div>\n\n <div className=\"rounded-lg border bg-card p-4\">\n <h2 className=\"mb-4 text-sm font-semibold uppercase text-muted-foreground\">\n {t('resources.resources.detail.formTitle', 'Resource settings')}\n </h2>\n <ResourcesResourceForm\n embedded\n title={t('resources.resources.form.editTitle', 'Edit resource')}\n backHref=\"/backend/resources/resources\"\n cancelHref=\"/backend/resources/resources\"\n successRedirect=\"/backend/resources/resources\"\n formConfig={formConfig}\n initialValues={initialValues ?? undefined}\n onSubmit={handleSubmit}\n onDelete={handleDelete}\n isLoading={!initialValues}\n loadingMessage={t('resources.resources.form.loading', 'Loading resource...')}\n />\n </div>\n </>\n ) : (\n <AvailabilityRulesEditor\n subjectType=\"resource\"\n subjectId={resourceId ?? ''}\n labelPrefix=\"resources.resources\"\n mode={availabilityMode}\n rulesetId={availabilityRuleSetId}\n onRulesetChange={handleRulesetChange}\n buildScheduleItems={buildScheduleItems}\n />\n )}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { ActivitiesSection, NotesSection, RecordNotFoundState, type SectionAction, type TagOption } from '@open-mercato/ui/backend/detail'\nimport { VersionHistoryAction } from '@open-mercato/ui/backend/version-history'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { buildResourceScheduleItems } from '@open-mercato/core/modules/resources/lib/resourceSchedule'\nimport { RESOURCES_RESOURCE_FIELDSET_DEFAULT } from '@open-mercato/core/modules/resources/lib/resourceCustomFields'\nimport type { AvailabilityScheduleItemBuilder } from '@open-mercato/core/modules/planner/components/AvailabilityRulesEditor'\nimport { AvailabilityRulesEditor } from '@open-mercato/core/modules/planner/components/AvailabilityRulesEditor'\nimport { ResourcesResourceForm, useResourcesResourceFormConfig } from '@open-mercato/core/modules/resources/components/ResourceCrudForm'\nimport { renderDictionaryColor, renderDictionaryIcon, ICON_SUGGESTIONS } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { createResourceNotesAdapter } from '@open-mercato/core/modules/resources/components/detail/notesAdapter'\nimport { createResourceActivitiesAdapter } from '@open-mercato/core/modules/resources/components/detail/activitiesAdapter'\nimport {\n createResourceDictionaryEntry,\n loadResourceDictionary,\n type DictionaryEntryOption,\n} from '@open-mercato/core/modules/resources/components/detail/dictionaries'\nimport type { DictionarySelectLabels } from '@open-mercato/core/modules/dictionaries/components/DictionaryEntrySelect'\n\ntype ResourceRecord = {\n id: string\n name: string\n description?: string | null\n resourceTypeId: string | null\n capacity: number | null\n capacityUnitValue: string | null\n capacityUnitName: string | null\n capacityUnitColor: string | null\n capacityUnitIcon: string | null\n tags?: TagOption[] | null\n isActive: boolean\n appearanceIcon?: string | null\n appearanceColor?: string | null\n resource_type_id?: string | null\n capacity_unit_value?: string | null\n capacity_unit_name?: string | null\n capacity_unit_color?: string | null\n capacity_unit_icon?: string | null\n appearance_icon?: string | null\n appearance_color?: string | null\n is_active?: boolean\n availabilityRuleSetId?: string | null\n availability_rule_set_id?: string | null\n} & Record<string, unknown>\n\ntype ResourceResponse = {\n items: ResourceRecord[]\n}\n\nfunction normalizeResourceRecord(record: ResourceRecord): ResourceRecord {\n return {\n ...record,\n resourceTypeId: record.resourceTypeId ?? record.resource_type_id ?? null,\n description: record.description ?? null,\n capacityUnitValue: record.capacityUnitValue ?? record.capacity_unit_value ?? null,\n capacityUnitName: record.capacityUnitName ?? record.capacity_unit_name ?? null,\n capacityUnitColor: record.capacityUnitColor ?? record.capacity_unit_color ?? null,\n capacityUnitIcon: record.capacityUnitIcon ?? record.capacity_unit_icon ?? null,\n appearanceIcon: record.appearanceIcon ?? record.appearance_icon ?? null,\n appearanceColor: record.appearanceColor ?? record.appearance_color ?? null,\n isActive: record.isActive ?? record.is_active ?? true,\n }\n}\n\nexport default function ResourcesResourceDetailPage({ params }: { params?: { id?: string } }) {\n const resourceId = params?.id\n const t = useT()\n const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])\n const router = useRouter()\n const searchParams = useSearchParams()\n const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [tags, setTags] = React.useState<TagOption[]>([])\n const [activeTab, setActiveTab] = React.useState<'details' | 'availability'>('details')\n const [activeDetailTab, setActiveDetailTab] = React.useState<'notes' | 'activities'>('notes')\n const [sectionAction, setSectionAction] = React.useState<SectionAction | null>(null)\n const [availabilityRuleSetId, setAvailabilityRuleSetId] = React.useState<string | null>(null)\n const [activityDictionaryId, setActivityDictionaryId] = React.useState<string | null>(null)\n const [activityTypeEntries, setActivityTypeEntries] = React.useState<DictionaryEntryOption[]>([])\n const flashShownRef = React.useRef(false)\n\n const availabilityMode = 'availability'\n const notesAdapter = React.useMemo(() => createResourceNotesAdapter(detailTranslator), [detailTranslator])\n const activitiesAdapter = React.useMemo(() => createResourceActivitiesAdapter(detailTranslator), [detailTranslator])\n\n const activityTypeLabels = React.useMemo<DictionarySelectLabels>(() => ({\n placeholder: t('resources.resources.detail.activities.dictionary.placeholder', 'Select an activity type'),\n addLabel: t('resources.resources.detail.activities.dictionary.add', 'Add type'),\n addPrompt: t('resources.resources.detail.activities.dictionary.prompt', 'Name the type'),\n dialogTitle: t('resources.resources.detail.activities.dictionary.dialogTitle', 'Add activity type'),\n valueLabel: t('resources.resources.detail.activities.dictionary.valueLabel', 'Name'),\n valuePlaceholder: t('resources.resources.detail.activities.dictionary.valuePlaceholder', 'Name'),\n labelLabel: t('resources.resources.detail.activities.dictionary.labelLabel', 'Label'),\n labelPlaceholder: t('resources.resources.detail.activities.dictionary.labelPlaceholder', 'Display name shown in UI'),\n emptyError: t('resources.resources.detail.activities.dictionary.emptyError', 'Please enter a name'),\n cancelLabel: t('resources.resources.detail.activities.dictionary.cancel', 'Cancel'),\n saveLabel: t('resources.resources.detail.activities.dictionary.save', 'Save'),\n saveShortcutHint: t('resources.resources.detail.activities.dictionary.saveShortcut', '\u2318/Ctrl + Enter'),\n errorLoad: t('resources.resources.detail.activities.dictionary.errorLoad', 'Failed to load options'),\n errorSave: t('resources.resources.detail.activities.dictionary.errorSave', 'Failed to save option'),\n loadingLabel: t('resources.resources.detail.activities.dictionary.loading', 'Loading\u2026'),\n manageTitle: t('resources.resources.detail.activities.dictionary.manage', 'Manage dictionary'),\n }), [t])\n\n const loadActivityOptions = React.useCallback(async () => {\n const { dictionary, entries } = await loadResourceDictionary('activityTypes')\n setActivityDictionaryId(dictionary?.id ?? null)\n setActivityTypeEntries(entries)\n return entries\n }, [])\n\n const createActivityOption = React.useCallback(\n async (input: { value: string; label?: string; color?: string | null; icon?: string | null }) => {\n const entry = await createResourceDictionaryEntry('activityTypes', input)\n if (!entry) {\n throw new Error(t('resources.resources.detail.activities.dictionary.errorSave', 'Failed to save option'))\n }\n return entry\n },\n [t],\n )\n\n React.useEffect(() => {\n loadActivityOptions().catch(() => {})\n }, [loadActivityOptions])\n\n const activityTypeMap = React.useMemo(\n () => new Map(activityTypeEntries.map((entry) => [entry.value, entry])),\n [activityTypeEntries],\n )\n\n const resolveActivityPresentation = React.useCallback(\n (activity: { activityType: string; appearanceIcon?: string | null; appearanceColor?: string | null }) => {\n const entry = activityTypeMap.get(activity.activityType)\n return {\n label: entry?.label ?? activity.activityType,\n icon: entry?.icon ?? activity.appearanceIcon ?? null,\n color: entry?.color ?? activity.appearanceColor ?? null,\n }\n },\n [activityTypeMap],\n )\n\n const manageActivityHref = React.useMemo(() => {\n if (!activityDictionaryId) return '/backend/config/dictionaries'\n return `/backend/config/dictionaries?dictionaryId=${encodeURIComponent(activityDictionaryId)}`\n }, [activityDictionaryId])\n\n const appearanceLabels = React.useMemo(() => ({\n colorLabel: t('resources.resources.detail.activities.appearance.colorLabel', 'Color'),\n colorHelp: t('resources.resources.detail.activities.appearance.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('resources.resources.detail.activities.appearance.colorClear', 'Remove color'),\n iconLabel: t('resources.resources.detail.activities.appearance.iconLabel', 'Icon or emoji'),\n iconPlaceholder: t('resources.resources.detail.activities.appearance.iconPlaceholder', 'Type an emoji or pick one of the suggestions.'),\n iconPickerTriggerLabel: t('resources.resources.detail.activities.appearance.iconBrowse', 'Browse icons and emojis'),\n iconSearchPlaceholder: t('resources.resources.detail.activities.appearance.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('resources.resources.detail.activities.appearance.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('resources.resources.detail.activities.appearance.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('resources.resources.detail.activities.appearance.iconClear', 'Remove icon'),\n previewEmptyLabel: t('resources.resources.detail.activities.appearance.previewEmpty', 'No appearance selected'),\n }), [t])\n\n const renderCustomFields = React.useCallback((activity: { id?: string; customFields?: Array<{ key: string; label?: string | null; value: unknown }> }) => {\n const entries = Array.isArray(activity.customFields) ? activity.customFields : []\n if (!entries.length) return null\n const emptyLabel = t('resources.resources.detail.activities.customFields.empty', 'Not provided')\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {entries.map((entry, index) => {\n const label = entry.label ?? entry.key\n const value = entry.value\n const hasValue = !(value == null || value === '' || (Array.isArray(value) && value.length === 0))\n const content = hasValue\n ? Array.isArray(value)\n ? value.map((item) => String(item)).join(', ')\n : String(value)\n : emptyLabel\n return (\n <div\n key={`activity-${activity.id ?? 'row'}-custom-${index}`}\n className=\"rounded-md border border-border/70 bg-muted/30 px-3 py-2\"\n >\n <div className=\"text-xs font-medium text-muted-foreground\">{label}</div>\n <div className=\"mt-1 text-sm text-foreground\">{content}</div>\n </div>\n )\n })}\n </div>\n )\n }, [t])\n\n React.useEffect(() => {\n if (!searchParams) return\n const tabParam = searchParams.get('tab')\n if (tabParam === 'availability') {\n setActiveTab('availability')\n }\n const created = searchParams.get('created') === '1'\n if (created && !flashShownRef.current) {\n flashShownRef.current = true\n flash(t('resources.resources.flash.createdAvailability', 'Saved. You can now set availability.'), 'success')\n const nextParams = new URLSearchParams(searchParams.toString())\n nextParams.delete('created')\n const nextQuery = nextParams.toString()\n const nextPath = resourceId\n ? `/backend/resources/resources/${encodeURIComponent(resourceId)}${nextQuery ? `?${nextQuery}` : ''}`\n : `/backend/resources/resources${nextQuery ? `?${nextQuery}` : ''}`\n router.replace(nextPath)\n }\n }, [resourceId, router, searchParams, t])\n\n const buildScheduleItems = React.useCallback<AvailabilityScheduleItemBuilder>(\n ({ availabilityRules, translate }) => buildResourceScheduleItems({\n availabilityRules,\n isAvailableByDefault: false,\n translate,\n }),\n [],\n )\n\n const tagLabels = React.useMemo(\n () => ({\n loading: t('resources.resources.tags.loading', 'Loading tags...'),\n placeholder: t('resources.resources.tags.placeholder', 'Type to add tags'),\n empty: t('resources.resources.tags.placeholder', 'No tags yet. Add labels to keep resources organized.'),\n loadError: t('resources.resources.tags.loadError', 'Failed to load tags.'),\n createError: t('resources.resources.tags.createError', 'Failed to create tag.'),\n updateError: t('resources.resources.tags.updateError', 'Failed to update tags.'),\n labelRequired: t('resources.resources.tags.labelRequired', 'Tag name is required.'),\n saveShortcut: t('resources.resources.tags.saveShortcut', 'Save Cmd+Enter / Ctrl+Enter'),\n cancelShortcut: t('resources.resources.tags.cancelShortcut', 'Cancel (Esc)'),\n edit: t('ui.forms.actions.edit', 'Edit'),\n cancel: t('ui.forms.actions.cancel', 'Cancel'),\n success: t('resources.resources.tags.success', 'Tags updated.'),\n }),\n [t],\n )\n\n const handleSubmit = React.useCallback(async (values: Record<string, unknown>) => {\n if (!resourceId) return\n const appearance = values.appearance && typeof values.appearance === 'object'\n ? values.appearance as { icon?: string | null; color?: string | null }\n : {}\n const { appearance: _appearance, ...rest } = values\n const customFieldsetCode = typeof values.customFieldsetCode === 'string' && values.customFieldsetCode.trim().length\n ? values.customFieldsetCode.trim()\n : RESOURCES_RESOURCE_FIELDSET_DEFAULT\n const payload: Record<string, unknown> = {\n ...rest,\n id: resourceId,\n resourceTypeId: values.resourceTypeId || null,\n capacity: values.capacity ? Number(values.capacity) : null,\n capacityUnitValue: values.capacityUnitValue ? String(values.capacityUnitValue) : null,\n appearanceIcon: appearance.icon ?? null,\n appearanceColor: appearance.color ?? null,\n isActive: values.isActive ?? true,\n customFieldsetCode,\n ...collectCustomFieldValues(values),\n }\n if (!payload.name || String(payload.name).trim().length === 0) {\n throw createCrudFormError(t('resources.resources.form.errors.nameRequired', 'Name is required.'))\n }\n await updateCrud('resources/resources', payload, {\n errorMessage: t('resources.resources.form.errors.update', 'Failed to update resource.'),\n })\n flash(t('resources.resources.form.flash.updated', 'Resource updated.'), 'success')\n }, [resourceId, t])\n\n const tabs = React.useMemo(() => ([\n { id: 'details', label: t('resources.resources.tabs.details', 'Details') },\n { id: 'availability', label: t('resources.resources.tabs.availability', 'Availability') },\n ]), [t])\n const detailTabs = React.useMemo(() => ([\n { id: 'notes' as const, label: t('resources.resources.detail.tabs.notes', 'Notes') },\n { id: 'activities' as const, label: t('resources.resources.detail.tabs.activities', 'Activities') },\n ]), [t])\n\n const loadTagOptions = React.useCallback(\n async (query?: string): Promise<TagOption[]> => {\n const params = new URLSearchParams({ pageSize: '100' })\n if (query) params.set('search', query)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/resources/tags?${params.toString()}`,\n undefined,\n { errorMessage: t('resources.resources.tags.loadError', 'Failed to load tags.') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n return items\n .map((item: unknown): TagOption | null => {\n if (!item || typeof item !== 'object') return null\n const raw = item as { id?: unknown; tagId?: unknown; label?: unknown; slug?: unknown; color?: unknown }\n const rawId =\n typeof raw.id === 'string'\n ? raw.id\n : typeof raw.tagId === 'string'\n ? raw.tagId\n : null\n if (!rawId) return null\n const labelValue =\n (typeof raw.label === 'string' && raw.label.trim().length && raw.label.trim()) ||\n (typeof raw.slug === 'string' && raw.slug.trim().length && raw.slug.trim()) ||\n rawId\n const color = typeof raw.color === 'string' && raw.color.trim().length ? raw.color.trim() : null\n return { id: rawId, label: labelValue, color }\n })\n .filter((entry): entry is TagOption => entry !== null)\n },\n [t],\n )\n\n const createTag = React.useCallback(\n async (label: string): Promise<TagOption> => {\n const trimmed = label.trim()\n if (!trimmed.length) {\n throw new Error(t('resources.resources.tags.labelRequired', 'Tag name is required.'))\n }\n const response = await apiCallOrThrow<Record<string, unknown>>(\n '/api/resources/tags',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ label: trimmed }),\n },\n { errorMessage: t('resources.resources.tags.createError', 'Failed to create tag.') },\n )\n const payload = response.result ?? {}\n const id =\n typeof payload?.id === 'string'\n ? payload.id\n : typeof (payload as any)?.tagId === 'string'\n ? (payload as any).tagId\n : ''\n if (!id) throw new Error(t('resources.resources.tags.createError', 'Failed to create tag.'))\n const color = typeof (payload as any)?.color === 'string' && (payload as any).color.trim().length\n ? (payload as any).color.trim()\n : null\n return { id, label: trimmed, color }\n },\n [t],\n )\n\n const assignTag = React.useCallback(async (tagId: string) => {\n if (!resourceId) return\n await apiCallOrThrow(\n '/api/resources/resources/tags/assign',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tagId, resourceId }),\n },\n { errorMessage: t('resources.resources.tags.updateError', 'Failed to update tags.') },\n )\n }, [resourceId, t])\n\n const unassignTag = React.useCallback(async (tagId: string) => {\n if (!resourceId) return\n await apiCallOrThrow(\n '/api/resources/resources/tags/unassign',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tagId, resourceId }),\n },\n { errorMessage: t('resources.resources.tags.updateError', 'Failed to update tags.') },\n )\n }, [resourceId, t])\n\n const handleTagsSave = React.useCallback(\n async ({ next, added, removed }: { next: TagOption[]; added: TagOption[]; removed: TagOption[] }) => {\n if (!resourceId) return\n for (const tag of added) {\n await assignTag(tag.id)\n }\n for (const tag of removed) {\n await unassignTag(tag.id)\n }\n setTags(next)\n flash(t('resources.resources.tags.success', 'Tags updated.'), 'success')\n },\n [assignTag, resourceId, t, unassignTag],\n )\n\n const tagsSection = React.useMemo(\n () => ({\n title: t('resources.resources.tags.title', 'Tags'),\n tags,\n onChange: setTags,\n loadOptions: loadTagOptions,\n createTag,\n onSave: handleTagsSave,\n labels: tagLabels,\n }),\n [createTag, handleTagsSave, loadTagOptions, t, tagLabels, tags],\n )\n\n const formConfig = useResourcesResourceFormConfig({ tagsSection })\n const { resourceTypesLoaded, resolveFieldsetCode } = formConfig\n\n React.useEffect(() => {\n if (!resourceId || !resourceTypesLoaded) return\n setIsNotFound(false)\n let cancelled = false\n async function loadResource() {\n try {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '1')\n if (resourceId) params.set('ids', resourceId)\n const record = await readApiResultOrThrow<ResourceResponse>(`/api/resources/resources?${params.toString()}`)\n const resourceRaw = Array.isArray(record?.items) ? record.items[0] : null\n const resource = resourceRaw ? normalizeResourceRecord(resourceRaw) : null\n if (!resource) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n if (!cancelled) {\n const customValues = extractCustomFieldEntries(resource)\n setTags(Array.isArray(resource.tags) ? resource.tags : [])\n setAvailabilityRuleSetId(\n typeof resource.availabilityRuleSetId === 'string'\n ? resource.availabilityRuleSetId\n : typeof resource.availability_rule_set_id === 'string'\n ? resource.availability_rule_set_id\n : null,\n )\n setInitialValues({\n id: resource.id,\n name: resource.name,\n description: resource.description ?? '',\n resourceTypeId: resource.resourceTypeId || '',\n capacity: resource.capacity ?? '',\n capacityUnitValue: resource.capacityUnitValue ?? '',\n appearance: { icon: resource.appearanceIcon ?? null, color: resource.appearanceColor ?? null },\n isActive: resource.isActive ?? true,\n customFieldsetCode: resource.resourceTypeId\n ? resolveFieldsetCode(resource.resourceTypeId)\n : RESOURCES_RESOURCE_FIELDSET_DEFAULT,\n ...customValues,\n })\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : t('resources.resources.form.errors.load', 'Failed to load resource.')\n flash(message, 'error')\n }\n }\n loadResource()\n return () => { cancelled = true }\n }, [resourceId, resourceTypesLoaded, resolveFieldsetCode, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!resourceId) return\n await deleteCrud('resources/resources', resourceId, {\n errorMessage: t('resources.resources.form.errors.delete', 'Failed to delete resource.'),\n })\n flash(t('resources.resources.form.flash.deleted', 'Resource deleted.'), 'success')\n router.push('/backend/resources/resources')\n }, [resourceId, router, t])\n\n const handleRulesetChange = React.useCallback(async (nextId: string | null) => {\n if (!resourceId) return\n await updateCrud('resources/resources', { id: resourceId, availabilityRuleSetId: nextId }, {\n errorMessage: t('resources.resources.availability.ruleset.updateError', 'Failed to update schedule.'),\n })\n setAvailabilityRuleSetId(nextId)\n flash(t('resources.resources.availability.ruleset.updateSuccess', 'Schedule updated.'), 'success')\n }, [resourceId, t])\n\n const resourceTitle =\n typeof initialValues?.name === 'string' && initialValues.name.trim().length > 0\n ? initialValues.name.trim()\n : t('resources.resources.detail.untitled', 'Unnamed resource')\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('resources.resources.form.errors.notFound', 'Resource not found.')}\n backHref=\"/backend/resources/resources\"\n backLabel={t('resources.resources.detail.back', 'Back to resources')}\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/resources/resources\"\n backLabel={t('resources.resources.detail.back', 'Back to resources')}\n utilityActions={(\n <>\n {resourceId ? (\n <SendObjectMessageDialog\n object={{\n entityModule: 'resources',\n entityType: 'resource',\n entityId: resourceId,\n previewData: {\n title: resourceTitle,\n },\n }}\n viewHref={`/backend/resources/resources/${resourceId}`}\n />\n ) : null}\n <VersionHistoryAction\n config={resourceId ? { resourceKind: 'resources.resource', resourceId, includeRelated: true } : null}\n t={t}\n />\n </>\n )}\n title={resourceTitle}\n subtitle={t('resources.resources.detail.subtitle', 'Resource profile and activity')}\n />\n\n <div className=\"border-b\">\n <nav className=\"flex flex-wrap items-center gap-5 text-sm\" aria-label={t('resources.resources.tabs.label', 'Resource sections')}>\n {tabs.map((tab) => (\n <Button\n key={tab.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === tab.id}\n variant=\"ghost\"\n size=\"sm\"\n className={`relative -mb-px h-auto rounded-none border-b-2 px-0 py-2 font-medium ${\n activeTab === tab.id\n ? 'border-accent-indigo text-foreground'\n : 'border-transparent text-muted-foreground hover:text-foreground'\n }`}\n onClick={() => setActiveTab(tab.id as 'details' | 'availability')}\n >\n {tab.label}\n </Button>\n ))}\n </nav>\n </div>\n\n {activeTab === 'details' ? (\n <>\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex gap-2\">\n {detailTabs.map((tab) => (\n <Button\n key={tab.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={`relative -mb-px h-auto rounded-none border-b-2 px-0 py-1 font-medium ${\n activeDetailTab === tab.id\n ? 'border-accent-indigo text-foreground'\n : 'border-transparent text-muted-foreground hover:text-foreground'\n }`}\n onClick={() => setActiveDetailTab(tab.id)}\n >\n {tab.label}\n </Button>\n ))}\n </div>\n {sectionAction ? (\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={sectionAction.disabled}\n onClick={() => sectionAction.onClick()}\n >\n {sectionAction.icon ?? null}\n {sectionAction.label}\n </Button>\n ) : null}\n </div>\n {activeDetailTab === 'notes' ? (\n <NotesSection\n entityId={resourceId ?? null}\n emptyLabel={t('resources.resources.detail.notes.empty', 'No notes yet.')}\n viewerUserId={null}\n viewerName={null}\n viewerEmail={null}\n addActionLabel={t('resources.resources.detail.notes.add', 'Add note')}\n emptyState={{\n title: t('resources.resources.detail.notes.emptyTitle', 'Keep everyone in the loop'),\n actionLabel: t('resources.resources.detail.notes.emptyAction', 'Add a note'),\n }}\n onActionChange={setSectionAction}\n translator={detailTranslator}\n labelPrefix=\"resources.resources.detail.notes\"\n inlineLabelPrefix=\"resources.resources.detail.inline\"\n dataAdapter={notesAdapter}\n renderIcon={renderDictionaryIcon}\n renderColor={renderDictionaryColor}\n iconSuggestions={ICON_SUGGESTIONS}\n />\n ) : null}\n {activeDetailTab === 'activities' ? (\n <ActivitiesSection\n entityId={resourceId ?? null}\n addActionLabel={t('resources.resources.detail.activities.add', 'Log activity')}\n emptyState={{\n title: t('resources.resources.detail.activities.emptyTitle', 'No activities yet'),\n actionLabel: t('resources.resources.detail.activities.emptyAction', 'Add an activity'),\n }}\n onActionChange={setSectionAction}\n dataAdapter={activitiesAdapter}\n activityTypeLabels={activityTypeLabels}\n loadActivityOptions={loadActivityOptions}\n createActivityOption={createActivityOption}\n resolveActivityPresentation={resolveActivityPresentation}\n renderCustomFields={renderCustomFields}\n labelPrefix=\"resources.resources.detail.activities\"\n renderIcon={renderDictionaryIcon}\n renderColor={renderDictionaryColor}\n appearanceLabels={appearanceLabels}\n manageHref={manageActivityHref}\n customFieldEntityIds={['resources:resources_resource_activity']}\n />\n ) : null}\n </div>\n\n <div className=\"rounded-lg border bg-card p-4\">\n <h2 className=\"mb-4 text-sm font-semibold uppercase text-muted-foreground\">\n {t('resources.resources.detail.formTitle', 'Resource settings')}\n </h2>\n <ResourcesResourceForm\n embedded\n title={t('resources.resources.form.editTitle', 'Edit resource')}\n backHref=\"/backend/resources/resources\"\n cancelHref=\"/backend/resources/resources\"\n successRedirect=\"/backend/resources/resources\"\n formConfig={formConfig}\n initialValues={initialValues ?? undefined}\n onSubmit={handleSubmit}\n onDelete={handleDelete}\n isLoading={!initialValues}\n loadingMessage={t('resources.resources.form.loading', 'Loading resource...')}\n />\n </div>\n </>\n ) : (\n <AvailabilityRulesEditor\n subjectType=\"resource\"\n subjectId={resourceId ?? ''}\n labelPrefix=\"resources.resources\"\n mode={availabilityMode}\n rulesetId={availabilityRuleSetId}\n onRulesetChange={handleRulesetChange}\n buildScheduleItems={buildScheduleItems}\n />\n )}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["params"]
|
|
7
7
|
}
|
|
@@ -52,7 +52,8 @@ async function POST(req) {
|
|
|
52
52
|
const em = container.resolve("em").fork();
|
|
53
53
|
const hashedToken = hashAuthToken(token);
|
|
54
54
|
const tenantScope = auth?.tenantId ? { tenantId: auth.tenantId } : void 0;
|
|
55
|
-
const
|
|
55
|
+
const commandBus = container.resolve("commandBus");
|
|
56
|
+
const { quote, orderId } = await em.transactional(async (trx) => {
|
|
56
57
|
const findQuoteByToken = (acceptanceToken) => findOneWithDecryption(
|
|
57
58
|
trx,
|
|
58
59
|
SalesQuote,
|
|
@@ -86,43 +87,19 @@ async function POST(req) {
|
|
|
86
87
|
quote2.updatedAt = now;
|
|
87
88
|
trx.persist(quote2);
|
|
88
89
|
await trx.flush();
|
|
89
|
-
|
|
90
|
+
const ctx = {
|
|
91
|
+
container,
|
|
92
|
+
auth: null,
|
|
93
|
+
organizationScope: null,
|
|
94
|
+
selectedOrganizationId: quote2.organizationId,
|
|
95
|
+
organizationIds: [quote2.organizationId],
|
|
96
|
+
request: req,
|
|
97
|
+
transactionalEm: trx
|
|
98
|
+
};
|
|
99
|
+
const result = await commandBus.execute("sales.quotes.convert_to_order", { input: { quoteId: quote2.id }, ctx });
|
|
100
|
+
const orderId2 = result?.result?.orderId ?? result?.orderId ?? quote2.id;
|
|
101
|
+
return { quote: quote2, orderId: orderId2 };
|
|
90
102
|
});
|
|
91
|
-
const commandBus = container.resolve("commandBus");
|
|
92
|
-
const ctx = {
|
|
93
|
-
container,
|
|
94
|
-
auth: null,
|
|
95
|
-
organizationScope: null,
|
|
96
|
-
selectedOrganizationId: quote.organizationId,
|
|
97
|
-
organizationIds: [quote.organizationId],
|
|
98
|
-
request: req
|
|
99
|
-
};
|
|
100
|
-
let result;
|
|
101
|
-
try {
|
|
102
|
-
result = await commandBus.execute("sales.quotes.convert_to_order", { input: { quoteId: quote.id }, ctx });
|
|
103
|
-
} catch (conversionError) {
|
|
104
|
-
const freshEm = container.resolve("em").fork();
|
|
105
|
-
const staleQuote = await findOneWithDecryption(
|
|
106
|
-
freshEm,
|
|
107
|
-
SalesQuote,
|
|
108
|
-
{ id: quote.id, deletedAt: null },
|
|
109
|
-
{},
|
|
110
|
-
tenantScope
|
|
111
|
-
);
|
|
112
|
-
if (staleQuote) {
|
|
113
|
-
staleQuote.status = "sent";
|
|
114
|
-
staleQuote.statusEntryId = await resolveStatusEntryIdByValue(freshEm, {
|
|
115
|
-
tenantId: staleQuote.tenantId,
|
|
116
|
-
organizationId: staleQuote.organizationId,
|
|
117
|
-
value: "sent"
|
|
118
|
-
});
|
|
119
|
-
staleQuote.updatedAt = /* @__PURE__ */ new Date();
|
|
120
|
-
freshEm.persist(staleQuote);
|
|
121
|
-
await freshEm.flush();
|
|
122
|
-
}
|
|
123
|
-
throw conversionError;
|
|
124
|
-
}
|
|
125
|
-
const orderId = result?.result?.orderId ?? result?.orderId ?? quote.id;
|
|
126
103
|
const order = await findOneWithDecryption(em, SalesOrder, { id: orderId, deletedAt: null }, {}, tenantScope);
|
|
127
104
|
const orderNumber = order?.orderNumber ?? orderId;
|
|
128
105
|
const adminEmail = process.env.ADMIN_EMAIL || "";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/sales/api/quotes/accept/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { LockMode } from '@mikro-orm/core'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { getCachedRateLimiterService } from '@open-mercato/core/bootstrap'\nimport { readEndpointRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'\nimport { checkRateLimit, getClientIp, rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { validateSameOriginMutationRequest } from './originGuard'\nimport { hashAuthToken } from '../../../../auth/lib/tokenHash'\nimport { SalesOrder, SalesQuote } from '../../../data/entities'\nimport { quoteAcceptSchema } from '../../../data/validators'\nimport { sendEmail } from '@open-mercato/shared/lib/email/send'\nimport { resolveStatusEntryIdByValue } from '../../../lib/statusHelpers'\nimport { QuoteAcceptedAdminEmail } from '../../../emails/QuoteAcceptedAdminEmail'\n\ntype ConvertToOrderResult = {\n result?: { orderId?: string } | null\n orderId?: string\n}\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nconst quoteAcceptRateLimitConfig = readEndpointRateLimitConfig('SALES_QUOTES_ACCEPT', {\n points: 10,\n duration: 60,\n blockDuration: 300,\n keyPrefix: 'sales_quote_accept',\n})\n\nexport async function POST(req: Request) {\n try {\n const { translate } = await resolveTranslations()\n const sameOriginViolation = validateSameOriginMutationRequest(req)\n if (sameOriginViolation) {\n return NextResponse.json(\n { error: translate('sales.quotes.accept.forbidden', 'Cross-site quote acceptance is not allowed.') },\n { status: 403 },\n )\n }\n\n const rateLimiterService = getCachedRateLimiterService()\n const clientIp = rateLimiterService ? getClientIp(req, rateLimiterService.trustProxyDepth) : null\n if (rateLimiterService && clientIp) {\n const rateLimitResponse = await checkRateLimit(\n rateLimiterService,\n quoteAcceptRateLimitConfig,\n clientIp,\n translate('api.errors.rateLimit', 'Too many requests. Please try again later.'),\n )\n if (rateLimitResponse) return rateLimitResponse\n }\n\n const { token } = quoteAcceptSchema.parse(await req.json().catch(() => ({})))\n const auth = await getAuthFromRequest(req)\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager).fork()\n\n const hashedToken = hashAuthToken(token)\n const tenantScope = auth?.tenantId ? { tenantId: auth.tenantId } : undefined\n\n const quote = await em.transactional(async (trx) => {\n const findQuoteByToken = (acceptanceToken: string) =>\n findOneWithDecryption(\n trx,\n SalesQuote,\n {\n acceptanceToken,\n ...(auth?.tenantId ? { tenantId: auth.tenantId } : {}),\n deletedAt: null,\n },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n tenantScope,\n )\n const quote = (await findQuoteByToken(hashedToken)) ?? (await findQuoteByToken(token))\n if (!quote) {\n throw new CrudHttpError(404, { error: translate('sales.quotes.accept.notFound', 'Quote not found.') })\n }\n\n const now = new Date()\n if (quote.validUntil && quote.validUntil.getTime() < now.getTime()) {\n throw new CrudHttpError(400, { error: translate('sales.quotes.accept.expired', 'This quote has expired.') })\n }\n\n if ((quote.status ?? null) !== 'sent') {\n throw new CrudHttpError(400, {\n error: translate('sales.quotes.accept.invalidStatus', 'This quote cannot be accepted in its current status.'),\n })\n }\n\n quote.status = 'confirmed'\n quote.statusEntryId = await resolveStatusEntryIdByValue(trx, {\n tenantId: quote.tenantId,\n organizationId: quote.organizationId,\n value: 'confirmed',\n })\n quote.updatedAt = now\n trx.persist(quote)\n await trx.flush()\n\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AAEvC,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAG/C,SAAS,gBAAgB;AACzB,SAAS,0BAA0B;AACnC,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAC5C,SAAS,mCAAmC;AAC5C,SAAS,gBAAgB,aAAa,4BAA4B;AAClE,SAAS,yCAAyC;AAClD,SAAS,qBAAqB;AAC9B,SAAS,YAAY,kBAAkB;AACvC,SAAS,yBAAyB;AAClC,SAAS,iBAAiB;AAC1B,SAAS,mCAAmC;AAC5C,SAAS,+BAA+B;AAOjC,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,MAAM,6BAA6B,4BAA4B,uBAAuB;AAAA,EACpF,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;AAED,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,sBAAsB,kCAAkC,GAAG;AACjE,QAAI,qBAAqB;AACvB,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,UAAU,iCAAiC,6CAA6C,EAAE;AAAA,QACnG,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,qBAAqB,4BAA4B;AACvD,UAAM,WAAW,qBAAqB,YAAY,KAAK,mBAAmB,eAAe,IAAI;AAC7F,QAAI,sBAAsB,UAAU;AAClC,YAAM,oBAAoB,MAAM;AAAA,QAC9B;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,wBAAwB,4CAA4C;AAAA,MAChF;AACA,UAAI,kBAAmB,QAAO;AAAA,IAChC;AAEA,UAAM,EAAE,MAAM,IAAI,kBAAkB,MAAM,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC;AAC5E,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAE3D,UAAM,cAAc,cAAc,KAAK;AACvC,UAAM,cAAc,MAAM,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI;AAEnE,UAAM,QAAQ,MAAM,GAAG,cAAc,OAAO,QAAQ;
|
|
6
|
-
"names": ["quote"]
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { LockMode } from '@mikro-orm/core'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { getCachedRateLimiterService } from '@open-mercato/core/bootstrap'\nimport { readEndpointRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'\nimport { checkRateLimit, getClientIp, rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { validateSameOriginMutationRequest } from './originGuard'\nimport { hashAuthToken } from '../../../../auth/lib/tokenHash'\nimport { SalesOrder, SalesQuote } from '../../../data/entities'\nimport { quoteAcceptSchema } from '../../../data/validators'\nimport { sendEmail } from '@open-mercato/shared/lib/email/send'\nimport { resolveStatusEntryIdByValue } from '../../../lib/statusHelpers'\nimport { QuoteAcceptedAdminEmail } from '../../../emails/QuoteAcceptedAdminEmail'\n\ntype ConvertToOrderResult = {\n result?: { orderId?: string } | null\n orderId?: string\n}\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nconst quoteAcceptRateLimitConfig = readEndpointRateLimitConfig('SALES_QUOTES_ACCEPT', {\n points: 10,\n duration: 60,\n blockDuration: 300,\n keyPrefix: 'sales_quote_accept',\n})\n\nexport async function POST(req: Request) {\n try {\n const { translate } = await resolveTranslations()\n const sameOriginViolation = validateSameOriginMutationRequest(req)\n if (sameOriginViolation) {\n return NextResponse.json(\n { error: translate('sales.quotes.accept.forbidden', 'Cross-site quote acceptance is not allowed.') },\n { status: 403 },\n )\n }\n\n const rateLimiterService = getCachedRateLimiterService()\n const clientIp = rateLimiterService ? getClientIp(req, rateLimiterService.trustProxyDepth) : null\n if (rateLimiterService && clientIp) {\n const rateLimitResponse = await checkRateLimit(\n rateLimiterService,\n quoteAcceptRateLimitConfig,\n clientIp,\n translate('api.errors.rateLimit', 'Too many requests. Please try again later.'),\n )\n if (rateLimitResponse) return rateLimitResponse\n }\n\n const { token } = quoteAcceptSchema.parse(await req.json().catch(() => ({})))\n const auth = await getAuthFromRequest(req)\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager).fork()\n\n const hashedToken = hashAuthToken(token)\n const tenantScope = auth?.tenantId ? { tenantId: auth.tenantId } : undefined\n\n const commandBus = container.resolve('commandBus') as CommandBus\n\n // Lock the quote, flip it to confirmed, and convert it to an order inside a\n // single transaction. The conversion command reuses this transaction (and its\n // PESSIMISTIC_WRITE lock) via ctx.transactionalEm, so the status flip and the\n // order creation are atomic: if conversion fails the whole transaction rolls\n // back, leaving the quote in its prior 'sent' state with no partial order and\n // no need for an out-of-band compensating write.\n const { quote, orderId } = await em.transactional(async (trx) => {\n const findQuoteByToken = (acceptanceToken: string) =>\n findOneWithDecryption(\n trx,\n SalesQuote,\n {\n acceptanceToken,\n ...(auth?.tenantId ? { tenantId: auth.tenantId } : {}),\n deletedAt: null,\n },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n tenantScope,\n )\n const quote = (await findQuoteByToken(hashedToken)) ?? (await findQuoteByToken(token))\n if (!quote) {\n throw new CrudHttpError(404, { error: translate('sales.quotes.accept.notFound', 'Quote not found.') })\n }\n\n const now = new Date()\n if (quote.validUntil && quote.validUntil.getTime() < now.getTime()) {\n throw new CrudHttpError(400, { error: translate('sales.quotes.accept.expired', 'This quote has expired.') })\n }\n\n if ((quote.status ?? null) !== 'sent') {\n throw new CrudHttpError(400, {\n error: translate('sales.quotes.accept.invalidStatus', 'This quote cannot be accepted in its current status.'),\n })\n }\n\n quote.status = 'confirmed'\n quote.statusEntryId = await resolveStatusEntryIdByValue(trx, {\n tenantId: quote.tenantId,\n organizationId: quote.organizationId,\n value: 'confirmed',\n })\n quote.updatedAt = now\n trx.persist(quote)\n await trx.flush()\n\n const ctx: CommandRuntimeContext = {\n container,\n auth: null,\n organizationScope: null,\n selectedOrganizationId: quote.organizationId,\n organizationIds: [quote.organizationId],\n request: req,\n transactionalEm: trx,\n }\n\n const result = (await commandBus.execute('sales.quotes.convert_to_order', { input: { quoteId: quote.id }, ctx })) as ConvertToOrderResult | null\n const orderId = result?.result?.orderId ?? result?.orderId ?? quote.id\n\n return { quote, orderId }\n })\n\n const order = await findOneWithDecryption(em, SalesOrder, { id: orderId, deletedAt: null }, {}, tenantScope)\n const orderNumber = order?.orderNumber ?? orderId\n\n // Admin notification should not block acceptance.\n const adminEmail = process.env.ADMIN_EMAIL || ''\n if (adminEmail) {\n try {\n const appUrl = process.env.APP_URL || ''\n const orderUrl = appUrl ? `${appUrl.replace(/\\/$/, '')}/backend/sales/orders/${orderId}` : `/backend/sales/orders/${orderId}`\n\n const copy = {\n preview: translate('sales.quotes.accept.adminEmail.preview', 'Quote {quoteNumber} accepted', { quoteNumber: quote.quoteNumber }),\n heading: translate('sales.quotes.accept.adminEmail.heading', 'Quote {quoteNumber} accepted', { quoteNumber: quote.quoteNumber }),\n body: translate('sales.quotes.accept.adminEmail.body', 'The customer accepted quote {quoteNumber}. An order has been created: {orderNumber}.', {\n quoteNumber: quote.quoteNumber,\n orderNumber,\n }),\n cta: translate('sales.quotes.accept.adminEmail.cta', 'View order'),\n footer: translate('sales.quotes.accept.adminEmail.footer', 'Open Mercato'),\n }\n\n await sendEmail({\n to: adminEmail,\n subject: translate('sales.quotes.accept.adminSubject', 'Quote {quoteNumber} accepted \u2192 Order {orderNumber}', {\n quoteNumber: quote.quoteNumber,\n orderNumber,\n }),\n react: QuoteAcceptedAdminEmail({ orderUrl, copy }),\n })\n } catch (err) {\n console.error('sales.quotes.accept.adminEmail failed', err)\n }\n }\n\n return NextResponse.json({ orderId, orderNumber })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('sales.quotes.accept failed', err)\n return NextResponse.json({ error: translate('sales.quotes.accept.failed', 'Failed to accept quote.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Accept a quote (public)',\n methods: {\n POST: {\n summary: 'Accept quote and convert to order',\n requestBody: {\n contentType: 'application/json',\n schema: quoteAcceptSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Quote accepted and order created',\n schema: z.object({ orderId: z.string().uuid(), orderNumber: z.string() }),\n },\n { status: 400, description: 'Invalid or expired quote', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Cross-site request rejected', schema: z.object({ error: z.string() }) },\n { status: 404, description: 'Quote not found', schema: z.object({ error: z.string() }) },\n { status: 429, description: 'Too many requests', schema: rateLimitErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AAEvC,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAG/C,SAAS,gBAAgB;AACzB,SAAS,0BAA0B;AACnC,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAC5C,SAAS,mCAAmC;AAC5C,SAAS,gBAAgB,aAAa,4BAA4B;AAClE,SAAS,yCAAyC;AAClD,SAAS,qBAAqB;AAC9B,SAAS,YAAY,kBAAkB;AACvC,SAAS,yBAAyB;AAClC,SAAS,iBAAiB;AAC1B,SAAS,mCAAmC;AAC5C,SAAS,+BAA+B;AAOjC,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM;AAC7B;AAEA,MAAM,6BAA6B,4BAA4B,uBAAuB;AAAA,EACpF,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,eAAe;AAAA,EACf,WAAW;AACb,CAAC;AAED,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,sBAAsB,kCAAkC,GAAG;AACjE,QAAI,qBAAqB;AACvB,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,UAAU,iCAAiC,6CAA6C,EAAE;AAAA,QACnG,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,qBAAqB,4BAA4B;AACvD,UAAM,WAAW,qBAAqB,YAAY,KAAK,mBAAmB,eAAe,IAAI;AAC7F,QAAI,sBAAsB,UAAU;AAClC,YAAM,oBAAoB,MAAM;AAAA,QAC9B;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,wBAAwB,4CAA4C;AAAA,MAChF;AACA,UAAI,kBAAmB,QAAO;AAAA,IAChC;AAEA,UAAM,EAAE,MAAM,IAAI,kBAAkB,MAAM,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC;AAC5E,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAE3D,UAAM,cAAc,cAAc,KAAK;AACvC,UAAM,cAAc,MAAM,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI;AAEnE,UAAM,aAAa,UAAU,QAAQ,YAAY;AAQjD,UAAM,EAAE,OAAO,QAAQ,IAAI,MAAM,GAAG,cAAc,OAAO,QAAQ;AAC/D,YAAM,mBAAmB,CAAC,oBACxB;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,UACE;AAAA,UACA,GAAI,MAAM,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,UACpD,WAAW;AAAA,QACb;AAAA,QACA,EAAE,UAAU,SAAS,kBAAkB;AAAA,QACvC;AAAA,MACF;AACF,YAAMA,SAAS,MAAM,iBAAiB,WAAW,KAAO,MAAM,iBAAiB,KAAK;AACpF,UAAI,CAACA,QAAO;AACV,cAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,gCAAgC,kBAAkB,EAAE,CAAC;AAAA,MACvG;AAEA,YAAM,MAAM,oBAAI,KAAK;AACrB,UAAIA,OAAM,cAAcA,OAAM,WAAW,QAAQ,IAAI,IAAI,QAAQ,GAAG;AAClE,cAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,+BAA+B,yBAAyB,EAAE,CAAC;AAAA,MAC7G;AAEA,WAAKA,OAAM,UAAU,UAAU,QAAQ;AACrC,cAAM,IAAI,cAAc,KAAK;AAAA,UAC3B,OAAO,UAAU,qCAAqC,sDAAsD;AAAA,QAC9G,CAAC;AAAA,MACH;AAEA,MAAAA,OAAM,SAAS;AACf,MAAAA,OAAM,gBAAgB,MAAM,4BAA4B,KAAK;AAAA,QAC3D,UAAUA,OAAM;AAAA,QAChB,gBAAgBA,OAAM;AAAA,QACtB,OAAO;AAAA,MACT,CAAC;AACD,MAAAA,OAAM,YAAY;AAClB,UAAI,QAAQA,MAAK;AACjB,YAAM,IAAI,MAAM;AAEhB,YAAM,MAA6B;AAAA,QACjC;AAAA,QACA,MAAM;AAAA,QACN,mBAAmB;AAAA,QACnB,wBAAwBA,OAAM;AAAA,QAC9B,iBAAiB,CAACA,OAAM,cAAc;AAAA,QACtC,SAAS;AAAA,QACT,iBAAiB;AAAA,MACnB;AAEA,YAAM,SAAU,MAAM,WAAW,QAAQ,iCAAiC,EAAE,OAAO,EAAE,SAASA,OAAM,GAAG,GAAG,IAAI,CAAC;AAC/G,YAAMC,WAAU,QAAQ,QAAQ,WAAW,QAAQ,WAAWD,OAAM;AAEpE,aAAO,EAAE,OAAAA,QAAO,SAAAC,SAAQ;AAAA,IAC1B,CAAC;AAED,UAAM,QAAQ,MAAM,sBAAsB,IAAI,YAAY,EAAE,IAAI,SAAS,WAAW,KAAK,GAAG,CAAC,GAAG,WAAW;AAC3G,UAAM,cAAc,OAAO,eAAe;AAG1C,UAAM,aAAa,QAAQ,IAAI,eAAe;AAC9C,QAAI,YAAY;AACd,UAAI;AACF,cAAM,SAAS,QAAQ,IAAI,WAAW;AACtC,cAAM,WAAW,SAAS,GAAG,OAAO,QAAQ,OAAO,EAAE,CAAC,yBAAyB,OAAO,KAAK,yBAAyB,OAAO;AAE3H,cAAM,OAAO;AAAA,UACX,SAAS,UAAU,0CAA0C,gCAAgC,EAAE,aAAa,MAAM,YAAY,CAAC;AAAA,UAC/H,SAAS,UAAU,0CAA0C,gCAAgC,EAAE,aAAa,MAAM,YAAY,CAAC;AAAA,UAC/H,MAAM,UAAU,uCAAuC,wFAAwF;AAAA,YAC7I,aAAa,MAAM;AAAA,YACnB;AAAA,UACF,CAAC;AAAA,UACD,KAAK,UAAU,sCAAsC,YAAY;AAAA,UACjE,QAAQ,UAAU,yCAAyC,cAAc;AAAA,QAC3E;AAEA,cAAM,UAAU;AAAA,UACd,IAAI;AAAA,UACJ,SAAS,UAAU,oCAAoC,2DAAsD;AAAA,YAC3G,aAAa,MAAM;AAAA,YACnB;AAAA,UACF,CAAC;AAAA,UACD,OAAO,wBAAwB,EAAE,UAAU,KAAK,CAAC;AAAA,QACnD,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,gBAAQ,MAAM,yCAAyC,GAAG;AAAA,MAC5D;AAAA,IACF;AAEA,WAAO,aAAa,KAAK,EAAE,SAAS,YAAY,CAAC;AAAA,EACnD,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,YAAQ,MAAM,8BAA8B,GAAG;AAC/C,WAAO,aAAa,KAAK,EAAE,OAAO,UAAU,8BAA8B,yBAAyB,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzH;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,aAAa,EAAE,OAAO,EAAE,CAAC;AAAA,QAC1E;AAAA,QACA,EAAE,QAAQ,KAAK,aAAa,4BAA4B,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QAChG,EAAE,QAAQ,KAAK,aAAa,+BAA+B,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACnG,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACvF,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": ["quote", "orderId"]
|
|
7
7
|
}
|
|
@@ -93,7 +93,7 @@ function EditChannelPage({ params }) {
|
|
|
93
93
|
"button",
|
|
94
94
|
{
|
|
95
95
|
type: "button",
|
|
96
|
-
className: `px-4 py-2 text-sm font-medium border-b-2 ${activeTab === value ? "border-
|
|
96
|
+
className: `px-4 py-2 text-sm font-medium border-b-2 ${activeTab === value ? "border-accent-indigo text-foreground" : "border-transparent text-muted-foreground"}`,
|
|
97
97
|
onClick: () => handleTabSelect(value),
|
|
98
98
|
children: label
|
|
99
99
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/sales/backend/sales/channels/%5BchannelId%5D/edit/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { useChannelFields, buildChannelPayload, type ChannelFormValues } from '@open-mercato/core/modules/sales/components/channels/channelFormFields'\nimport { E } from '#generated/entities.ids.generated'\nimport { SalesChannelOffersPanel } from '@open-mercato/core/modules/sales/components/channels/SalesChannelOffersPanel'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\n\ntype ChannelApiResponse = {\n items?: Array<Record<string, unknown>>\n}\n\nexport default function EditChannelPage({ params }: { params?: { channelId?: string } }) {\n const channelId = params?.channelId ?? ''\n const router = useRouter()\n const searchParams = useSearchParams()\n const t = useT()\n const { fields, groups } = useChannelFields()\n const [initialValues, setInitialValues] = React.useState<ChannelFormValues | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [activeTab, setActiveTab] = React.useState<'settings' | 'offers'>('settings')\n\n React.useEffect(() => {\n const tabParam = (searchParams?.get('tab') ?? '').toLowerCase()\n if (tabParam === 'offers') {\n setActiveTab('offers')\n } else if (tabParam === 'settings') {\n setActiveTab('settings')\n }\n }, [searchParams])\n\n React.useEffect(() => {\n if (!channelId) return\n let cancelled = false\n async function loadChannel() {\n setLoading(true)\n setError(null)\n try {\n const payload = await readApiResultOrThrow<ChannelApiResponse>(\n `/api/sales/channels?id=${encodeURIComponent(channelId)}&pageSize=1`,\n undefined,\n { errorMessage: t('sales.channels.form.errors.load', 'Failed to load channel.') },\n )\n const item = Array.isArray(payload.items) ? payload.items[0] : null\n if (!item) {\n throw new Error(t('sales.channels.form.errors.notFound', 'Channel not found.'))\n }\n if (!cancelled) {\n setInitialValues(mapChannelToFormValues(item))\n }\n } catch (err) {\n console.error('sales.channels.load', err)\n if (!cancelled) setError(t('sales.channels.form.errors.load', 'Failed to load channel.'))\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n void loadChannel()\n return () => { cancelled = true }\n }, [channelId, t])\n\n const handleSubmit = React.useCallback(async (values: ChannelFormValues) => {\n if (!channelId) return\n const payload: Record<string, unknown> = { id: channelId, ...buildChannelPayload(values) }\n const customFields = collectCustomFieldValues(values)\n if (Object.keys(customFields).length) payload.customFields = customFields\n await updateCrud('sales/channels', payload, {\n errorMessage: t('sales.channels.form.errors.update', 'Failed to save channel.'),\n })\n flash(t('sales.channels.form.messages.updated', 'Channel updated.'), 'success')\n router.push('/backend/sales/channels')\n }, [channelId, router, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!channelId) return\n await deleteCrud('sales/channels', channelId, {\n errorMessage: t('sales.channels.form.errors.delete', 'Failed to delete channel.'),\n })\n flash(t('sales.channels.form.messages.deleted', 'Channel deleted.'), 'success')\n router.push('/backend/sales/channels')\n }, [channelId, router, t])\n\n const handleTabSelect = React.useCallback((value: 'settings' | 'offers') => {\n setActiveTab(value)\n if (!channelId) return\n const basePath = `/backend/sales/channels/${channelId}/edit`\n const nextUrl = value === 'offers' ? `${basePath}?tab=offers` : basePath\n router.replace(nextUrl)\n }, [channelId, router])\n\n const tabButton = React.useCallback((value: 'settings' | 'offers', label: string) => (\n <button\n key={value}\n type=\"button\"\n className={`px-4 py-2 text-sm font-medium border-b-2 ${activeTab === value ? 'border-
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { useChannelFields, buildChannelPayload, type ChannelFormValues } from '@open-mercato/core/modules/sales/components/channels/channelFormFields'\nimport { E } from '#generated/entities.ids.generated'\nimport { SalesChannelOffersPanel } from '@open-mercato/core/modules/sales/components/channels/SalesChannelOffersPanel'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\n\ntype ChannelApiResponse = {\n items?: Array<Record<string, unknown>>\n}\n\nexport default function EditChannelPage({ params }: { params?: { channelId?: string } }) {\n const channelId = params?.channelId ?? ''\n const router = useRouter()\n const searchParams = useSearchParams()\n const t = useT()\n const { fields, groups } = useChannelFields()\n const [initialValues, setInitialValues] = React.useState<ChannelFormValues | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [activeTab, setActiveTab] = React.useState<'settings' | 'offers'>('settings')\n\n React.useEffect(() => {\n const tabParam = (searchParams?.get('tab') ?? '').toLowerCase()\n if (tabParam === 'offers') {\n setActiveTab('offers')\n } else if (tabParam === 'settings') {\n setActiveTab('settings')\n }\n }, [searchParams])\n\n React.useEffect(() => {\n if (!channelId) return\n let cancelled = false\n async function loadChannel() {\n setLoading(true)\n setError(null)\n try {\n const payload = await readApiResultOrThrow<ChannelApiResponse>(\n `/api/sales/channels?id=${encodeURIComponent(channelId)}&pageSize=1`,\n undefined,\n { errorMessage: t('sales.channels.form.errors.load', 'Failed to load channel.') },\n )\n const item = Array.isArray(payload.items) ? payload.items[0] : null\n if (!item) {\n throw new Error(t('sales.channels.form.errors.notFound', 'Channel not found.'))\n }\n if (!cancelled) {\n setInitialValues(mapChannelToFormValues(item))\n }\n } catch (err) {\n console.error('sales.channels.load', err)\n if (!cancelled) setError(t('sales.channels.form.errors.load', 'Failed to load channel.'))\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n void loadChannel()\n return () => { cancelled = true }\n }, [channelId, t])\n\n const handleSubmit = React.useCallback(async (values: ChannelFormValues) => {\n if (!channelId) return\n const payload: Record<string, unknown> = { id: channelId, ...buildChannelPayload(values) }\n const customFields = collectCustomFieldValues(values)\n if (Object.keys(customFields).length) payload.customFields = customFields\n await updateCrud('sales/channels', payload, {\n errorMessage: t('sales.channels.form.errors.update', 'Failed to save channel.'),\n })\n flash(t('sales.channels.form.messages.updated', 'Channel updated.'), 'success')\n router.push('/backend/sales/channels')\n }, [channelId, router, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!channelId) return\n await deleteCrud('sales/channels', channelId, {\n errorMessage: t('sales.channels.form.errors.delete', 'Failed to delete channel.'),\n })\n flash(t('sales.channels.form.messages.deleted', 'Channel deleted.'), 'success')\n router.push('/backend/sales/channels')\n }, [channelId, router, t])\n\n const handleTabSelect = React.useCallback((value: 'settings' | 'offers') => {\n setActiveTab(value)\n if (!channelId) return\n const basePath = `/backend/sales/channels/${channelId}/edit`\n const nextUrl = value === 'offers' ? `${basePath}?tab=offers` : basePath\n router.replace(nextUrl)\n }, [channelId, router])\n\n const tabButton = React.useCallback((value: 'settings' | 'offers', label: string) => (\n <button\n key={value}\n type=\"button\"\n className={`px-4 py-2 text-sm font-medium border-b-2 ${activeTab === value ? 'border-accent-indigo text-foreground' : 'border-transparent text-muted-foreground'}`}\n onClick={() => handleTabSelect(value)}\n >\n {label}\n </button>\n ), [activeTab, handleTabSelect])\n\n const renderTabs = React.useCallback(() => (\n <div className=\"flex items-center gap-2 border-b mb-6\">\n {tabButton('settings', t('sales.channels.form.tabs.settings', 'Settings'))}\n {tabButton('offers', t('sales.channels.form.tabs.offers', 'Offers'))}\n </div>\n ), [tabButton, t])\n\n return (\n <Page>\n <PageBody>\n {error ? (\n <div className=\"mb-4 rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n {activeTab === 'settings' ? (\n <CrudForm<ChannelFormValues>\n title={t('sales.channels.form.editTitle', 'Edit channel')}\n versionHistory={{ resourceKind: 'sales.channel', resourceId: channelId ? String(channelId) : '' }}\n extraActions={channelId ? (\n <SendObjectMessageDialog\n object={{\n entityModule: 'sales',\n entityType: 'channel',\n entityId: channelId,\n previewData: {\n title: initialValues?.name ?? '',\n metadata: {\n [t('sales.channels.form.contactEmail')]: initialValues?.contactEmail ?? '-',\n [t('sales.channels.form.websiteUrl')]: initialValues?.websiteUrl ?? '-',\n },\n },\n }}\n viewHref={`/backend/sales/channels/${channelId}/edit`}\n />\n ) : undefined}\n entityId={E.sales.sales_channel}\n fields={fields}\n groups={[\n ...groups,\n { id: 'custom', title: t('entities.customFields.title', 'Custom Attributes'), column: 2, kind: 'customFields' },\n ]}\n initialValues={initialValues ?? undefined}\n isLoading={loading}\n loadingMessage={t('sales.channels.form.loading', 'Loading channel\u2026')}\n submitLabel={t('sales.channels.form.updateSubmit', 'Save changes')}\n cancelHref=\"/backend/sales/channels\"\n backHref=\"/backend/sales/channels\"\n contentHeader={renderTabs()}\n onSubmit={handleSubmit}\n onDelete={handleDelete}\n deleteVisible\n deleteRedirect=\"/backend/sales/channels\"\n />\n ) : (\n <>\n {renderTabs()}\n <SalesChannelOffersPanel channelId={channelId} channelName={initialValues?.name ?? ''} />\n </>\n )}\n </PageBody>\n </Page>\n )\n}\n\nfunction mapChannelToFormValues(item: Record<string, unknown>): ChannelFormValues {\n const values: ChannelFormValues = {\n name: typeof item.name === 'string' ? item.name : '',\n code: typeof item.code === 'string' ? item.code : null,\n description: typeof item.description === 'string' ? item.description : null,\n websiteUrl: typeof item.websiteUrl === 'string' ? item.websiteUrl : typeof item.website_url === 'string' ? item.website_url : null,\n contactEmail: typeof item.contactEmail === 'string' ? item.contactEmail : typeof item.contact_email === 'string' ? item.contact_email : null,\n contactPhone: typeof item.contactPhone === 'string' ? item.contactPhone : typeof item.contact_phone === 'string' ? item.contact_phone : null,\n addressLine1: typeof item.addressLine1 === 'string' ? item.addressLine1 : typeof item.address_line1 === 'string' ? item.address_line1 : null,\n addressLine2: typeof item.addressLine2 === 'string' ? item.addressLine2 : typeof item.address_line2 === 'string' ? item.address_line2 : null,\n city: typeof item.city === 'string' ? item.city : null,\n region: typeof item.region === 'string' ? item.region : null,\n postalCode: typeof item.postalCode === 'string' ? item.postalCode : typeof item.postal_code === 'string' ? item.postal_code : null,\n country: typeof item.country === 'string' ? item.country : null,\n latitude: typeof item.latitude === 'number' ? item.latitude : typeof item.latitude === 'string' ? item.latitude : null,\n longitude: typeof item.longitude === 'number' ? item.longitude : typeof item.longitude === 'string' ? item.longitude : null,\n statusEntryId: typeof item.statusEntryId === 'string'\n ? item.statusEntryId\n : typeof item.status_entry_id === 'string'\n ? item.status_entry_id\n : null,\n isActive: item.isActive === true || item.is_active === true,\n }\n return { ...values, ...extractCustomFieldEntries(item) }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3977,7 +3977,7 @@ function SalesDocumentDetailPage({
|
|
|
3977
3977
|
"data-active": activeTab === tab.id ? "true" : "false",
|
|
3978
3978
|
className: cn(
|
|
3979
3979
|
"h-auto rounded-none border-b-2 px-3 py-2 text-sm font-medium transition-colors hover:bg-transparent",
|
|
3980
|
-
activeTab === tab.id ? "border-b-2 border-
|
|
3980
|
+
activeTab === tab.id ? "border-b-2 border-accent-indigo text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
|
|
3981
3981
|
),
|
|
3982
3982
|
onClick: () => setActiveTab(tab.id),
|
|
3983
3983
|
children: tab.label
|