@open-mercato/ui 0.5.1-develop.2856.35de414092 → 0.5.1-develop.2874.77704bccbd
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/AGENTS.md +204 -121
- package/dist/backend/AppShell.js +25 -28
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/ContextHelp.js +1 -1
- package/dist/backend/ContextHelp.js.map +1 -1
- package/dist/backend/CrudForm.js +12 -15
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/DataTable.js +9 -10
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/FilterBar.js +6 -8
- package/dist/backend/FilterBar.js.map +2 -2
- package/dist/backend/FilterOverlay.js +10 -10
- package/dist/backend/FilterOverlay.js.map +2 -2
- package/dist/backend/FlashMessages.js +1 -1
- package/dist/backend/FlashMessages.js.map +2 -2
- package/dist/backend/JsonBuilder.js +6 -6
- package/dist/backend/JsonBuilder.js.map +1 -1
- package/dist/backend/NextStepCallout.js +1 -1
- package/dist/backend/NextStepCallout.js.map +1 -1
- package/dist/backend/PerspectiveSidebar.js +2 -2
- package/dist/backend/PerspectiveSidebar.js.map +2 -2
- package/dist/backend/ProfileDropdown.js +1 -1
- package/dist/backend/ProfileDropdown.js.map +1 -1
- package/dist/backend/RowActions.js +1 -1
- package/dist/backend/RowActions.js.map +1 -1
- package/dist/backend/UserMenu.js +2 -2
- package/dist/backend/UserMenu.js.map +1 -1
- package/dist/backend/WebhookSetupGuide.js +11 -11
- package/dist/backend/WebhookSetupGuide.js.map +2 -2
- package/dist/backend/charts/KpiCard.js +3 -3
- package/dist/backend/charts/KpiCard.js.map +1 -1
- package/dist/backend/columns/ColumnChooserPanel.js +1 -1
- package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js +3 -3
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +2 -2
- package/dist/backend/dashboard/DashboardScreen.js +1 -1
- package/dist/backend/dashboard/DashboardScreen.js.map +1 -1
- package/dist/backend/date-range/DateRangeSelect.js +1 -1
- package/dist/backend/date-range/DateRangeSelect.js.map +1 -1
- package/dist/backend/date-range/InlineDateRangeSelect.js +1 -1
- package/dist/backend/date-range/InlineDateRangeSelect.js.map +1 -1
- package/dist/backend/detail/AccessDeniedMessage.js +1 -1
- package/dist/backend/detail/AccessDeniedMessage.js.map +1 -1
- package/dist/backend/detail/ActivitiesSection.js +5 -5
- package/dist/backend/detail/ActivitiesSection.js.map +1 -1
- package/dist/backend/detail/AddressEditor.js +3 -3
- package/dist/backend/detail/AddressEditor.js.map +2 -2
- package/dist/backend/detail/AddressTiles.js +3 -3
- package/dist/backend/detail/AddressTiles.js.map +2 -2
- package/dist/backend/detail/AttachmentMetadataDialog.js +1 -1
- package/dist/backend/detail/AttachmentMetadataDialog.js.map +1 -1
- package/dist/backend/detail/CustomDataSection.js +1 -1
- package/dist/backend/detail/CustomDataSection.js.map +1 -1
- package/dist/backend/detail/InlineEditors.js +5 -5
- package/dist/backend/detail/InlineEditors.js.map +1 -1
- package/dist/backend/detail/NotesSection.js +6 -6
- package/dist/backend/detail/NotesSection.js.map +1 -1
- package/dist/backend/detail/TagsSection.js +1 -1
- package/dist/backend/detail/TagsSection.js.map +1 -1
- package/dist/backend/devtools/UmesDevToolsPanel.js +6 -6
- package/dist/backend/devtools/UmesDevToolsPanel.js.map +2 -2
- package/dist/backend/devtools/components/ConflictWarnings.js +3 -3
- package/dist/backend/devtools/components/ConflictWarnings.js.map +2 -2
- package/dist/backend/devtools/components/EnricherTiming.js +2 -2
- package/dist/backend/devtools/components/EnricherTiming.js.map +2 -2
- package/dist/backend/devtools/components/EventFlow.js +5 -5
- package/dist/backend/devtools/components/EventFlow.js.map +2 -2
- package/dist/backend/devtools/components/ExtensionPointList.js +3 -3
- package/dist/backend/devtools/components/ExtensionPointList.js.map +2 -2
- package/dist/backend/devtools/components/InterceptorActivity.js +6 -6
- package/dist/backend/devtools/components/InterceptorActivity.js.map +2 -2
- package/dist/backend/forms/ActionsDropdown.js +1 -1
- package/dist/backend/forms/ActionsDropdown.js.map +1 -1
- package/dist/backend/forms/FormActionButtons.js +2 -3
- package/dist/backend/forms/FormActionButtons.js.map +2 -2
- package/dist/backend/indexes/PartialIndexBanner.js +8 -8
- package/dist/backend/indexes/PartialIndexBanner.js.map +2 -2
- package/dist/backend/inputs/ComboboxInput.js +1 -1
- package/dist/backend/inputs/ComboboxInput.js.map +2 -2
- package/dist/backend/inputs/DatePicker.js +3 -3
- package/dist/backend/inputs/DatePicker.js.map +1 -1
- package/dist/backend/inputs/DateTimePicker.js +3 -3
- package/dist/backend/inputs/DateTimePicker.js.map +1 -1
- package/dist/backend/inputs/EventSelect.js +1 -1
- package/dist/backend/inputs/EventSelect.js.map +2 -2
- package/dist/backend/inputs/LookupSelect.js +1 -1
- package/dist/backend/inputs/LookupSelect.js.map +1 -1
- package/dist/backend/inputs/SwitchableMarkdownInput.js +1 -1
- package/dist/backend/inputs/SwitchableMarkdownInput.js.map +1 -1
- package/dist/backend/inputs/TagsInput.js +2 -2
- package/dist/backend/inputs/TagsInput.js.map +2 -2
- package/dist/backend/inputs/TimeInput.js +1 -1
- package/dist/backend/inputs/TimeInput.js.map +1 -1
- package/dist/backend/inputs/TimePicker.js +3 -3
- package/dist/backend/inputs/TimePicker.js.map +1 -1
- package/dist/backend/messages/MessageObjectDetail.js +1 -1
- package/dist/backend/messages/MessageObjectDetail.js.map +1 -1
- package/dist/backend/messages/MessageObjectPreview.js +1 -1
- package/dist/backend/messages/MessageObjectPreview.js.map +1 -1
- package/dist/backend/messages/message-compose-form-groups.js +3 -3
- package/dist/backend/messages/message-compose-form-groups.js.map +1 -1
- package/dist/backend/notifications/NotificationCountBadge.js +1 -1
- package/dist/backend/notifications/NotificationCountBadge.js.map +2 -2
- package/dist/backend/notifications/NotificationPanel.js +3 -3
- package/dist/backend/notifications/NotificationPanel.js.map +1 -1
- package/dist/backend/progress/ProgressTopBar.js +4 -4
- package/dist/backend/progress/ProgressTopBar.js.map +2 -2
- package/dist/backend/schedule/ScheduleAgenda.js +1 -1
- package/dist/backend/schedule/ScheduleAgenda.js.map +2 -2
- package/dist/backend/schedule/ScheduleCalendar.js +1 -1
- package/dist/backend/schedule/ScheduleCalendar.js.map +1 -1
- package/dist/backend/schedule/ScheduleGrid.js +1 -1
- package/dist/backend/schedule/ScheduleGrid.js.map +2 -2
- package/dist/backend/version-history/VersionHistoryPanel.js +4 -4
- package/dist/backend/version-history/VersionHistoryPanel.js.map +2 -2
- package/dist/frontend/AuthFooter.js +1 -1
- package/dist/frontend/AuthFooter.js.map +1 -1
- package/dist/frontend/LanguageSwitcher.js +1 -1
- package/dist/frontend/LanguageSwitcher.js.map +1 -1
- package/dist/frontend/Layout.js +2 -2
- package/dist/frontend/Layout.js.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +2 -2
- package/dist/portal/PortalShell.js +15 -15
- package/dist/portal/PortalShell.js.map +2 -2
- package/dist/portal/components/PortalCard.js +2 -2
- package/dist/portal/components/PortalCard.js.map +2 -2
- package/dist/portal/components/PortalNotificationPanel.js +18 -18
- package/dist/portal/components/PortalNotificationPanel.js.map +2 -2
- package/dist/portal/components/PortalPageHeader.js +1 -1
- package/dist/portal/components/PortalPageHeader.js.map +2 -2
- package/dist/primitives/avatar.js +11 -1
- package/dist/primitives/avatar.js.map +2 -2
- package/dist/primitives/badge.js +1 -1
- package/dist/primitives/badge.js.map +1 -1
- package/dist/primitives/button.js +9 -5
- package/dist/primitives/button.js.map +2 -2
- package/dist/primitives/calendar.js +1 -1
- package/dist/primitives/calendar.js.map +1 -1
- package/dist/primitives/checkbox-field.js +63 -0
- package/dist/primitives/checkbox-field.js.map +7 -0
- package/dist/primitives/checkbox.js +31 -17
- package/dist/primitives/checkbox.js.map +2 -2
- package/dist/primitives/dialog.js +4 -4
- package/dist/primitives/dialog.js.map +1 -1
- package/dist/primitives/fancy-button.js +72 -0
- package/dist/primitives/fancy-button.js.map +7 -0
- package/dist/primitives/icon-button.js +20 -4
- package/dist/primitives/icon-button.js.map +2 -2
- package/dist/primitives/kbd.js +27 -0
- package/dist/primitives/kbd.js.map +7 -0
- package/dist/primitives/link-button.js +56 -0
- package/dist/primitives/link-button.js.map +7 -0
- package/dist/primitives/popover.js +1 -1
- package/dist/primitives/popover.js.map +1 -1
- package/dist/primitives/social-button.js +61 -0
- package/dist/primitives/social-button.js.map +7 -0
- package/dist/primitives/tabs.js +1 -1
- package/dist/primitives/tabs.js.map +1 -1
- package/dist/primitives/tag.js +45 -0
- package/dist/primitives/tag.js.map +7 -0
- package/dist/primitives/tooltip.js +1 -1
- package/dist/primitives/tooltip.js.map +1 -1
- package/package.json +3 -3
- package/src/backend/AppShell.tsx +25 -28
- package/src/backend/ContextHelp.tsx +1 -1
- package/src/backend/CrudForm.tsx +12 -15
- package/src/backend/DataTable.tsx +9 -10
- package/src/backend/FilterBar.tsx +6 -5
- package/src/backend/FilterOverlay.tsx +10 -10
- package/src/backend/FlashMessages.tsx +1 -1
- package/src/backend/JsonBuilder.tsx +6 -6
- package/src/backend/NextStepCallout.tsx +1 -1
- package/src/backend/PerspectiveSidebar.tsx +2 -2
- package/src/backend/ProfileDropdown.tsx +1 -1
- package/src/backend/RowActions.tsx +1 -1
- package/src/backend/UserMenu.tsx +2 -2
- package/src/backend/WebhookSetupGuide.tsx +11 -11
- package/src/backend/charts/KpiCard.tsx +3 -3
- package/src/backend/columns/ColumnChooserPanel.tsx +1 -1
- package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +3 -3
- package/src/backend/dashboard/DashboardScreen.tsx +1 -1
- package/src/backend/date-range/DateRangeSelect.tsx +1 -1
- package/src/backend/date-range/InlineDateRangeSelect.tsx +1 -1
- package/src/backend/detail/AccessDeniedMessage.tsx +1 -1
- package/src/backend/detail/ActivitiesSection.tsx +5 -5
- package/src/backend/detail/AddressEditor.tsx +3 -3
- package/src/backend/detail/AddressTiles.tsx +3 -3
- package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
- package/src/backend/detail/CustomDataSection.tsx +1 -1
- package/src/backend/detail/InlineEditors.tsx +5 -5
- package/src/backend/detail/NotesSection.tsx +6 -6
- package/src/backend/detail/TagsSection.tsx +1 -1
- package/src/backend/devtools/UmesDevToolsPanel.tsx +6 -6
- package/src/backend/devtools/components/ConflictWarnings.tsx +4 -4
- package/src/backend/devtools/components/EnricherTiming.tsx +2 -2
- package/src/backend/devtools/components/EventFlow.tsx +5 -5
- package/src/backend/devtools/components/ExtensionPointList.tsx +3 -3
- package/src/backend/devtools/components/InterceptorActivity.tsx +6 -6
- package/src/backend/forms/ActionsDropdown.tsx +1 -1
- package/src/backend/forms/FormActionButtons.tsx +4 -5
- package/src/backend/indexes/PartialIndexBanner.tsx +8 -8
- package/src/backend/inputs/ComboboxInput.tsx +1 -1
- package/src/backend/inputs/DatePicker.tsx +3 -3
- package/src/backend/inputs/DateTimePicker.tsx +3 -3
- package/src/backend/inputs/EventSelect.tsx +1 -1
- package/src/backend/inputs/LookupSelect.tsx +1 -1
- package/src/backend/inputs/SwitchableMarkdownInput.tsx +1 -1
- package/src/backend/inputs/TagsInput.tsx +2 -2
- package/src/backend/inputs/TimeInput.tsx +1 -1
- package/src/backend/inputs/TimePicker.tsx +3 -3
- package/src/backend/messages/MessageObjectDetail.tsx +1 -1
- package/src/backend/messages/MessageObjectPreview.tsx +1 -1
- package/src/backend/messages/message-compose-form-groups.tsx +3 -3
- package/src/backend/notifications/NotificationCountBadge.tsx +1 -1
- package/src/backend/notifications/NotificationPanel.tsx +3 -3
- package/src/backend/progress/ProgressTopBar.tsx +4 -4
- package/src/backend/schedule/ScheduleAgenda.tsx +1 -1
- package/src/backend/schedule/ScheduleCalendar.tsx +1 -1
- package/src/backend/schedule/ScheduleGrid.tsx +1 -1
- package/src/backend/version-history/VersionHistoryPanel.tsx +4 -4
- package/src/frontend/AuthFooter.tsx +1 -1
- package/src/frontend/LanguageSwitcher.tsx +1 -1
- package/src/frontend/Layout.tsx +2 -2
- package/src/index.ts +6 -1
- package/src/portal/PortalShell.tsx +15 -15
- package/src/portal/components/PortalCard.tsx +2 -2
- package/src/portal/components/PortalNotificationPanel.tsx +18 -18
- package/src/portal/components/PortalPageHeader.tsx +1 -1
- package/src/primitives/avatar.tsx +22 -0
- package/src/primitives/badge.tsx +1 -1
- package/src/primitives/button.tsx +12 -5
- package/src/primitives/calendar.tsx +1 -1
- package/src/primitives/checkbox-field.tsx +85 -0
- package/src/primitives/checkbox.tsx +44 -18
- package/src/primitives/dialog.tsx +4 -4
- package/src/primitives/fancy-button.tsx +89 -0
- package/src/primitives/icon-button.tsx +19 -2
- package/src/primitives/kbd.tsx +38 -0
- package/src/primitives/link-button.tsx +55 -0
- package/src/primitives/popover.tsx +1 -1
- package/src/primitives/social-button.tsx +80 -0
- package/src/primitives/tabs.tsx +1 -1
- package/src/primitives/tag.tsx +66 -0
- package/src/primitives/tooltip.tsx +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/messages/message-compose-form-groups.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\nimport { FileCode, Globe, Lock } from 'lucide-react'\nimport { type CrudField } from '../CrudForm'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport { Label } from '../../primitives/label'\nimport { Switch } from '../../primitives/switch'\nimport { AttachmentsSection } from '../detail/AttachmentsSection'\nimport { SwitchableMarkdownInput } from '../inputs/SwitchableMarkdownInput'\nimport { TagsInput } from '../inputs/TagsInput'\nimport { MessagePrioritySelector } from './MessagePrioritySelector'\nimport type { UseMessageComposeResult } from './useMessageCompose'\n\ntype ComposeProps = {\n compose: UseMessageComposeResult\n}\n\nfunction RecipientTagsInput({ compose }: ComposeProps) {\n return (\n <TagsInput\n value={compose.recipientIds}\n onChange={compose.setRecipientIds}\n selectedOptions={compose.selectedRecipientOptions}\n resolveLabel={compose.resolveRecipientLabel}\n loadSuggestions={compose.loadRecipientSuggestions}\n placeholder={compose.t('messages.placeholders.recipients', 'Search recipients...')}\n allowCustomValues={false}\n showSuggestionsOnFocus={false}\n />\n )\n}\n\nfunction VisibilitySelector({ compose }: ComposeProps) {\n return (\n <>\n <Label>{compose.t('messages.visibility', 'Visibility')}</Label>\n <div\n className=\"inline-flex items-center gap-1 rounded-md border bg-background p-1\"\n role=\"radiogroup\"\n aria-label={compose.t('messages.visibility', 'Visibility')}\n >\n <IconButton\n type=\"button\"\n size=\"xs\"\n variant={compose.visibility === 'internal' ? 'outline' : 'ghost'}\n role=\"radio\"\n aria-checked={compose.visibility === 'internal'}\n aria-label={compose.t('messages.visibilityInternal', 'Internal')}\n title={compose.t('messages.visibilityInternal', 'Internal')}\n className=\"h-7 w-7\"\n onClick={() => compose.setVisibility('internal')}\n >\n <Lock className=\"h-3.5 w-3.5\" />\n </IconButton>\n <IconButton\n type=\"button\"\n size=\"xs\"\n variant={compose.visibility === 'public' ? 'outline' : 'ghost'}\n role=\"radio\"\n aria-checked={compose.visibility === 'public'}\n aria-label={compose.t('messages.visibilityPublic', 'Public')}\n title={compose.t('messages.visibilityPublic', 'Public')}\n className=\"h-7 w-7\"\n onClick={() => compose.setVisibility('public')}\n >\n <Globe className=\"h-3.5 w-3.5\" />\n </IconButton>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {compose.visibility === 'public'\n ? compose.t('messages.visibilityPublicHint', 'Public messages are sent to external email only.')\n : compose.t('messages.visibilityInternalHint', 'Internal messages are sent to selected system users.')}\n </p>\n </>\n )\n}\n\nfunction ContextActionsSection({ compose }: ComposeProps) {\n if (!compose.shouldShowContextActions) return null\n\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {compose.normalizedRequiredActionMode === 'optional' ? (\n <div className=\"flex items-center justify-between rounded border px-3 py-2 sm:col-span-2\">\n <div>\n <p className=\"text-sm font-medium\">\n {compose.t('messages.composer.objectPicker.actionRequiredLabel', 'Action required')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {compose.t('messages.composer.objectPicker.actionRequiredHint', 'Mark this object as requiring recipient action.')}\n </p>\n </div>\n <Switch checked={compose.contextActionRequired} onCheckedChange={compose.setContextActionRequired} />\n </div>\n ) : null}\n {compose.normalizedRequiredActionMode === 'required' || compose.contextActionRequired ? (\n <div className=\"space-y-2 sm:col-span-2\">\n <Label htmlFor=\"messages-compose-context-action-type\">\n {compose.t('messages.composer.objectPicker.actionTypeLabel', 'Action type')}\n </Label>\n <select\n id=\"messages-compose-context-action-type\"\n value={compose.contextActionType}\n onChange={(event) => compose.setContextActionType(event.target.value)}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n >\n <option value=\"\">{compose.t('messages.composer.objectPicker.actionTypePlaceholder', 'Select action')}</option>\n {compose.contextActionOptions.map((option) => (\n <option key={option.id} value={option.id}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction PrioritySelector({ compose }: ComposeProps) {\n return (\n <>\n <Label>{compose.t('messages.priority', 'Priority')}</Label>\n <MessagePrioritySelector\n value={compose.priority}\n onChange={compose.setPriority}\n t={compose.t}\n />\n </>\n )\n}\n\nfunction MarkdownBodySection({\n compose,\n label,\n placeholder,\n inputId,\n rows,\n textareaClassName,\n}: ComposeProps & {\n label: string\n placeholder: string\n inputId: string\n rows: number\n textareaClassName: string\n}) {\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <Label htmlFor={inputId}>{label}</Label>\n <IconButton\n type=\"button\"\n size=\"sm\"\n variant={compose.bodyFormat === 'markdown' ? 'outline' : 'ghost'}\n aria-pressed={compose.bodyFormat === 'markdown'}\n onClick={() => compose.setBodyFormat((previousValue) => (previousValue === 'markdown' ? 'text' : 'markdown'))}\n title={compose.t('messages.bodyFormat.toggle', 'Toggle markdown')}\n >\n <FileCode className=\"h-4 w-4\" />\n </IconButton>\n </div>\n <div id={inputId}>\n <SwitchableMarkdownInput\n value={compose.body}\n onChange={compose.setBody}\n isMarkdownEnabled={compose.bodyFormat === 'markdown'}\n rows={rows}\n placeholder={placeholder}\n textareaClassName={textareaClassName}\n />\n </div>\n </div>\n )\n}\n\nfunction ComposeModeFields({ compose }: ComposeProps) {\n return (\n <>\n <div className=\"space-y-3\">\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n {compose.visibility === 'public' ? (\n <>\n <Label htmlFor=\"messages-compose-external-email\">{compose.t('messages.externalEmail', 'External email')}</Label>\n <Input\n id=\"messages-compose-external-email\"\n type=\"email\"\n value={compose.externalEmail}\n onChange={(event) => compose.setExternalEmail(event.target.value)}\n placeholder={compose.t('messages.placeholders.externalEmail', 'name@example.com')}\n />\n </>\n ) : (\n <>\n <Label htmlFor=\"messages-compose-recipients\">{compose.t('messages.to', 'To')}</Label>\n <RecipientTagsInput compose={compose} />\n </>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <VisibilitySelector compose={compose} />\n </div>\n </div>\n\n <ContextActionsSection compose={compose} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-compose-subject\">{compose.t('messages.subject', 'Subject')}</Label>\n <Input\n id=\"messages-compose-subject\"\n value={compose.subject}\n onChange={(event) => compose.setSubject(event.target.value)}\n placeholder={compose.t('messages.placeholders.subject', 'Enter subject...')}\n />\n </div>\n\n <div className=\"space-y-2\">\n <PrioritySelector compose={compose} />\n </div>\n </div>\n\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.body', 'Message')}\n placeholder={compose.t('messages.placeholders.body', 'Write your message...')}\n inputId=\"messages-compose-body\"\n rows={8}\n textareaClassName=\"min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n />\n\n <div className=\"space-y-2\">\n <Label>{compose.t('messages.attachedFiles', 'Attachments')}</Label>\n <AttachmentsSection\n entityId={compose.attachmentEntityId}\n recordId={compose.attachmentRecordId}\n showHeader={false}\n compact\n onChanged={() => {\n void compose.loadAttachmentIds().catch(() => null)\n }}\n />\n </div>\n </>\n )\n}\n\nfunction ReplyModeFields({ compose }: ComposeProps) {\n return (\n <>\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.replyBody', 'Reply')}\n placeholder={compose.t('messages.placeholders.replyBody', 'Write your reply...')}\n inputId=\"messages-compose-body\"\n rows={8}\n textareaClassName=\"min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n />\n\n <div className=\"space-y-2\">\n <Label>{compose.t('messages.attachedFiles', 'Attachments')}</Label>\n <AttachmentsSection\n entityId={compose.attachmentEntityId}\n recordId={compose.attachmentRecordId}\n showHeader={false}\n compact\n onChanged={() => {\n void compose.loadAttachmentIds().catch(() => null)\n }}\n />\n </div>\n </>\n )\n}\n\nfunction ForwardModeFields({ compose }: ComposeProps) {\n return (\n <>\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-compose-recipients\">{compose.t('messages.to', 'To')}</Label>\n <RecipientTagsInput compose={compose} />\n </div>\n\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.forwardContent', 'Forwarded content')}\n placeholder={compose.t('messages.placeholders.forwardContent', 'Review and edit forwarded content...')}\n inputId=\"messages-forward-note\"\n rows={6}\n textareaClassName=\"min-h-[140px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n />\n </>\n )\n}\n\nfunction EmailDeliverySection({ compose }: ComposeProps) {\n if (compose.variant === 'reply' || compose.variant === 'forward') {\n return null\n }\n\n if (compose.isComposePublicVisibility) {\n return (\n <div className=\"rounded border px-3 py-2\">\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailForcedPublic', 'For public visibility, email delivery is always enabled.')}</p>\n </div>\n )\n }\n\n return (\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}</p>\n </div>\n <Switch checked={compose.sendViaEmail} onCheckedChange={compose.setSendViaEmail} />\n </div>\n )\n}\n\nfunction ReplyForwardOptionsRow({ compose }: ComposeProps) {\n if (compose.variant !== 'reply' && compose.variant !== 'forward') {\n return null\n }\n\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {compose.variant === 'forward' ? (\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.includeAttachments', 'Include attachments')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.includeAttachmentsHint', 'Carry over attachments from the original message.')}</p>\n </div>\n <Switch checked={compose.includeAttachments} onCheckedChange={compose.setIncludeAttachments} />\n </div>\n ) : (\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.replyAll', 'Reply all')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.replyAllHint', 'Include all original recipients.')}</p>\n </div>\n <Switch checked={compose.replyAll} onCheckedChange={compose.setReplyAll} />\n </div>\n )}\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}</p>\n </div>\n <Switch checked={compose.sendViaEmail} onCheckedChange={compose.setSendViaEmail} />\n </div>\n </div>\n )\n}\n\nfunction MessageComposeFormBody({ compose }: ComposeProps) {\n return (\n <div className=\"space-y-4\" onKeyDown={compose.handleKeyDown}>\n {compose.contextPreview ? (\n <div className=\"rounded border bg-muted/30 p-3 text-sm\">\n {compose.contextPreview}\n </div>\n ) : null}\n {compose.variant === 'compose' ? <ComposeModeFields compose={compose} /> : null}\n {compose.variant === 'reply' ? <ReplyModeFields compose={compose} /> : null}\n {compose.variant === 'forward' ? <ForwardModeFields compose={compose} /> : null}\n <ReplyForwardOptionsRow compose={compose} />\n <EmailDeliverySection compose={compose} />\n {compose.submitError ? <p className=\"text-sm text-destructive\">{compose.submitError}</p> : null}\n </div>\n )\n}\n\nexport function createMessageComposeFormGroups(compose: UseMessageComposeResult): CrudField[] {\n return [{\n id: 'composer',\n label: '',\n type: 'custom',\n component: () => <MessageComposeFormBody compose={compose} />,\n }]\n}\n"],
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport { FileCode, Globe, Lock } from 'lucide-react'\nimport { type CrudField } from '../CrudForm'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport { Label } from '../../primitives/label'\nimport { Switch } from '../../primitives/switch'\nimport { AttachmentsSection } from '../detail/AttachmentsSection'\nimport { SwitchableMarkdownInput } from '../inputs/SwitchableMarkdownInput'\nimport { TagsInput } from '../inputs/TagsInput'\nimport { MessagePrioritySelector } from './MessagePrioritySelector'\nimport type { UseMessageComposeResult } from './useMessageCompose'\n\ntype ComposeProps = {\n compose: UseMessageComposeResult\n}\n\nfunction RecipientTagsInput({ compose }: ComposeProps) {\n return (\n <TagsInput\n value={compose.recipientIds}\n onChange={compose.setRecipientIds}\n selectedOptions={compose.selectedRecipientOptions}\n resolveLabel={compose.resolveRecipientLabel}\n loadSuggestions={compose.loadRecipientSuggestions}\n placeholder={compose.t('messages.placeholders.recipients', 'Search recipients...')}\n allowCustomValues={false}\n showSuggestionsOnFocus={false}\n />\n )\n}\n\nfunction VisibilitySelector({ compose }: ComposeProps) {\n return (\n <>\n <Label>{compose.t('messages.visibility', 'Visibility')}</Label>\n <div\n className=\"inline-flex items-center gap-1 rounded-md border bg-background p-1\"\n role=\"radiogroup\"\n aria-label={compose.t('messages.visibility', 'Visibility')}\n >\n <IconButton\n type=\"button\"\n size=\"xs\"\n variant={compose.visibility === 'internal' ? 'outline' : 'ghost'}\n role=\"radio\"\n aria-checked={compose.visibility === 'internal'}\n aria-label={compose.t('messages.visibilityInternal', 'Internal')}\n title={compose.t('messages.visibilityInternal', 'Internal')}\n className=\"h-7 w-7\"\n onClick={() => compose.setVisibility('internal')}\n >\n <Lock className=\"h-3.5 w-3.5\" />\n </IconButton>\n <IconButton\n type=\"button\"\n size=\"xs\"\n variant={compose.visibility === 'public' ? 'outline' : 'ghost'}\n role=\"radio\"\n aria-checked={compose.visibility === 'public'}\n aria-label={compose.t('messages.visibilityPublic', 'Public')}\n title={compose.t('messages.visibilityPublic', 'Public')}\n className=\"h-7 w-7\"\n onClick={() => compose.setVisibility('public')}\n >\n <Globe className=\"h-3.5 w-3.5\" />\n </IconButton>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {compose.visibility === 'public'\n ? compose.t('messages.visibilityPublicHint', 'Public messages are sent to external email only.')\n : compose.t('messages.visibilityInternalHint', 'Internal messages are sent to selected system users.')}\n </p>\n </>\n )\n}\n\nfunction ContextActionsSection({ compose }: ComposeProps) {\n if (!compose.shouldShowContextActions) return null\n\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {compose.normalizedRequiredActionMode === 'optional' ? (\n <div className=\"flex items-center justify-between rounded border px-3 py-2 sm:col-span-2\">\n <div>\n <p className=\"text-sm font-medium\">\n {compose.t('messages.composer.objectPicker.actionRequiredLabel', 'Action required')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {compose.t('messages.composer.objectPicker.actionRequiredHint', 'Mark this object as requiring recipient action.')}\n </p>\n </div>\n <Switch checked={compose.contextActionRequired} onCheckedChange={compose.setContextActionRequired} />\n </div>\n ) : null}\n {compose.normalizedRequiredActionMode === 'required' || compose.contextActionRequired ? (\n <div className=\"space-y-2 sm:col-span-2\">\n <Label htmlFor=\"messages-compose-context-action-type\">\n {compose.t('messages.composer.objectPicker.actionTypeLabel', 'Action type')}\n </Label>\n <select\n id=\"messages-compose-context-action-type\"\n value={compose.contextActionType}\n onChange={(event) => compose.setContextActionType(event.target.value)}\n className=\"h-9 w-full rounded-md border bg-background px-3 text-sm\"\n >\n <option value=\"\">{compose.t('messages.composer.objectPicker.actionTypePlaceholder', 'Select action')}</option>\n {compose.contextActionOptions.map((option) => (\n <option key={option.id} value={option.id}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction PrioritySelector({ compose }: ComposeProps) {\n return (\n <>\n <Label>{compose.t('messages.priority', 'Priority')}</Label>\n <MessagePrioritySelector\n value={compose.priority}\n onChange={compose.setPriority}\n t={compose.t}\n />\n </>\n )\n}\n\nfunction MarkdownBodySection({\n compose,\n label,\n placeholder,\n inputId,\n rows,\n textareaClassName,\n}: ComposeProps & {\n label: string\n placeholder: string\n inputId: string\n rows: number\n textareaClassName: string\n}) {\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <Label htmlFor={inputId}>{label}</Label>\n <IconButton\n type=\"button\"\n size=\"sm\"\n variant={compose.bodyFormat === 'markdown' ? 'outline' : 'ghost'}\n aria-pressed={compose.bodyFormat === 'markdown'}\n onClick={() => compose.setBodyFormat((previousValue) => (previousValue === 'markdown' ? 'text' : 'markdown'))}\n title={compose.t('messages.bodyFormat.toggle', 'Toggle markdown')}\n >\n <FileCode className=\"h-4 w-4\" />\n </IconButton>\n </div>\n <div id={inputId}>\n <SwitchableMarkdownInput\n value={compose.body}\n onChange={compose.setBody}\n isMarkdownEnabled={compose.bodyFormat === 'markdown'}\n rows={rows}\n placeholder={placeholder}\n textareaClassName={textareaClassName}\n />\n </div>\n </div>\n )\n}\n\nfunction ComposeModeFields({ compose }: ComposeProps) {\n return (\n <>\n <div className=\"space-y-3\">\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n {compose.visibility === 'public' ? (\n <>\n <Label htmlFor=\"messages-compose-external-email\">{compose.t('messages.externalEmail', 'External email')}</Label>\n <Input\n id=\"messages-compose-external-email\"\n type=\"email\"\n value={compose.externalEmail}\n onChange={(event) => compose.setExternalEmail(event.target.value)}\n placeholder={compose.t('messages.placeholders.externalEmail', 'name@example.com')}\n />\n </>\n ) : (\n <>\n <Label htmlFor=\"messages-compose-recipients\">{compose.t('messages.to', 'To')}</Label>\n <RecipientTagsInput compose={compose} />\n </>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <VisibilitySelector compose={compose} />\n </div>\n </div>\n\n <ContextActionsSection compose={compose} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-compose-subject\">{compose.t('messages.subject', 'Subject')}</Label>\n <Input\n id=\"messages-compose-subject\"\n value={compose.subject}\n onChange={(event) => compose.setSubject(event.target.value)}\n placeholder={compose.t('messages.placeholders.subject', 'Enter subject...')}\n />\n </div>\n\n <div className=\"space-y-2\">\n <PrioritySelector compose={compose} />\n </div>\n </div>\n\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.body', 'Message')}\n placeholder={compose.t('messages.placeholders.body', 'Write your message...')}\n inputId=\"messages-compose-body\"\n rows={8}\n textareaClassName=\"min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n\n <div className=\"space-y-2\">\n <Label>{compose.t('messages.attachedFiles', 'Attachments')}</Label>\n <AttachmentsSection\n entityId={compose.attachmentEntityId}\n recordId={compose.attachmentRecordId}\n showHeader={false}\n compact\n onChanged={() => {\n void compose.loadAttachmentIds().catch(() => null)\n }}\n />\n </div>\n </>\n )\n}\n\nfunction ReplyModeFields({ compose }: ComposeProps) {\n return (\n <>\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.replyBody', 'Reply')}\n placeholder={compose.t('messages.placeholders.replyBody', 'Write your reply...')}\n inputId=\"messages-compose-body\"\n rows={8}\n textareaClassName=\"min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n\n <div className=\"space-y-2\">\n <Label>{compose.t('messages.attachedFiles', 'Attachments')}</Label>\n <AttachmentsSection\n entityId={compose.attachmentEntityId}\n recordId={compose.attachmentRecordId}\n showHeader={false}\n compact\n onChanged={() => {\n void compose.loadAttachmentIds().catch(() => null)\n }}\n />\n </div>\n </>\n )\n}\n\nfunction ForwardModeFields({ compose }: ComposeProps) {\n return (\n <>\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-compose-recipients\">{compose.t('messages.to', 'To')}</Label>\n <RecipientTagsInput compose={compose} />\n </div>\n\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.forwardContent', 'Forwarded content')}\n placeholder={compose.t('messages.placeholders.forwardContent', 'Review and edit forwarded content...')}\n inputId=\"messages-forward-note\"\n rows={6}\n textareaClassName=\"min-h-[140px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n </>\n )\n}\n\nfunction EmailDeliverySection({ compose }: ComposeProps) {\n if (compose.variant === 'reply' || compose.variant === 'forward') {\n return null\n }\n\n if (compose.isComposePublicVisibility) {\n return (\n <div className=\"rounded border px-3 py-2\">\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailForcedPublic', 'For public visibility, email delivery is always enabled.')}</p>\n </div>\n )\n }\n\n return (\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}</p>\n </div>\n <Switch checked={compose.sendViaEmail} onCheckedChange={compose.setSendViaEmail} />\n </div>\n )\n}\n\nfunction ReplyForwardOptionsRow({ compose }: ComposeProps) {\n if (compose.variant !== 'reply' && compose.variant !== 'forward') {\n return null\n }\n\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {compose.variant === 'forward' ? (\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.includeAttachments', 'Include attachments')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.includeAttachmentsHint', 'Carry over attachments from the original message.')}</p>\n </div>\n <Switch checked={compose.includeAttachments} onCheckedChange={compose.setIncludeAttachments} />\n </div>\n ) : (\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.replyAll', 'Reply all')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.replyAllHint', 'Include all original recipients.')}</p>\n </div>\n <Switch checked={compose.replyAll} onCheckedChange={compose.setReplyAll} />\n </div>\n )}\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}</p>\n </div>\n <Switch checked={compose.sendViaEmail} onCheckedChange={compose.setSendViaEmail} />\n </div>\n </div>\n )\n}\n\nfunction MessageComposeFormBody({ compose }: ComposeProps) {\n return (\n <div className=\"space-y-4\" onKeyDown={compose.handleKeyDown}>\n {compose.contextPreview ? (\n <div className=\"rounded border bg-muted/30 p-3 text-sm\">\n {compose.contextPreview}\n </div>\n ) : null}\n {compose.variant === 'compose' ? <ComposeModeFields compose={compose} /> : null}\n {compose.variant === 'reply' ? <ReplyModeFields compose={compose} /> : null}\n {compose.variant === 'forward' ? <ForwardModeFields compose={compose} /> : null}\n <ReplyForwardOptionsRow compose={compose} />\n <EmailDeliverySection compose={compose} />\n {compose.submitError ? <p className=\"text-sm text-destructive\">{compose.submitError}</p> : null}\n </div>\n )\n}\n\nexport function createMessageComposeFormGroups(compose: UseMessageComposeResult): CrudField[] {\n return [{\n id: 'composer',\n label: '',\n type: 'custom',\n component: () => <MessageComposeFormBody compose={compose} />,\n }]\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
function NotificationCountBadge({ count }) {
|
|
3
3
|
if (count <= 0) return null;
|
|
4
|
-
return /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-
|
|
4
|
+
return /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-overline font-medium text-destructive-foreground", children: count > 99 ? "99+" : count });
|
|
5
5
|
}
|
|
6
6
|
export {
|
|
7
7
|
NotificationCountBadge
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/notifications/NotificationCountBadge.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\n\nexport type NotificationCountBadgeProps = {\n count: number\n}\n\nexport function NotificationCountBadge({ count }: NotificationCountBadgeProps) {\n if (count <= 0) return null\n return (\n <span className=\"absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-
|
|
5
|
-
"mappings": "AASI;AAHG,SAAS,uBAAuB,EAAE,MAAM,GAAgC;AAC7E,MAAI,SAAS,EAAG,QAAO;AACvB,SACE,oBAAC,UAAK,WAAU,
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\n\nexport type NotificationCountBadgeProps = {\n count: number\n}\n\nexport function NotificationCountBadge({ count }: NotificationCountBadgeProps) {\n if (count <= 0) return null\n return (\n <span className=\"absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-overline font-medium text-destructive-foreground\">\n {count > 99 ? '99+' : count}\n </span>\n )\n}\n"],
|
|
5
|
+
"mappings": "AASI;AAHG,SAAS,uBAAuB,EAAE,MAAM,GAAgC;AAC7E,MAAI,SAAS,EAAG,QAAO;AACvB,SACE,oBAAC,UAAK,WAAU,uJACb,kBAAQ,KAAK,QAAQ,OACxB;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -64,7 +64,7 @@ function NotificationPanel({
|
|
|
64
64
|
/* @__PURE__ */ jsx(
|
|
65
65
|
"div",
|
|
66
66
|
{
|
|
67
|
-
className: "fixed inset-0 z-
|
|
67
|
+
className: "fixed inset-0 z-overlay bg-black/20",
|
|
68
68
|
onClick: () => onOpenChange(false),
|
|
69
69
|
"aria-hidden": "true"
|
|
70
70
|
}
|
|
@@ -72,7 +72,7 @@ function NotificationPanel({
|
|
|
72
72
|
/* @__PURE__ */ jsx(
|
|
73
73
|
"div",
|
|
74
74
|
{
|
|
75
|
-
className: "fixed right-0 top-0 z-
|
|
75
|
+
className: "fixed right-0 top-0 z-modal h-full w-full max-w-md border-l bg-background shadow-lg",
|
|
76
76
|
role: "dialog",
|
|
77
77
|
"aria-modal": "true",
|
|
78
78
|
"aria-label": t("notifications.title", "Notifications"),
|
|
@@ -113,7 +113,7 @@ function NotificationPanel({
|
|
|
113
113
|
] })
|
|
114
114
|
}
|
|
115
115
|
),
|
|
116
|
-
dismissUndo && onUndoDismiss && /* @__PURE__ */ jsx("div", { className: "border-b bg-muted/
|
|
116
|
+
dismissUndo && onUndoDismiss && /* @__PURE__ */ jsx("div", { className: "border-b bg-muted/50 px-4 py-2 text-sm", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
117
117
|
/* @__PURE__ */ jsx("span", { children: t("notifications.toast.dismissed", "Notification dismissed") }),
|
|
118
118
|
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => onUndoDismiss(), children: [
|
|
119
119
|
/* @__PURE__ */ jsx(RotateCcw, { className: "mr-1 h-3 w-3" }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/notifications/NotificationPanel.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { X, Bell, CheckCheck, Loader2, RotateCcw } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Tabs, TabsList, TabsTrigger } from '../../primitives/tabs'\nimport { NotificationItem } from './NotificationItem'\nimport type { NotificationDto, NotificationRendererProps } from '@open-mercato/shared/modules/notifications/types'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport type { ComponentType } from 'react'\n\n/**\n * Map of notification type to custom renderer component.\n * Used to provide custom rendering for specific notification types.\n *\n * @example\n * ```tsx\n * const customRenderers = {\n * 'sales.order.created': SalesOrderCreatedRenderer,\n * 'sales.quote.created': SalesQuoteCreatedRenderer,\n * }\n * ```\n */\nexport type NotificationRenderers = Record<string, ComponentType<NotificationRendererProps>>\n\nexport type NotificationPanelProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n notifications: NotificationDto[]\n unreadCount: number\n onMarkAsRead: (id: string) => Promise<void>\n onExecuteAction: (id: string, actionId: string) => Promise<{ href?: string }>\n onDismiss: (id: string) => Promise<void>\n dismissUndo?: { notification: NotificationDto; previousStatus: 'read' | 'unread' } | null\n onUndoDismiss?: () => Promise<void>\n onMarkAllRead: () => Promise<void>\n t: TranslateFn\n /**\n * Optional map of notification type to custom renderer component.\n * When a notification's type matches a key in this map, the corresponding\n * renderer will be used instead of the default NotificationItem rendering.\n *\n * @example\n * ```tsx\n * import { salesNotificationTypes } from '@open-mercato/core/modules/sales/notifications.client'\n *\n * // Build renderers map from notification types\n * const renderers = Object.fromEntries(\n * salesNotificationTypes\n * .filter(t => t.Renderer)\n * .map(t => [t.type, t.Renderer!])\n * )\n *\n * <NotificationPanel customRenderers={renderers} ... />\n * ```\n */\n customRenderers?: NotificationRenderers\n}\n\nexport function NotificationPanel({\n open,\n onOpenChange,\n notifications,\n unreadCount,\n onMarkAsRead,\n onExecuteAction,\n onDismiss,\n dismissUndo,\n onUndoDismiss,\n onMarkAllRead,\n t,\n customRenderers,\n}: NotificationPanelProps) {\n const [filter, setFilter] = React.useState<'all' | 'unread' | 'action'>('all')\n const [markingAllRead, setMarkingAllRead] = React.useState(false)\n\n const filteredNotifications = React.useMemo(() => {\n switch (filter) {\n case 'unread':\n return notifications.filter((n) => n.status === 'unread')\n case 'action':\n return notifications.filter(\n (n) => n.actions && n.actions.length > 0 && n.status !== 'actioned'\n )\n default:\n return notifications\n }\n }, [notifications, filter])\n\n const handleMarkAllRead = async () => {\n setMarkingAllRead(true)\n try {\n await onMarkAllRead()\n } finally {\n setMarkingAllRead(false)\n }\n }\n\n React.useEffect(() => {\n if (!open) return\n const prevOverflow = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n return () => {\n document.body.style.overflow = prevOverflow\n }\n }, [open])\n\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && open) {\n onOpenChange(false)\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [open, onOpenChange])\n\n if (!open) return null\n\n return (\n <>\n <div\n className=\"fixed inset-0 z-
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { X, Bell, CheckCheck, Loader2, RotateCcw } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Tabs, TabsList, TabsTrigger } from '../../primitives/tabs'\nimport { NotificationItem } from './NotificationItem'\nimport type { NotificationDto, NotificationRendererProps } from '@open-mercato/shared/modules/notifications/types'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport type { ComponentType } from 'react'\n\n/**\n * Map of notification type to custom renderer component.\n * Used to provide custom rendering for specific notification types.\n *\n * @example\n * ```tsx\n * const customRenderers = {\n * 'sales.order.created': SalesOrderCreatedRenderer,\n * 'sales.quote.created': SalesQuoteCreatedRenderer,\n * }\n * ```\n */\nexport type NotificationRenderers = Record<string, ComponentType<NotificationRendererProps>>\n\nexport type NotificationPanelProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n notifications: NotificationDto[]\n unreadCount: number\n onMarkAsRead: (id: string) => Promise<void>\n onExecuteAction: (id: string, actionId: string) => Promise<{ href?: string }>\n onDismiss: (id: string) => Promise<void>\n dismissUndo?: { notification: NotificationDto; previousStatus: 'read' | 'unread' } | null\n onUndoDismiss?: () => Promise<void>\n onMarkAllRead: () => Promise<void>\n t: TranslateFn\n /**\n * Optional map of notification type to custom renderer component.\n * When a notification's type matches a key in this map, the corresponding\n * renderer will be used instead of the default NotificationItem rendering.\n *\n * @example\n * ```tsx\n * import { salesNotificationTypes } from '@open-mercato/core/modules/sales/notifications.client'\n *\n * // Build renderers map from notification types\n * const renderers = Object.fromEntries(\n * salesNotificationTypes\n * .filter(t => t.Renderer)\n * .map(t => [t.type, t.Renderer!])\n * )\n *\n * <NotificationPanel customRenderers={renderers} ... />\n * ```\n */\n customRenderers?: NotificationRenderers\n}\n\nexport function NotificationPanel({\n open,\n onOpenChange,\n notifications,\n unreadCount,\n onMarkAsRead,\n onExecuteAction,\n onDismiss,\n dismissUndo,\n onUndoDismiss,\n onMarkAllRead,\n t,\n customRenderers,\n}: NotificationPanelProps) {\n const [filter, setFilter] = React.useState<'all' | 'unread' | 'action'>('all')\n const [markingAllRead, setMarkingAllRead] = React.useState(false)\n\n const filteredNotifications = React.useMemo(() => {\n switch (filter) {\n case 'unread':\n return notifications.filter((n) => n.status === 'unread')\n case 'action':\n return notifications.filter(\n (n) => n.actions && n.actions.length > 0 && n.status !== 'actioned'\n )\n default:\n return notifications\n }\n }, [notifications, filter])\n\n const handleMarkAllRead = async () => {\n setMarkingAllRead(true)\n try {\n await onMarkAllRead()\n } finally {\n setMarkingAllRead(false)\n }\n }\n\n React.useEffect(() => {\n if (!open) return\n const prevOverflow = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n return () => {\n document.body.style.overflow = prevOverflow\n }\n }, [open])\n\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && open) {\n onOpenChange(false)\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [open, onOpenChange])\n\n if (!open) return null\n\n return (\n <>\n <div\n className=\"fixed inset-0 z-overlay bg-black/20\"\n onClick={() => onOpenChange(false)}\n aria-hidden=\"true\"\n />\n\n <div\n className=\"fixed right-0 top-0 z-modal h-full w-full max-w-md border-l bg-background shadow-lg\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={t('notifications.title', 'Notifications')}\n >\n <div className=\"flex h-full flex-col\">\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <Bell className=\"h-5 w-5\" />\n <h2 className=\"font-semibold\">{t('notifications.title', 'Notifications')}</h2>\n {unreadCount > 0 && (\n <span className=\"rounded-full bg-destructive px-2 py-0.5 text-xs text-destructive-foreground\">\n {unreadCount}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {unreadCount > 0 && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleMarkAllRead}\n disabled={markingAllRead}\n >\n {markingAllRead ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <CheckCheck className=\"mr-1 h-4 w-4\" />\n )}\n {t('notifications.markAllRead', 'Mark all read')}\n </Button>\n )}\n <IconButton variant=\"ghost\" size=\"lg\" onClick={() => onOpenChange(false)}>\n <X className=\"h-5 w-5\" />\n </IconButton>\n </div>\n </div>\n\n <Tabs\n value={filter}\n onValueChange={(v) => setFilter(v as typeof filter)}\n className=\"border-b\"\n >\n <TabsList className=\"w-full justify-start rounded-none border-0 bg-transparent px-4\">\n <TabsTrigger value=\"all\">\n {t('notifications.filters.all', 'All')}\n </TabsTrigger>\n <TabsTrigger value=\"unread\">\n {t('notifications.filters.unread', 'Unread')}\n </TabsTrigger>\n <TabsTrigger value=\"action\">\n {t('notifications.filters.actionRequired', 'Action Required')}\n </TabsTrigger>\n </TabsList>\n </Tabs>\n\n {dismissUndo && onUndoDismiss && (\n <div className=\"border-b bg-muted/50 px-4 py-2 text-sm\">\n <div className=\"flex items-center justify-between gap-3\">\n <span>\n {t('notifications.toast.dismissed', 'Notification dismissed')}\n </span>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onUndoDismiss()}>\n <RotateCcw className=\"mr-1 h-3 w-3\" />\n {t('notifications.actions.undo', 'Undo')}\n </Button>\n </div>\n </div>\n )}\n\n <div className=\"flex-1 overflow-y-auto overscroll-contain\">\n {filteredNotifications.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 text-muted-foreground\">\n <Bell className=\"mb-2 h-8 w-8 opacity-50\" />\n <p>{t('notifications.empty', 'No notifications')}</p>\n </div>\n ) : (\n <div className=\"divide-y\">\n {filteredNotifications.map((notification) => (\n <NotificationItem\n key={notification.id}\n notification={notification}\n onMarkAsRead={() => onMarkAsRead(notification.id)}\n onExecuteAction={(actionId) => onExecuteAction(notification.id, actionId)}\n onDismiss={() => onDismiss(notification.id)}\n t={t}\n customRenderer={customRenderers?.[notification.type]}\n />\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n </>\n )\n}\n"],
|
|
5
5
|
"mappings": ";AAwHI,mBACE,KAcM,YAfR;AAvHJ,YAAY,WAAW;AACvB,SAAS,GAAG,MAAM,YAAY,SAAS,iBAAiB;AACxD,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,MAAM,UAAU,mBAAmB;AAC5C,SAAS,wBAAwB;AAqD1B,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAsC,KAAK;AAC7E,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,KAAK;AAEhE,QAAM,wBAAwB,MAAM,QAAQ,MAAM;AAChD,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,cAAc,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ;AAAA,MAC1D,KAAK;AACH,eAAO,cAAc;AAAA,UACnB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,SAAS,KAAK,EAAE,WAAW;AAAA,QAC3D;AAAA,MACF;AACE,eAAO;AAAA,IACX;AAAA,EACF,GAAG,CAAC,eAAe,MAAM,CAAC;AAE1B,QAAM,oBAAoB,YAAY;AACpC,sBAAkB,IAAI;AACtB,QAAI;AACF,YAAM,cAAc;AAAA,IACtB,UAAE;AACA,wBAAkB,KAAK;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,UAAM,eAAe,SAAS,KAAK,MAAM;AACzC,aAAS,KAAK,MAAM,WAAW;AAC/B,WAAO,MAAM;AACX,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,UAAU,MAAM;AACpB,UAAM,gBAAgB,CAAC,UAAyB;AAC9C,UAAI,MAAM,QAAQ,YAAY,MAAM;AAClC,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AACA,aAAS,iBAAiB,WAAW,aAAa;AAClD,WAAO,MAAM,SAAS,oBAAoB,WAAW,aAAa;AAAA,EACpE,GAAG,CAAC,MAAM,YAAY,CAAC;AAEvB,MAAI,CAAC,KAAM,QAAO;AAElB,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,SAAS,MAAM,aAAa,KAAK;AAAA,QACjC,eAAY;AAAA;AAAA,IACd;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,MAAK;AAAA,QACL,cAAW;AAAA,QACX,cAAY,EAAE,uBAAuB,eAAe;AAAA,QAEpD,+BAAC,SAAI,WAAU,wBACb;AAAA,+BAAC,SAAI,WAAU,wDACb;AAAA,iCAAC,SAAI,WAAU,2BACb;AAAA,kCAAC,QAAK,WAAU,WAAU;AAAA,cAC1B,oBAAC,QAAG,WAAU,iBAAiB,YAAE,uBAAuB,eAAe,GAAE;AAAA,cACxE,cAAc,KACb,oBAAC,UAAK,WAAU,+EACb,uBACH;AAAA,eAEJ;AAAA,YACA,qBAAC,SAAI,WAAU,2BACZ;AAAA,4BAAc,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,SAAS;AAAA,kBACT,UAAU;AAAA,kBAET;AAAA,qCACC,oBAAC,WAAQ,WAAU,6BAA4B,IAE/C,oBAAC,cAAW,WAAU,gBAAe;AAAA,oBAEtC,EAAE,6BAA6B,eAAe;AAAA;AAAA;AAAA,cACjD;AAAA,cAEF,oBAAC,cAAW,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,aAAa,KAAK,GACrE,8BAAC,KAAE,WAAU,WAAU,GACzB;AAAA,eACF;AAAA,aACF;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,eAAe,CAAC,MAAM,UAAU,CAAkB;AAAA,cAClD,WAAU;AAAA,cAEV,+BAAC,YAAS,WAAU,kEAClB;AAAA,oCAAC,eAAY,OAAM,OAChB,YAAE,6BAA6B,KAAK,GACvC;AAAA,gBACA,oBAAC,eAAY,OAAM,UAChB,YAAE,gCAAgC,QAAQ,GAC7C;AAAA,gBACA,oBAAC,eAAY,OAAM,UAChB,YAAE,wCAAwC,iBAAiB,GAC9D;AAAA,iBACF;AAAA;AAAA,UACF;AAAA,UAEC,eAAe,iBACd,oBAAC,SAAI,WAAU,0CACb,+BAAC,SAAI,WAAU,2CACb;AAAA,gCAAC,UACE,YAAE,iCAAiC,wBAAwB,GAC9D;AAAA,YACA,qBAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,cAAc,GAC7D;AAAA,kCAAC,aAAU,WAAU,gBAAe;AAAA,cACnC,EAAE,8BAA8B,MAAM;AAAA,eACzC;AAAA,aACF,GACF;AAAA,UAGF,oBAAC,SAAI,WAAU,6CACZ,gCAAsB,WAAW,IAChC,qBAAC,SAAI,WAAU,yEACb;AAAA,gCAAC,QAAK,WAAU,2BAA0B;AAAA,YAC1C,oBAAC,OAAG,YAAE,uBAAuB,kBAAkB,GAAE;AAAA,aACnD,IAEA,oBAAC,SAAI,WAAU,YACZ,gCAAsB,IAAI,CAAC,iBAC1B;AAAA,YAAC;AAAA;AAAA,cAEC;AAAA,cACA,cAAc,MAAM,aAAa,aAAa,EAAE;AAAA,cAChD,iBAAiB,CAAC,aAAa,gBAAgB,aAAa,IAAI,QAAQ;AAAA,cACxE,WAAW,MAAM,UAAU,aAAa,EAAE;AAAA,cAC1C;AAAA,cACA,gBAAgB,kBAAkB,aAAa,IAAI;AAAA;AAAA,YAN9C,aAAa;AAAA,UAOpB,CACD,GACH,GAEJ;AAAA,WACF;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -39,7 +39,7 @@ function ProgressTopBar({ className, t }) {
|
|
|
39
39
|
activeJobs[0].totalCount && activeJobs[0].totalCount > 0 ? `(${activeJobs[0].progressPercent}%)` : `(${activeJobs[0].processedCount.toLocaleString()} ${t("progress.processed", "processed")})`
|
|
40
40
|
] })
|
|
41
41
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
42
|
-
/* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 text-
|
|
42
|
+
/* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 text-status-success-icon" }),
|
|
43
43
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("progress.recentlyCompleted", "{count} operations completed", { count: recentlyCompleted.length }) })
|
|
44
44
|
] }) }),
|
|
45
45
|
expanded ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
|
|
@@ -70,13 +70,13 @@ function ProgressJobCard({ job, t, onCancel }) {
|
|
|
70
70
|
return /* @__PURE__ */ jsxs("div", { className: cn(
|
|
71
71
|
"rounded-md border bg-card p-3",
|
|
72
72
|
isFailed && "border-destructive/50 bg-destructive/5",
|
|
73
|
-
isCompleted && "border-
|
|
73
|
+
isCompleted && "border-status-success-border bg-status-success-bg"
|
|
74
74
|
), children: [
|
|
75
75
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
76
76
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
77
77
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
78
78
|
isActive && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-primary flex-shrink-0" }),
|
|
79
|
-
isCompleted && /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 text-
|
|
79
|
+
isCompleted && /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 text-status-success-icon flex-shrink-0" }),
|
|
80
80
|
isFailed && /* @__PURE__ */ jsx(XCircle, { className: "h-4 w-4 text-destructive flex-shrink-0" }),
|
|
81
81
|
/* @__PURE__ */ jsx("span", { className: "font-medium truncate", children: job.name })
|
|
82
82
|
] }),
|
|
@@ -108,7 +108,7 @@ function ProgressJobCard({ job, t, onCancel }) {
|
|
|
108
108
|
function IndeterminateProgressBar({ className }) {
|
|
109
109
|
return /* @__PURE__ */ jsxs("div", { className: cn("relative w-full overflow-hidden rounded-full bg-secondary", className), children: [
|
|
110
110
|
/* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 left-0 w-1/2 animate-pulse rounded-full bg-primary/80" }),
|
|
111
|
-
/* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 right-0 w-1/3 rounded-full bg-primary/
|
|
111
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 right-0 w-1/3 rounded-full bg-primary/10" })
|
|
112
112
|
] });
|
|
113
113
|
}
|
|
114
114
|
function formatEta(seconds, t) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/progress/ProgressTopBar.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ChevronDown, ChevronUp, Loader2, CheckCircle, XCircle, X } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { Progress } from '../../primitives/progress'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useProgress } from './useProgress'\nimport type { ProgressJobDto } from './useProgressPoll'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '../utils/apiCall'\n\nexport type ProgressTopBarProps = {\n className?: string\n t: TranslateFn\n}\n\nexport function ProgressTopBar({ className, t }: ProgressTopBarProps) {\n const { activeJobs, recentlyCompleted, refresh } = useProgress()\n const [expanded, setExpanded] = React.useState(false)\n\n React.useEffect(() => {\n const saved = localStorage.getItem('om:progress:expanded')\n if (saved === 'true') setExpanded(true)\n }, [])\n\n React.useEffect(() => {\n localStorage.setItem('om:progress:expanded', String(expanded))\n }, [expanded])\n\n const hasActiveJobs = activeJobs.length > 0\n const hasRecentJobs = recentlyCompleted.length > 0\n\n if (!hasActiveJobs && !hasRecentJobs) return null\n\n return (\n <div className={cn('border-b bg-background', className)}>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => setExpanded(!expanded)}\n className=\"h-auto w-full justify-between rounded-none bg-background px-4 py-2 hover:bg-muted\"\n >\n <div className=\"flex items-center gap-2 text-sm\">\n {hasActiveJobs ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin text-primary\" />\n <span>\n {t('progress.activeCount', '{count} operations running', { count: activeJobs.length })}\n </span>\n {activeJobs[0] && (\n <span className=\"text-muted-foreground\">\n \u2014 {activeJobs[0].name}{' '}\n {activeJobs[0].totalCount && activeJobs[0].totalCount > 0\n ? `(${activeJobs[0].progressPercent}%)`\n : `(${activeJobs[0].processedCount.toLocaleString()} ${t('progress.processed', 'processed')})`}\n </span>\n )}\n </>\n ) : (\n <>\n <CheckCircle className=\"h-4 w-4 text-
|
|
5
|
-
"mappings": ";AA4CY,mBACE,KAKE,YANJ;AA3CZ,YAAY,WAAW;AACvB,SAAS,aAAa,WAAW,SAAS,aAAa,SAAS,SAAS;AACzE,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAG5B,SAAS,eAAe;AAOjB,SAAS,eAAe,EAAE,WAAW,EAAE,GAAwB;AACpE,QAAM,EAAE,YAAY,mBAAmB,QAAQ,IAAI,YAAY;AAC/D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AAEpD,QAAM,UAAU,MAAM;AACpB,UAAM,QAAQ,aAAa,QAAQ,sBAAsB;AACzD,QAAI,UAAU,OAAQ,aAAY,IAAI;AAAA,EACxC,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM;AACpB,iBAAa,QAAQ,wBAAwB,OAAO,QAAQ,CAAC;AAAA,EAC/D,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,gBAAgB,WAAW,SAAS;AAC1C,QAAM,gBAAgB,kBAAkB,SAAS;AAEjD,MAAI,CAAC,iBAAiB,CAAC,cAAe,QAAO;AAE7C,SACE,qBAAC,SAAI,WAAW,GAAG,0BAA0B,SAAS,GACpD;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,SAAS,MAAM,YAAY,CAAC,QAAQ;AAAA,QACpC,WAAU;AAAA,QAEV;AAAA,8BAAC,SAAI,WAAU,mCACZ,0BACC,iCACE;AAAA,gCAAC,WAAQ,WAAU,qCAAoC;AAAA,YACvD,oBAAC,UACE,YAAE,wBAAwB,8BAA8B,EAAE,OAAO,WAAW,OAAO,CAAC,GACvF;AAAA,YACC,WAAW,CAAC,KACX,qBAAC,UAAK,WAAU,yBAAwB;AAAA;AAAA,cACnC,WAAW,CAAC,EAAE;AAAA,cAAM;AAAA,cACtB,WAAW,CAAC,EAAE,cAAc,WAAW,CAAC,EAAE,aAAa,IACpD,IAAI,WAAW,CAAC,EAAE,eAAe,OACjC,IAAI,WAAW,CAAC,EAAE,eAAe,eAAe,CAAC,IAAI,EAAE,sBAAsB,WAAW,CAAC;AAAA,eAC/F;AAAA,aAEJ,IAEA,iCACE;AAAA,gCAAC,eAAY,WAAU,
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ChevronDown, ChevronUp, Loader2, CheckCircle, XCircle, X } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { Progress } from '../../primitives/progress'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useProgress } from './useProgress'\nimport type { ProgressJobDto } from './useProgressPoll'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '../utils/apiCall'\n\nexport type ProgressTopBarProps = {\n className?: string\n t: TranslateFn\n}\n\nexport function ProgressTopBar({ className, t }: ProgressTopBarProps) {\n const { activeJobs, recentlyCompleted, refresh } = useProgress()\n const [expanded, setExpanded] = React.useState(false)\n\n React.useEffect(() => {\n const saved = localStorage.getItem('om:progress:expanded')\n if (saved === 'true') setExpanded(true)\n }, [])\n\n React.useEffect(() => {\n localStorage.setItem('om:progress:expanded', String(expanded))\n }, [expanded])\n\n const hasActiveJobs = activeJobs.length > 0\n const hasRecentJobs = recentlyCompleted.length > 0\n\n if (!hasActiveJobs && !hasRecentJobs) return null\n\n return (\n <div className={cn('border-b bg-background', className)}>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={() => setExpanded(!expanded)}\n className=\"h-auto w-full justify-between rounded-none bg-background px-4 py-2 hover:bg-muted\"\n >\n <div className=\"flex items-center gap-2 text-sm\">\n {hasActiveJobs ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin text-primary\" />\n <span>\n {t('progress.activeCount', '{count} operations running', { count: activeJobs.length })}\n </span>\n {activeJobs[0] && (\n <span className=\"text-muted-foreground\">\n \u2014 {activeJobs[0].name}{' '}\n {activeJobs[0].totalCount && activeJobs[0].totalCount > 0\n ? `(${activeJobs[0].progressPercent}%)`\n : `(${activeJobs[0].processedCount.toLocaleString()} ${t('progress.processed', 'processed')})`}\n </span>\n )}\n </>\n ) : (\n <>\n <CheckCircle className=\"h-4 w-4 text-status-success-icon\" />\n <span className=\"text-muted-foreground\">\n {t('progress.recentlyCompleted', '{count} operations completed', { count: recentlyCompleted.length })}\n </span>\n </>\n )}\n </div>\n {expanded ? (\n <ChevronUp className=\"h-4 w-4\" />\n ) : (\n <ChevronDown className=\"h-4 w-4\" />\n )}\n </Button>\n\n {expanded && (\n <div className=\"space-y-2 bg-background px-4 pb-3\">\n {activeJobs.map((job) => (\n <ProgressJobCard key={job.id} job={job} t={t} onCancel={refresh} />\n ))}\n {recentlyCompleted.map((job) => (\n <ProgressJobCard key={job.id} job={job} t={t} />\n ))}\n </div>\n )}\n </div>\n )\n}\n\nfunction ProgressJobCard({ job, t, onCancel }: { job: ProgressJobDto; t: TranslateFn; onCancel?: () => void }) {\n const [cancelling, setCancelling] = React.useState(false)\n\n const handleCancel = async () => {\n if (!job.cancellable || cancelling) return\n setCancelling(true)\n try {\n await apiCall(`/api/progress/jobs/${job.id}`, { method: 'DELETE' })\n onCancel?.()\n } finally {\n setCancelling(false)\n }\n }\n\n const isActive = job.status === 'pending' || job.status === 'running'\n const isFailed = job.status === 'failed'\n const isCompleted = job.status === 'completed'\n\n return (\n <div className={cn(\n 'rounded-md border bg-card p-3',\n isFailed && 'border-destructive/50 bg-destructive/5',\n isCompleted && 'border-status-success-border bg-status-success-bg',\n )}>\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n {isActive && <Loader2 className=\"h-4 w-4 animate-spin text-primary flex-shrink-0\" />}\n {isCompleted && <CheckCircle className=\"h-4 w-4 text-status-success-icon flex-shrink-0\" />}\n {isFailed && <XCircle className=\"h-4 w-4 text-destructive flex-shrink-0\" />}\n <span className=\"font-medium truncate\">{job.name}</span>\n </div>\n\n {job.description && (\n <p className=\"text-sm text-muted-foreground mt-1 truncate\">{job.description}</p>\n )}\n\n {isFailed && job.errorMessage && (\n <p className=\"text-sm text-destructive mt-1\">{job.errorMessage}</p>\n )}\n </div>\n\n {isActive && job.cancellable && (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleCancel}\n disabled={cancelling}\n className=\"flex-shrink-0\"\n aria-label={t('progress.actions.cancel', 'Cancel')}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n\n {isActive && (\n <div className=\"mt-2 space-y-1\">\n {job.totalCount && job.totalCount > 0 ? (\n <Progress value={job.progressPercent} className=\"h-2\" />\n ) : (\n <IndeterminateProgressBar className=\"h-2\" />\n )}\n <div className=\"flex justify-between text-xs text-muted-foreground\">\n <span>\n {job.totalCount\n ? `${job.processedCount.toLocaleString()} / ${job.totalCount.toLocaleString()}`\n : `${job.processedCount.toLocaleString()} ${t('progress.processed', 'processed')}`\n }\n </span>\n {job.etaSeconds != null && job.etaSeconds > 0 && (\n <span>{formatEta(job.etaSeconds, t)}</span>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\nfunction IndeterminateProgressBar({ className }: { className?: string }) {\n return (\n <div className={cn('relative w-full overflow-hidden rounded-full bg-secondary', className)}>\n <div className=\"absolute inset-y-0 left-0 w-1/2 animate-pulse rounded-full bg-primary/80\" />\n <div className=\"absolute inset-y-0 right-0 w-1/3 rounded-full bg-primary/10\" />\n </div>\n )\n}\n\nfunction formatEta(seconds: number, t: TranslateFn): string {\n if (seconds < 60) {\n return t('progress.eta.seconds', '{count}s remaining', { count: seconds })\n }\n if (seconds < 3600) {\n const minutes = Math.ceil(seconds / 60)\n return t('progress.eta.minutes', '{count}m remaining', { count: minutes })\n }\n const hours = Math.floor(seconds / 3600)\n const mins = Math.ceil((seconds % 3600) / 60)\n return t('progress.eta.hoursMinutes', '{hours}h {minutes}m remaining', { hours, minutes: mins })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -89,7 +89,7 @@ function ScheduleAgenda({ items, range, timezone, onItemClick, onSlotClick, clas
|
|
|
89
89
|
/* @__PURE__ */ jsx("span", { className: "font-semibold", children: item.title }),
|
|
90
90
|
statusLabel ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: statusLabel }) : null
|
|
91
91
|
] }),
|
|
92
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-
|
|
92
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-overline text-muted-foreground", children: [
|
|
93
93
|
/* @__PURE__ */ jsx("span", { children: formatTimeRange(item, timezone) }),
|
|
94
94
|
/* @__PURE__ */ jsx("span", { className: "capitalize", children: item.kind })
|
|
95
95
|
] })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/schedule/ScheduleAgenda.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '../../primitives/badge'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { expandRecurringItems } from './recurrence'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction startOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate())\n}\n\nfunction endOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate(), 23, 59, 59, 999)\n}\n\nfunction eachDay(start: Date, end: Date): Date[] {\n const days: Date[] = []\n let cursor = startOfDay(start)\n const last = startOfDay(end)\n while (cursor <= last) {\n days.push(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n }\n return days\n}\n\nfunction overlapsDay(item: ScheduleItem, day: Date): boolean {\n const dayStart = startOfDay(day)\n const dayEnd = endOfDay(day)\n return item.startsAt <= dayEnd && item.endsAt >= dayStart\n}\n\nfunction formatDayLabel(day: Date): string {\n return day.toLocaleDateString(undefined, { weekday: 'long', month: 'short', day: 'numeric' })\n}\n\nfunction formatTimeRange(item: ScheduleItem, timezone?: string): string {\n const options: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' }\n if (timezone) options.timeZone = timezone\n const startLabel = item.startsAt.toLocaleTimeString(undefined, options)\n const endLabel = item.endsAt.toLocaleTimeString(undefined, options)\n return `${startLabel}-${endLabel}`\n}\n\nfunction getStatusLabel(status: ScheduleItem['status'], t: (key: string, fallback?: string) => string): string | null {\n if (!status) return null\n if (status === 'draft') return t('schedule.item.status.draft', 'Draft')\n if (status === 'negotiation') return t('schedule.item.status.negotiation', 'Negotiation')\n if (status === 'confirmed') return t('schedule.item.status.confirmed', 'Confirmed')\n if (status === 'cancelled') return t('schedule.item.status.cancelled', 'Cancelled')\n return null\n}\n\nfunction getKindStyles(kind: ScheduleItem['kind']): string {\n if (kind === 'event') return 'border-blue-500/40 bg-blue-500/10 text-blue-950'\n if (kind === 'exception') return 'border-amber-500/40 bg-amber-500/10 text-amber-950'\n return 'border-emerald-500/40 bg-emerald-500/10 text-emerald-950'\n}\n\nexport type ScheduleAgendaProps = {\n items: ScheduleItem[]\n range: ScheduleRange\n timezone?: string\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n className?: string\n}\n\nexport function ScheduleAgenda({ items, range, timezone, onItemClick, onSlotClick, className }: ScheduleAgendaProps) {\n const t = useT()\n const days = React.useMemo(() => eachDay(range.start, range.end), [range])\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n\n return (\n <div className={cn('space-y-4', className)}>\n {days.map((day) => {\n const dayItems = expandedItems.filter((item) => overlapsDay(item, day))\n const slotStart = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9, 0, 0)\n const slotEnd = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10, 0, 0)\n return (\n <div key={day.toISOString()} className=\"rounded-xl border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold text-foreground\">{formatDayLabel(day)}</div>\n {onSlotClick ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSlotClick({ start: slotStart, end: slotEnd })}\n >\n {t('schedule.actions.add', 'Add')}\n </Button>\n ) : null}\n </div>\n <div className=\"mt-3 space-y-2\">\n {dayItems.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-3 text-xs text-muted-foreground\">\n {t('schedule.emptyDay', 'No scheduled items')}\n </div>\n ) : (\n dayItems.map((item) => {\n const statusLabel = getStatusLabel(item.status, t)\n return (\n <button\n key={item.id}\n type=\"button\"\n className={cn(\n 'flex w-full cursor-pointer flex-col gap-2 rounded-lg border px-3 py-2 text-left text-xs transition hover:shadow-sm',\n getKindStyles(item.kind)\n )}\n onClick={() => onItemClick?.(item)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">{item.title}</span>\n {statusLabel ? <Badge variant=\"secondary\">{statusLabel}</Badge> : null}\n </div>\n <div className=\"flex items-center justify-between text-
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '../../primitives/badge'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { expandRecurringItems } from './recurrence'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction startOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate())\n}\n\nfunction endOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate(), 23, 59, 59, 999)\n}\n\nfunction eachDay(start: Date, end: Date): Date[] {\n const days: Date[] = []\n let cursor = startOfDay(start)\n const last = startOfDay(end)\n while (cursor <= last) {\n days.push(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n }\n return days\n}\n\nfunction overlapsDay(item: ScheduleItem, day: Date): boolean {\n const dayStart = startOfDay(day)\n const dayEnd = endOfDay(day)\n return item.startsAt <= dayEnd && item.endsAt >= dayStart\n}\n\nfunction formatDayLabel(day: Date): string {\n return day.toLocaleDateString(undefined, { weekday: 'long', month: 'short', day: 'numeric' })\n}\n\nfunction formatTimeRange(item: ScheduleItem, timezone?: string): string {\n const options: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' }\n if (timezone) options.timeZone = timezone\n const startLabel = item.startsAt.toLocaleTimeString(undefined, options)\n const endLabel = item.endsAt.toLocaleTimeString(undefined, options)\n return `${startLabel}-${endLabel}`\n}\n\nfunction getStatusLabel(status: ScheduleItem['status'], t: (key: string, fallback?: string) => string): string | null {\n if (!status) return null\n if (status === 'draft') return t('schedule.item.status.draft', 'Draft')\n if (status === 'negotiation') return t('schedule.item.status.negotiation', 'Negotiation')\n if (status === 'confirmed') return t('schedule.item.status.confirmed', 'Confirmed')\n if (status === 'cancelled') return t('schedule.item.status.cancelled', 'Cancelled')\n return null\n}\n\nfunction getKindStyles(kind: ScheduleItem['kind']): string {\n if (kind === 'event') return 'border-blue-500/40 bg-blue-500/10 text-blue-950'\n if (kind === 'exception') return 'border-amber-500/40 bg-amber-500/10 text-amber-950'\n return 'border-emerald-500/40 bg-emerald-500/10 text-emerald-950'\n}\n\nexport type ScheduleAgendaProps = {\n items: ScheduleItem[]\n range: ScheduleRange\n timezone?: string\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n className?: string\n}\n\nexport function ScheduleAgenda({ items, range, timezone, onItemClick, onSlotClick, className }: ScheduleAgendaProps) {\n const t = useT()\n const days = React.useMemo(() => eachDay(range.start, range.end), [range])\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n\n return (\n <div className={cn('space-y-4', className)}>\n {days.map((day) => {\n const dayItems = expandedItems.filter((item) => overlapsDay(item, day))\n const slotStart = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9, 0, 0)\n const slotEnd = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10, 0, 0)\n return (\n <div key={day.toISOString()} className=\"rounded-xl border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold text-foreground\">{formatDayLabel(day)}</div>\n {onSlotClick ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSlotClick({ start: slotStart, end: slotEnd })}\n >\n {t('schedule.actions.add', 'Add')}\n </Button>\n ) : null}\n </div>\n <div className=\"mt-3 space-y-2\">\n {dayItems.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-3 text-xs text-muted-foreground\">\n {t('schedule.emptyDay', 'No scheduled items')}\n </div>\n ) : (\n dayItems.map((item) => {\n const statusLabel = getStatusLabel(item.status, t)\n return (\n <button\n key={item.id}\n type=\"button\"\n className={cn(\n 'flex w-full cursor-pointer flex-col gap-2 rounded-lg border px-3 py-2 text-left text-xs transition hover:shadow-sm',\n getKindStyles(item.kind)\n )}\n onClick={() => onItemClick?.(item)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">{item.title}</span>\n {statusLabel ? <Badge variant=\"secondary\">{statusLabel}</Badge> : null}\n </div>\n <div className=\"flex items-center justify-between text-overline text-muted-foreground\">\n <span>{formatTimeRange(item, timezone)}</span>\n <span className=\"capitalize\">{item.kind}</span>\n </div>\n </button>\n )\n })\n )}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAsFY,SACE,KADF;AApFZ,YAAY,WAAW;AAEvB,SAAS,UAAU;AACnB,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,4BAA4B;AAErC,MAAM,SAAS,KAAK,KAAK,KAAK;AAE9B,SAAS,WAAW,OAAmB;AACrC,SAAO,IAAI,KAAK,MAAM,YAAY,GAAG,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC;AACxE;AAEA,SAAS,SAAS,OAAmB;AACnC,SAAO,IAAI,KAAK,MAAM,YAAY,GAAG,MAAM,SAAS,GAAG,MAAM,QAAQ,GAAG,IAAI,IAAI,IAAI,GAAG;AACzF;AAEA,SAAS,QAAQ,OAAa,KAAmB;AAC/C,QAAM,OAAe,CAAC;AACtB,MAAI,SAAS,WAAW,KAAK;AAC7B,QAAM,OAAO,WAAW,GAAG;AAC3B,SAAO,UAAU,MAAM;AACrB,SAAK,KAAK,IAAI,KAAK,MAAM,CAAC;AAC1B,aAAS,IAAI,KAAK,OAAO,QAAQ,IAAI,MAAM;AAAA,EAC7C;AACA,SAAO;AACT;AAEA,SAAS,YAAY,MAAoB,KAAoB;AAC3D,QAAM,WAAW,WAAW,GAAG;AAC/B,QAAM,SAAS,SAAS,GAAG;AAC3B,SAAO,KAAK,YAAY,UAAU,KAAK,UAAU;AACnD;AAEA,SAAS,eAAe,KAAmB;AACzC,SAAO,IAAI,mBAAmB,QAAW,EAAE,SAAS,QAAQ,OAAO,SAAS,KAAK,UAAU,CAAC;AAC9F;AAEA,SAAS,gBAAgB,MAAoB,UAA2B;AACtE,QAAM,UAAsC,EAAE,MAAM,WAAW,QAAQ,UAAU;AACjF,MAAI,SAAU,SAAQ,WAAW;AACjC,QAAM,aAAa,KAAK,SAAS,mBAAmB,QAAW,OAAO;AACtE,QAAM,WAAW,KAAK,OAAO,mBAAmB,QAAW,OAAO;AAClE,SAAO,GAAG,UAAU,IAAI,QAAQ;AAClC;AAEA,SAAS,eAAe,QAAgC,GAA8D;AACpH,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,WAAW,QAAS,QAAO,EAAE,8BAA8B,OAAO;AACtE,MAAI,WAAW,cAAe,QAAO,EAAE,oCAAoC,aAAa;AACxF,MAAI,WAAW,YAAa,QAAO,EAAE,kCAAkC,WAAW;AAClF,MAAI,WAAW,YAAa,QAAO,EAAE,kCAAkC,WAAW;AAClF,SAAO;AACT;AAEA,SAAS,cAAc,MAAoC;AACzD,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,SAAS,YAAa,QAAO;AACjC,SAAO;AACT;AAWO,SAAS,eAAe,EAAE,OAAO,OAAO,UAAU,aAAa,aAAa,UAAU,GAAwB;AACnH,QAAM,IAAI,KAAK;AACf,QAAM,OAAO,MAAM,QAAQ,MAAM,QAAQ,MAAM,OAAO,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC;AACzE,QAAM,gBAAgB,MAAM,QAAQ,MAAM,qBAAqB,OAAO,KAAK,GAAG,CAAC,OAAO,KAAK,CAAC;AAE5F,SACE,oBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GACtC,eAAK,IAAI,CAAC,QAAQ;AACjB,UAAM,WAAW,cAAc,OAAO,CAAC,SAAS,YAAY,MAAM,GAAG,CAAC;AACtE,UAAM,YAAY,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,SAAS,GAAG,IAAI,QAAQ,GAAG,GAAG,GAAG,CAAC;AACpF,UAAM,UAAU,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,SAAS,GAAG,IAAI,QAAQ,GAAG,IAAI,GAAG,CAAC;AACnF,WACE,qBAAC,SAA4B,WAAU,iCACrC;AAAA,2BAAC,SAAI,WAAU,2CACb;AAAA,4BAAC,SAAI,WAAU,yCAAyC,yBAAe,GAAG,GAAE;AAAA,QAC3E,cACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,MAAM,YAAY,EAAE,OAAO,WAAW,KAAK,QAAQ,CAAC;AAAA,YAE5D,YAAE,wBAAwB,KAAK;AAAA;AAAA,QAClC,IACE;AAAA,SACN;AAAA,MACA,oBAAC,SAAI,WAAU,kBACZ,mBAAS,WAAW,IACnB,oBAAC,SAAI,WAAU,qEACZ,YAAE,qBAAqB,oBAAoB,GAC9C,IAEA,SAAS,IAAI,CAAC,SAAS;AACrB,cAAM,cAAc,eAAe,KAAK,QAAQ,CAAC;AACjD,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,WAAW;AAAA,cACT;AAAA,cACA,cAAc,KAAK,IAAI;AAAA,YACzB;AAAA,YACA,SAAS,MAAM,cAAc,IAAI;AAAA,YAEjC;AAAA,mCAAC,SAAI,WAAU,2CACb;AAAA,oCAAC,UAAK,WAAU,iBAAiB,eAAK,OAAM;AAAA,gBAC3C,cAAc,oBAAC,SAAM,SAAQ,aAAa,uBAAY,IAAW;AAAA,iBACpE;AAAA,cACA,qBAAC,SAAI,WAAU,yEACb;AAAA,oCAAC,UAAM,0BAAgB,MAAM,QAAQ,GAAE;AAAA,gBACvC,oBAAC,UAAK,WAAU,cAAc,eAAK,MAAK;AAAA,iBAC1C;AAAA;AAAA;AAAA,UAfK,KAAK;AAAA,QAgBZ;AAAA,MAEJ,CAAC,GAEL;AAAA,SA5CQ,IAAI,YAAY,CA6C1B;AAAA,EAEJ,CAAC,GACH;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -123,7 +123,7 @@ function ScheduleCalendar({
|
|
|
123
123
|
type: "button",
|
|
124
124
|
variant: "link",
|
|
125
125
|
size: "sm",
|
|
126
|
-
className: "h-auto p-0 text-
|
|
126
|
+
className: "h-auto p-0 text-overline",
|
|
127
127
|
onClick: (clickEvent) => {
|
|
128
128
|
clickEvent.stopPropagation();
|
|
129
129
|
onItemClick?.(resource);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/schedule/ScheduleCalendar.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Calendar, dateFnsLocalizer, type View, type SlotInfo } from 'react-big-calendar'\nimport { addDays, differenceInCalendarDays, endOfDay, endOfMonth, endOfWeek, format, getDay, parse, startOfDay, startOfMonth, startOfWeek } from 'date-fns'\nimport { enUS } from 'date-fns/locale/en-US'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot, ScheduleViewMode } from './types'\nimport { Button } from '../../primitives/button'\nimport { expandRecurringItems } from './recurrence'\n\ntype CalendarEvent = {\n id: string\n title: string\n start: Date\n end: Date\n resource: ScheduleItem\n}\n\nconst localizer = dateFnsLocalizer({\n format,\n parse,\n startOfWeek,\n getDay,\n locales: { 'en-US': enUS },\n})\n\nconst VIEW_MAP: Record<ScheduleViewMode, View> = {\n day: 'day',\n week: 'week',\n month: 'month',\n agenda: 'agenda',\n}\n\nfunction deriveRange(date: Date, view: ScheduleViewMode, agendaLength: number): ScheduleRange {\n if (view === 'day') {\n return { start: startOfDay(date), end: endOfDay(date) }\n }\n if (view === 'week') {\n return { start: startOfWeek(date, { locale: enUS }), end: endOfWeek(date, { locale: enUS }) }\n }\n if (view === 'month') {\n return { start: startOfMonth(date), end: endOfMonth(date) }\n }\n const length = Math.max(1, agendaLength)\n return { start: startOfDay(date), end: endOfDay(addDays(date, length - 1)) }\n}\n\nfunction normalizeRange(\n nextRange: Date[] | { start: Date; end: Date } | null | undefined,\n view: ScheduleViewMode,\n agendaLength: number,\n): ScheduleRange | null {\n if (!nextRange) return null\n if (Array.isArray(nextRange)) {\n if (nextRange.length === 0) return null\n if (view === 'agenda') {\n return { start: nextRange[0], end: nextRange[nextRange.length - 1] }\n }\n return deriveRange(nextRange[0], view, agendaLength)\n }\n if (nextRange.start && nextRange.end) return { start: nextRange.start, end: nextRange.end }\n return deriveRange(new Date(), view, agendaLength)\n}\n\nfunction getEventStyles(item: ScheduleItem): React.CSSProperties {\n if (item.kind === 'event') {\n return { backgroundColor: 'rgba(59, 130, 246, 0.15)', border: '1px solid rgba(59, 130, 246, 0.5)', color: '#1e3a8a' }\n }\n if (item.kind === 'exception') {\n return { backgroundColor: 'rgba(148, 163, 184, 0.2)', border: '1px solid rgba(100, 116, 139, 0.6)', color: '#334155' }\n }\n return { backgroundColor: 'rgba(16, 185, 129, 0.15)', border: '1px solid rgba(16, 185, 129, 0.5)', color: '#064e3b' }\n}\n\nexport type ScheduleCalendarProps = {\n items: ScheduleItem[]\n view: ScheduleViewMode\n range: ScheduleRange\n onRangeChange: (range: ScheduleRange) => void\n onViewChange: (view: ScheduleViewMode) => void\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n}\n\nexport default function ScheduleCalendar({\n items,\n view,\n range,\n onRangeChange,\n onViewChange,\n onItemClick,\n onSlotClick,\n}: ScheduleCalendarProps) {\n const agendaLength = React.useMemo(\n () => Math.max(1, differenceInCalendarDays(range.end, range.start) + 1),\n [range.end, range.start],\n )\n const currentView = VIEW_MAP[view]\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n const events = React.useMemo<CalendarEvent[]>(\n () => expandedItems.map((item) => ({\n id: item.id,\n title: item.title,\n start: item.startsAt,\n end: item.endsAt,\n resource: item,\n })),\n [expandedItems],\n )\n\n const handleNavigate = React.useCallback((date: Date, nextView?: View) => {\n const resolvedView = (nextView ?? currentView) as ScheduleViewMode\n onRangeChange(deriveRange(date, resolvedView, agendaLength))\n }, [agendaLength, currentView, onRangeChange])\n\n const handleRangeChange = React.useCallback((nextRange: Date[] | { start: Date; end: Date }, nextView?: View) => {\n const resolvedView = (nextView ?? currentView) as ScheduleViewMode\n const normalized = normalizeRange(nextRange, resolvedView, agendaLength)\n if (normalized) onRangeChange(normalized)\n }, [agendaLength, currentView, onRangeChange])\n\n const handleViewChange = React.useCallback((nextView: View) => {\n const resolved = nextView as ScheduleViewMode\n if (resolved !== view) {\n onViewChange(resolved)\n onRangeChange(deriveRange(new Date(), resolved, agendaLength))\n }\n }, [agendaLength, onRangeChange, onViewChange, view])\n\n const handleSelectEvent = React.useCallback(\n (event: CalendarEvent) => onItemClick?.(event.resource),\n [onItemClick],\n )\n\n const handleSelectSlot = React.useCallback(\n (slot: SlotInfo) => {\n if (!onSlotClick) return\n onSlotClick({ start: slot.start, end: slot.end })\n },\n [onSlotClick],\n )\n\n const eventPropGetter = React.useCallback(\n (event: CalendarEvent) => ({ style: getEventStyles(event.resource) }),\n [],\n )\n\n const calendarStyle = React.useMemo<React.CSSProperties>(() => ({ height: 640 }), [])\n\n const components = React.useMemo(\n () => ({\n event: ({ event }: { event: CalendarEvent }) => {\n const resource = event.resource\n const hasLink = Boolean(resource.linkLabel) && typeof onItemClick === 'function'\n return (\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"truncate text-xs font-medium\">{resource.title}</span>\n {hasLink ? (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n className=\"h-auto p-0 text-
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Calendar, dateFnsLocalizer, type View, type SlotInfo } from 'react-big-calendar'\nimport { addDays, differenceInCalendarDays, endOfDay, endOfMonth, endOfWeek, format, getDay, parse, startOfDay, startOfMonth, startOfWeek } from 'date-fns'\nimport { enUS } from 'date-fns/locale/en-US'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot, ScheduleViewMode } from './types'\nimport { Button } from '../../primitives/button'\nimport { expandRecurringItems } from './recurrence'\n\ntype CalendarEvent = {\n id: string\n title: string\n start: Date\n end: Date\n resource: ScheduleItem\n}\n\nconst localizer = dateFnsLocalizer({\n format,\n parse,\n startOfWeek,\n getDay,\n locales: { 'en-US': enUS },\n})\n\nconst VIEW_MAP: Record<ScheduleViewMode, View> = {\n day: 'day',\n week: 'week',\n month: 'month',\n agenda: 'agenda',\n}\n\nfunction deriveRange(date: Date, view: ScheduleViewMode, agendaLength: number): ScheduleRange {\n if (view === 'day') {\n return { start: startOfDay(date), end: endOfDay(date) }\n }\n if (view === 'week') {\n return { start: startOfWeek(date, { locale: enUS }), end: endOfWeek(date, { locale: enUS }) }\n }\n if (view === 'month') {\n return { start: startOfMonth(date), end: endOfMonth(date) }\n }\n const length = Math.max(1, agendaLength)\n return { start: startOfDay(date), end: endOfDay(addDays(date, length - 1)) }\n}\n\nfunction normalizeRange(\n nextRange: Date[] | { start: Date; end: Date } | null | undefined,\n view: ScheduleViewMode,\n agendaLength: number,\n): ScheduleRange | null {\n if (!nextRange) return null\n if (Array.isArray(nextRange)) {\n if (nextRange.length === 0) return null\n if (view === 'agenda') {\n return { start: nextRange[0], end: nextRange[nextRange.length - 1] }\n }\n return deriveRange(nextRange[0], view, agendaLength)\n }\n if (nextRange.start && nextRange.end) return { start: nextRange.start, end: nextRange.end }\n return deriveRange(new Date(), view, agendaLength)\n}\n\nfunction getEventStyles(item: ScheduleItem): React.CSSProperties {\n if (item.kind === 'event') {\n return { backgroundColor: 'rgba(59, 130, 246, 0.15)', border: '1px solid rgba(59, 130, 246, 0.5)', color: '#1e3a8a' }\n }\n if (item.kind === 'exception') {\n return { backgroundColor: 'rgba(148, 163, 184, 0.2)', border: '1px solid rgba(100, 116, 139, 0.6)', color: '#334155' }\n }\n return { backgroundColor: 'rgba(16, 185, 129, 0.15)', border: '1px solid rgba(16, 185, 129, 0.5)', color: '#064e3b' }\n}\n\nexport type ScheduleCalendarProps = {\n items: ScheduleItem[]\n view: ScheduleViewMode\n range: ScheduleRange\n onRangeChange: (range: ScheduleRange) => void\n onViewChange: (view: ScheduleViewMode) => void\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n}\n\nexport default function ScheduleCalendar({\n items,\n view,\n range,\n onRangeChange,\n onViewChange,\n onItemClick,\n onSlotClick,\n}: ScheduleCalendarProps) {\n const agendaLength = React.useMemo(\n () => Math.max(1, differenceInCalendarDays(range.end, range.start) + 1),\n [range.end, range.start],\n )\n const currentView = VIEW_MAP[view]\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n const events = React.useMemo<CalendarEvent[]>(\n () => expandedItems.map((item) => ({\n id: item.id,\n title: item.title,\n start: item.startsAt,\n end: item.endsAt,\n resource: item,\n })),\n [expandedItems],\n )\n\n const handleNavigate = React.useCallback((date: Date, nextView?: View) => {\n const resolvedView = (nextView ?? currentView) as ScheduleViewMode\n onRangeChange(deriveRange(date, resolvedView, agendaLength))\n }, [agendaLength, currentView, onRangeChange])\n\n const handleRangeChange = React.useCallback((nextRange: Date[] | { start: Date; end: Date }, nextView?: View) => {\n const resolvedView = (nextView ?? currentView) as ScheduleViewMode\n const normalized = normalizeRange(nextRange, resolvedView, agendaLength)\n if (normalized) onRangeChange(normalized)\n }, [agendaLength, currentView, onRangeChange])\n\n const handleViewChange = React.useCallback((nextView: View) => {\n const resolved = nextView as ScheduleViewMode\n if (resolved !== view) {\n onViewChange(resolved)\n onRangeChange(deriveRange(new Date(), resolved, agendaLength))\n }\n }, [agendaLength, onRangeChange, onViewChange, view])\n\n const handleSelectEvent = React.useCallback(\n (event: CalendarEvent) => onItemClick?.(event.resource),\n [onItemClick],\n )\n\n const handleSelectSlot = React.useCallback(\n (slot: SlotInfo) => {\n if (!onSlotClick) return\n onSlotClick({ start: slot.start, end: slot.end })\n },\n [onSlotClick],\n )\n\n const eventPropGetter = React.useCallback(\n (event: CalendarEvent) => ({ style: getEventStyles(event.resource) }),\n [],\n )\n\n const calendarStyle = React.useMemo<React.CSSProperties>(() => ({ height: 640 }), [])\n\n const components = React.useMemo(\n () => ({\n event: ({ event }: { event: CalendarEvent }) => {\n const resource = event.resource\n const hasLink = Boolean(resource.linkLabel) && typeof onItemClick === 'function'\n return (\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"truncate text-xs font-medium\">{resource.title}</span>\n {hasLink ? (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n className=\"h-auto p-0 text-overline\"\n onClick={(clickEvent) => {\n clickEvent.stopPropagation()\n onItemClick?.(resource)\n }}\n >\n {resource.linkLabel}\n </Button>\n ) : null}\n </div>\n )\n },\n }),\n [onItemClick],\n )\n\n return (\n <Calendar\n localizer={localizer}\n culture=\"en-US\"\n events={events}\n view={currentView}\n date={range.start}\n toolbar={false}\n selectable={Boolean(onSlotClick)}\n popup\n length={agendaLength}\n onView={handleViewChange}\n onNavigate={handleNavigate}\n onRangeChange={handleRangeChange}\n onSelectEvent={handleSelectEvent}\n onSelectSlot={handleSelectSlot}\n eventPropGetter={eventPropGetter}\n components={components}\n style={calendarStyle}\n />\n )\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -89,7 +89,7 @@ function ScheduleGrid({ items, range, timezone, onItemClick, onSlotClick, classN
|
|
|
89
89
|
/* @__PURE__ */ jsx("span", { className: "font-semibold", children: item.title }),
|
|
90
90
|
statusLabel ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: statusLabel }) : null
|
|
91
91
|
] }),
|
|
92
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-
|
|
92
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-overline text-muted-foreground", children: [
|
|
93
93
|
/* @__PURE__ */ jsx("span", { children: formatTimeRange(item, timezone) }),
|
|
94
94
|
/* @__PURE__ */ jsx("span", { className: "capitalize", children: item.kind })
|
|
95
95
|
] })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/schedule/ScheduleGrid.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '../../primitives/badge'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { expandRecurringItems } from './recurrence'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction startOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate())\n}\n\nfunction endOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate(), 23, 59, 59, 999)\n}\n\nfunction eachDay(start: Date, end: Date): Date[] {\n const days: Date[] = []\n let cursor = startOfDay(start)\n const last = startOfDay(end)\n while (cursor <= last) {\n days.push(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n }\n return days\n}\n\nfunction overlapsDay(item: ScheduleItem, day: Date): boolean {\n const dayStart = startOfDay(day)\n const dayEnd = endOfDay(day)\n return item.startsAt <= dayEnd && item.endsAt >= dayStart\n}\n\nfunction formatDayLabel(day: Date): string {\n return day.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })\n}\n\nfunction formatTimeRange(item: ScheduleItem, timezone?: string): string {\n const options: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' }\n if (timezone) options.timeZone = timezone\n const startLabel = item.startsAt.toLocaleTimeString(undefined, options)\n const endLabel = item.endsAt.toLocaleTimeString(undefined, options)\n return `${startLabel}-${endLabel}`\n}\n\nfunction getStatusLabel(status: ScheduleItem['status'], t: (key: string, fallback?: string) => string): string | null {\n if (!status) return null\n if (status === 'draft') return t('schedule.item.status.draft', 'Draft')\n if (status === 'negotiation') return t('schedule.item.status.negotiation', 'Negotiation')\n if (status === 'confirmed') return t('schedule.item.status.confirmed', 'Confirmed')\n if (status === 'cancelled') return t('schedule.item.status.cancelled', 'Cancelled')\n return null\n}\n\nfunction getKindStyles(kind: ScheduleItem['kind']): string {\n if (kind === 'event') return 'border-blue-500/40 bg-blue-500/10 text-blue-950'\n if (kind === 'exception') return 'border-amber-500/40 bg-amber-500/10 text-amber-950'\n return 'border-emerald-500/40 bg-emerald-500/10 text-emerald-950'\n}\n\nexport type ScheduleGridProps = {\n items: ScheduleItem[]\n range: ScheduleRange\n timezone?: string\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n className?: string\n}\n\nexport function ScheduleGrid({ items, range, timezone, onItemClick, onSlotClick, className }: ScheduleGridProps) {\n const t = useT()\n const days = React.useMemo(() => eachDay(range.start, range.end), [range])\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n\n return (\n <div className={cn('grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3', className)}>\n {days.map((day) => {\n const dayItems = expandedItems.filter((item) => overlapsDay(item, day))\n const slotStart = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9, 0, 0)\n const slotEnd = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10, 0, 0)\n return (\n <div key={day.toISOString()} className=\"rounded-xl border bg-card p-4 shadow-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold text-foreground\">{formatDayLabel(day)}</div>\n {onSlotClick ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSlotClick({ start: slotStart, end: slotEnd })}\n >\n {t('schedule.actions.add', 'Add')}\n </Button>\n ) : null}\n </div>\n <div className=\"mt-3 space-y-2\">\n {dayItems.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-3 text-xs text-muted-foreground\">\n {t('schedule.emptyDay', 'No scheduled items')}\n </div>\n ) : (\n dayItems.map((item) => {\n const statusLabel = getStatusLabel(item.status, t)\n return (\n <button\n key={item.id}\n type=\"button\"\n className={cn(\n 'flex w-full cursor-pointer flex-col gap-2 rounded-lg border px-3 py-2 text-left text-xs transition hover:shadow-sm',\n getKindStyles(item.kind)\n )}\n onClick={() => onItemClick?.(item)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">{item.title}</span>\n {statusLabel ? <Badge variant=\"secondary\">{statusLabel}</Badge> : null}\n </div>\n <div className=\"flex items-center justify-between text-
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ScheduleItem, ScheduleRange, ScheduleSlot } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '../../primitives/badge'\nimport { Button } from '../../primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { expandRecurringItems } from './recurrence'\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction startOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate())\n}\n\nfunction endOfDay(value: Date): Date {\n return new Date(value.getFullYear(), value.getMonth(), value.getDate(), 23, 59, 59, 999)\n}\n\nfunction eachDay(start: Date, end: Date): Date[] {\n const days: Date[] = []\n let cursor = startOfDay(start)\n const last = startOfDay(end)\n while (cursor <= last) {\n days.push(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n }\n return days\n}\n\nfunction overlapsDay(item: ScheduleItem, day: Date): boolean {\n const dayStart = startOfDay(day)\n const dayEnd = endOfDay(day)\n return item.startsAt <= dayEnd && item.endsAt >= dayStart\n}\n\nfunction formatDayLabel(day: Date): string {\n return day.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })\n}\n\nfunction formatTimeRange(item: ScheduleItem, timezone?: string): string {\n const options: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' }\n if (timezone) options.timeZone = timezone\n const startLabel = item.startsAt.toLocaleTimeString(undefined, options)\n const endLabel = item.endsAt.toLocaleTimeString(undefined, options)\n return `${startLabel}-${endLabel}`\n}\n\nfunction getStatusLabel(status: ScheduleItem['status'], t: (key: string, fallback?: string) => string): string | null {\n if (!status) return null\n if (status === 'draft') return t('schedule.item.status.draft', 'Draft')\n if (status === 'negotiation') return t('schedule.item.status.negotiation', 'Negotiation')\n if (status === 'confirmed') return t('schedule.item.status.confirmed', 'Confirmed')\n if (status === 'cancelled') return t('schedule.item.status.cancelled', 'Cancelled')\n return null\n}\n\nfunction getKindStyles(kind: ScheduleItem['kind']): string {\n if (kind === 'event') return 'border-blue-500/40 bg-blue-500/10 text-blue-950'\n if (kind === 'exception') return 'border-amber-500/40 bg-amber-500/10 text-amber-950'\n return 'border-emerald-500/40 bg-emerald-500/10 text-emerald-950'\n}\n\nexport type ScheduleGridProps = {\n items: ScheduleItem[]\n range: ScheduleRange\n timezone?: string\n onItemClick?: (item: ScheduleItem) => void\n onSlotClick?: (slot: ScheduleSlot) => void\n className?: string\n}\n\nexport function ScheduleGrid({ items, range, timezone, onItemClick, onSlotClick, className }: ScheduleGridProps) {\n const t = useT()\n const days = React.useMemo(() => eachDay(range.start, range.end), [range])\n const expandedItems = React.useMemo(() => expandRecurringItems(items, range), [items, range])\n\n return (\n <div className={cn('grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3', className)}>\n {days.map((day) => {\n const dayItems = expandedItems.filter((item) => overlapsDay(item, day))\n const slotStart = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9, 0, 0)\n const slotEnd = new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10, 0, 0)\n return (\n <div key={day.toISOString()} className=\"rounded-xl border bg-card p-4 shadow-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold text-foreground\">{formatDayLabel(day)}</div>\n {onSlotClick ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => onSlotClick({ start: slotStart, end: slotEnd })}\n >\n {t('schedule.actions.add', 'Add')}\n </Button>\n ) : null}\n </div>\n <div className=\"mt-3 space-y-2\">\n {dayItems.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-3 text-xs text-muted-foreground\">\n {t('schedule.emptyDay', 'No scheduled items')}\n </div>\n ) : (\n dayItems.map((item) => {\n const statusLabel = getStatusLabel(item.status, t)\n return (\n <button\n key={item.id}\n type=\"button\"\n className={cn(\n 'flex w-full cursor-pointer flex-col gap-2 rounded-lg border px-3 py-2 text-left text-xs transition hover:shadow-sm',\n getKindStyles(item.kind)\n )}\n onClick={() => onItemClick?.(item)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-semibold\">{item.title}</span>\n {statusLabel ? <Badge variant=\"secondary\">{statusLabel}</Badge> : null}\n </div>\n <div className=\"flex items-center justify-between text-overline text-muted-foreground\">\n <span>{formatTimeRange(item, timezone)}</span>\n <span className=\"capitalize\">{item.kind}</span>\n </div>\n </button>\n )\n })\n )}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -115,7 +115,7 @@ function VersionHistoryPanel({
|
|
|
115
115
|
/* @__PURE__ */ jsx(
|
|
116
116
|
"div",
|
|
117
117
|
{
|
|
118
|
-
className: "fixed inset-0 z-
|
|
118
|
+
className: "fixed inset-0 z-overlay bg-black/20",
|
|
119
119
|
onClick: () => onOpenChange(false),
|
|
120
120
|
"aria-hidden": "true"
|
|
121
121
|
}
|
|
@@ -123,7 +123,7 @@ function VersionHistoryPanel({
|
|
|
123
123
|
/* @__PURE__ */ jsx(
|
|
124
124
|
"div",
|
|
125
125
|
{
|
|
126
|
-
className: "fixed right-0 top-0 z-
|
|
126
|
+
className: "fixed right-0 top-0 z-modal h-full w-full max-w-md border-l bg-background shadow-lg",
|
|
127
127
|
role: "dialog",
|
|
128
128
|
"aria-modal": "true",
|
|
129
129
|
"aria-label": t("audit_logs.version_history.title"),
|
|
@@ -179,7 +179,7 @@ function VersionHistoryPanel({
|
|
|
179
179
|
return /* @__PURE__ */ jsxs(
|
|
180
180
|
"div",
|
|
181
181
|
{
|
|
182
|
-
className: `flex items-start justify-between gap-3 py-3 transition-colors hover:bg-muted/
|
|
182
|
+
className: `flex items-start justify-between gap-3 py-3 transition-colors hover:bg-muted/50 ${isRelatedEntry ? "pl-8 pr-4 border-l-2 border-l-muted-foreground/20" : "px-4"}`,
|
|
183
183
|
children: [
|
|
184
184
|
/* @__PURE__ */ jsxs(
|
|
185
185
|
Button,
|
|
@@ -189,7 +189,7 @@ function VersionHistoryPanel({
|
|
|
189
189
|
className: "h-auto min-w-0 flex-1 flex-col items-start justify-start gap-1 whitespace-normal px-0 py-0 text-left hover:bg-transparent",
|
|
190
190
|
onClick: () => setSelectedEntry(entry),
|
|
191
191
|
children: [
|
|
192
|
-
isRelatedEntry ? /* @__PURE__ */ jsx("span", { className: "text-
|
|
192
|
+
isRelatedEntry ? /* @__PURE__ */ jsx("span", { className: "text-overline uppercase tracking-wider text-muted-foreground/70 font-medium", children: humanizeResourceKind(entry.resourceKind, t) }) : null,
|
|
193
193
|
/* @__PURE__ */ jsx("div", { className: "break-words text-sm font-medium", children: actionLabel }),
|
|
194
194
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 text-xs text-muted-foreground", children: [
|
|
195
195
|
/* @__PURE__ */ jsx("span", { children: entry.actorUserName || entry.actorUserId || t("audit_logs.common.none") }),
|