@open-mercato/ui 0.6.7-develop.6788.1.c2a1520a30 → 0.6.7-develop.6795.1.8a3f27921c

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.
@@ -7,15 +7,15 @@ import { Progress } from "@open-mercato/ui/primitives/progress";
7
7
  const STEP_STYLES = {
8
8
  pending: "",
9
9
  active: "border-primary/40 bg-primary/10 text-primary",
10
- completed: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200"
10
+ completed: "border-status-success-border bg-status-success-bg text-status-success-text"
11
11
  };
12
12
  const STATUS_STYLES = {
13
13
  default: "border-border bg-background/80 text-foreground",
14
14
  info: "border-primary/20 bg-primary/5 text-primary",
15
- success: "border-emerald-500/20 bg-emerald-500/5 text-emerald-200",
16
- warning: "border-amber-500/20 bg-amber-500/5 text-amber-200",
15
+ success: "border-status-success-border bg-status-success-bg text-status-success-text",
16
+ warning: "border-status-warning-border bg-status-warning-bg text-status-warning-text",
17
17
  danger: "border-destructive/20 bg-destructive/5 text-destructive",
18
- muted: "border-zinc-500/20 bg-zinc-500/5 text-zinc-200"
18
+ muted: "border-status-neutral-border bg-status-neutral-bg text-status-neutral-text"
19
19
  };
20
20
  function NextStepCallout({
21
21
  icon,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backend/NextStepCallout.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\n\ntype NextStepCalloutStep = {\n id: string\n label: React.ReactNode\n state?: 'pending' | 'active' | 'completed'\n}\n\ntype NextStepCalloutStatusTone = 'default' | 'info' | 'success' | 'warning' | 'danger' | 'muted'\n\ntype NextStepCalloutStatus = {\n tone?: NextStepCalloutStatusTone\n icon?: React.ReactNode\n label: React.ReactNode\n badge?: React.ReactNode\n progressValue?: number | null\n progressDescription?: React.ReactNode\n errorMessage?: React.ReactNode\n}\n\ntype NextStepCalloutProps = {\n icon?: React.ReactNode\n title: React.ReactNode\n description?: React.ReactNode\n steps?: NextStepCalloutStep[]\n actionLabel: React.ReactNode\n actionIcon?: React.ReactNode\n onAction?: () => void\n disabled?: boolean\n disabledMessage?: React.ReactNode\n busy?: boolean\n status?: NextStepCalloutStatus | null\n className?: string\n}\n\nconst STEP_STYLES: Record<NonNullable<NextStepCalloutStep['state']>, string> = {\n pending: '',\n active: 'border-primary/40 bg-primary/10 text-primary',\n completed: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-200',\n}\n\nconst STATUS_STYLES: Record<NextStepCalloutStatusTone, string> = {\n default: 'border-border bg-background/80 text-foreground',\n info: 'border-primary/20 bg-primary/5 text-primary',\n success: 'border-emerald-500/20 bg-emerald-500/5 text-emerald-200',\n warning: 'border-amber-500/20 bg-amber-500/5 text-amber-200',\n danger: 'border-destructive/20 bg-destructive/5 text-destructive',\n muted: 'border-zinc-500/20 bg-zinc-500/5 text-zinc-200',\n}\n\nexport function NextStepCallout({\n icon,\n title,\n description,\n steps,\n actionLabel,\n actionIcon,\n onAction,\n disabled = false,\n disabledMessage,\n busy = false,\n status,\n className,\n}: NextStepCalloutProps) {\n return (\n <div className={cn('rounded-xl border border-dashed border-primary/30 bg-primary/5 px-6 py-8', className)}>\n <div className=\"mx-auto flex max-w-2xl flex-col items-center text-center\">\n {icon ? (\n <div className=\"mb-4 flex h-14 w-14 items-center justify-center rounded-full border border-primary/30 bg-background text-primary\">\n {icon}\n </div>\n ) : null}\n <h4 className=\"text-lg font-semibold\">{title}</h4>\n {description ? (\n <p className=\"mt-2 text-sm text-muted-foreground\">{description}</p>\n ) : null}\n {steps && steps.length > 0 ? (\n <div className=\"mt-4 flex flex-wrap items-center justify-center gap-2\">\n {steps.map((step) => (\n <Badge\n key={step.id}\n variant=\"outline\"\n className={cn(step.state ? STEP_STYLES[step.state] : STEP_STYLES.pending)}\n >\n {step.label}\n </Badge>\n ))}\n </div>\n ) : null}\n <Button\n type=\"button\"\n size=\"lg\"\n className=\"mt-6 min-w-72\"\n onClick={onAction}\n disabled={disabled || busy}\n >\n {actionIcon}\n {actionLabel}\n </Button>\n {disabledMessage ? (\n <p className=\"mt-3 text-xs text-muted-foreground\">{disabledMessage}</p>\n ) : null}\n {status ? (\n <div className={cn('mt-4 w-full rounded-lg border px-4 py-3 text-left', STATUS_STYLES[status.tone ?? 'default'])}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2 text-sm font-medium\">\n {status.icon ? <span className=\"shrink-0\">{status.icon}</span> : null}\n <span>{status.label}</span>\n </div>\n {status.badge ? status.badge : null}\n </div>\n {typeof status.progressValue === 'number' ? (\n <Progress value={status.progressValue} className=\"mt-3 h-2\" />\n ) : busy ? (\n <div className=\"mt-3 h-2 rounded-full bg-secondary\" />\n ) : null}\n {status.progressDescription ? (\n <p className=\"mt-2 text-xs text-muted-foreground\">{status.progressDescription}</p>\n ) : null}\n {status.errorMessage ? (\n <p className=\"mt-2 text-xs text-destructive\">{status.errorMessage}</p>\n ) : null}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\n\ntype NextStepCalloutStep = {\n id: string\n label: React.ReactNode\n state?: 'pending' | 'active' | 'completed'\n}\n\ntype NextStepCalloutStatusTone = 'default' | 'info' | 'success' | 'warning' | 'danger' | 'muted'\n\ntype NextStepCalloutStatus = {\n tone?: NextStepCalloutStatusTone\n icon?: React.ReactNode\n label: React.ReactNode\n badge?: React.ReactNode\n progressValue?: number | null\n progressDescription?: React.ReactNode\n errorMessage?: React.ReactNode\n}\n\ntype NextStepCalloutProps = {\n icon?: React.ReactNode\n title: React.ReactNode\n description?: React.ReactNode\n steps?: NextStepCalloutStep[]\n actionLabel: React.ReactNode\n actionIcon?: React.ReactNode\n onAction?: () => void\n disabled?: boolean\n disabledMessage?: React.ReactNode\n busy?: boolean\n status?: NextStepCalloutStatus | null\n className?: string\n}\n\nconst STEP_STYLES: Record<NonNullable<NextStepCalloutStep['state']>, string> = {\n pending: '',\n active: 'border-primary/40 bg-primary/10 text-primary',\n completed: 'border-status-success-border bg-status-success-bg text-status-success-text',\n}\n\nconst STATUS_STYLES: Record<NextStepCalloutStatusTone, string> = {\n default: 'border-border bg-background/80 text-foreground',\n info: 'border-primary/20 bg-primary/5 text-primary',\n success: 'border-status-success-border bg-status-success-bg text-status-success-text',\n warning: 'border-status-warning-border bg-status-warning-bg text-status-warning-text',\n danger: 'border-destructive/20 bg-destructive/5 text-destructive',\n muted: 'border-status-neutral-border bg-status-neutral-bg text-status-neutral-text',\n}\n\nexport function NextStepCallout({\n icon,\n title,\n description,\n steps,\n actionLabel,\n actionIcon,\n onAction,\n disabled = false,\n disabledMessage,\n busy = false,\n status,\n className,\n}: NextStepCalloutProps) {\n return (\n <div className={cn('rounded-xl border border-dashed border-primary/30 bg-primary/5 px-6 py-8', className)}>\n <div className=\"mx-auto flex max-w-2xl flex-col items-center text-center\">\n {icon ? (\n <div className=\"mb-4 flex h-14 w-14 items-center justify-center rounded-full border border-primary/30 bg-background text-primary\">\n {icon}\n </div>\n ) : null}\n <h4 className=\"text-lg font-semibold\">{title}</h4>\n {description ? (\n <p className=\"mt-2 text-sm text-muted-foreground\">{description}</p>\n ) : null}\n {steps && steps.length > 0 ? (\n <div className=\"mt-4 flex flex-wrap items-center justify-center gap-2\">\n {steps.map((step) => (\n <Badge\n key={step.id}\n variant=\"outline\"\n className={cn(step.state ? STEP_STYLES[step.state] : STEP_STYLES.pending)}\n >\n {step.label}\n </Badge>\n ))}\n </div>\n ) : null}\n <Button\n type=\"button\"\n size=\"lg\"\n className=\"mt-6 min-w-72\"\n onClick={onAction}\n disabled={disabled || busy}\n >\n {actionIcon}\n {actionLabel}\n </Button>\n {disabledMessage ? (\n <p className=\"mt-3 text-xs text-muted-foreground\">{disabledMessage}</p>\n ) : null}\n {status ? (\n <div className={cn('mt-4 w-full rounded-lg border px-4 py-3 text-left', STATUS_STYLES[status.tone ?? 'default'])}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2 text-sm font-medium\">\n {status.icon ? <span className=\"shrink-0\">{status.icon}</span> : null}\n <span>{status.label}</span>\n </div>\n {status.badge ? status.badge : null}\n </div>\n {typeof status.progressValue === 'number' ? (\n <Progress value={status.progressValue} className=\"mt-3 h-2\" />\n ) : busy ? (\n <div className=\"mt-3 h-2 rounded-full bg-secondary\" />\n ) : null}\n {status.progressDescription ? (\n <p className=\"mt-2 text-xs text-muted-foreground\">{status.progressDescription}</p>\n ) : null}\n {status.errorMessage ? (\n <p className=\"mt-2 text-xs text-destructive\">{status.errorMessage}</p>\n ) : null}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],
5
5
  "mappings": ";AA0EU,cAqBF,YArBE;AAvEV,SAAS,UAAU;AACnB,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,gBAAgB;AAmCzB,MAAM,cAAyE;AAAA,EAC7E,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AACb;AAEA,MAAM,gBAA2D;AAAA,EAC/D,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AACT;AAEO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AACF,GAAyB;AACvB,SACE,oBAAC,SAAI,WAAW,GAAG,4EAA4E,SAAS,GACtG,+BAAC,SAAI,WAAU,4DACZ;AAAA,WACC,oBAAC,SAAI,WAAU,oHACZ,gBACH,IACE;AAAA,IACJ,oBAAC,QAAG,WAAU,yBAAyB,iBAAM;AAAA,IAC5C,cACC,oBAAC,OAAE,WAAU,sCAAsC,uBAAY,IAC7D;AAAA,IACH,SAAS,MAAM,SAAS,IACvB,oBAAC,SAAI,WAAU,yDACZ,gBAAM,IAAI,CAAC,SACV;AAAA,MAAC;AAAA;AAAA,QAEC,SAAQ;AAAA,QACR,WAAW,GAAG,KAAK,QAAQ,YAAY,KAAK,KAAK,IAAI,YAAY,OAAO;AAAA,QAEvE,eAAK;AAAA;AAAA,MAJD,KAAK;AAAA,IAKZ,CACD,GACH,IACE;AAAA,IACJ;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,WAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,YAAY;AAAA,QAErB;AAAA;AAAA,UACA;AAAA;AAAA;AAAA,IACH;AAAA,IACC,kBACC,oBAAC,OAAE,WAAU,sCAAsC,2BAAgB,IACjE;AAAA,IACH,SACC,qBAAC,SAAI,WAAW,GAAG,qDAAqD,cAAc,OAAO,QAAQ,SAAS,CAAC,GAC7G;AAAA,2BAAC,SAAI,WAAU,qDACb;AAAA,6BAAC,SAAI,WAAU,+CACZ;AAAA,iBAAO,OAAO,oBAAC,UAAK,WAAU,YAAY,iBAAO,MAAK,IAAU;AAAA,UACjE,oBAAC,UAAM,iBAAO,OAAM;AAAA,WACtB;AAAA,QACC,OAAO,QAAQ,OAAO,QAAQ;AAAA,SACjC;AAAA,MACC,OAAO,OAAO,kBAAkB,WAC/B,oBAAC,YAAS,OAAO,OAAO,eAAe,WAAU,YAAW,IAC1D,OACF,oBAAC,SAAI,WAAU,sCAAqC,IAClD;AAAA,MACH,OAAO,sBACN,oBAAC,OAAE,WAAU,sCAAsC,iBAAO,qBAAoB,IAC5E;AAAA,MACH,OAAO,eACN,oBAAC,OAAE,WAAU,iCAAiC,iBAAO,cAAa,IAChE;AAAA,OACN,IACE;AAAA,KACN,GACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -7,7 +7,7 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
7
7
  import { TabEmptyState } from "@open-mercato/ui/backend/detail";
8
8
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
9
9
  import { normalizeCoordinateInput, validateCoordinateInput } from "@open-mercato/shared/lib/location/coordinates";
10
- import { AddressView, formatAddressString } from "./addressFormat.js";
10
+ import { AddressView } from "./addressFormat.js";
11
11
  import AddressEditor from "./AddressEditor.js";
12
12
  const defaultDraft = {
13
13
  name: "",
@@ -78,7 +78,7 @@ function AddressTiles({
78
78
  t,
79
79
  emptyLabel,
80
80
  isSubmitting = false,
81
- gridClassName = "grid grid-cols-1 gap-2 sm:gap-4 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4",
81
+ gridClassName = "grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-2 sm:gap-4",
82
82
  hideAddButton = false,
83
83
  onAddActionChange,
84
84
  emptyStateTitle,
@@ -348,7 +348,7 @@ function AddressTiles({
348
348
  addressTypesContext
349
349
  }
350
350
  ),
351
- generalError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: generalError }) : null,
351
+ generalError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-status-error-text", children: generalError }) : null,
352
352
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [
353
353
  /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: handleCancel, disabled: disableActions, children: label("cancel", "Cancel") }),
354
354
  /* @__PURE__ */ jsx(Button, { type: "button", onClick: handleSave, disabled: disableActions, children: saving ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -412,8 +412,7 @@ function AddressTiles({
412
412
  address.isPrimary ? /* @__PURE__ */ jsx("span", { className: "rounded-full border border-border bg-muted px-2 py-0.5 text-overline font-semibold uppercase", children: label("primaryBadge", "Primary") }) : null
413
413
  ] }),
414
414
  address.purpose ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: address.purpose }) : null,
415
- /* @__PURE__ */ jsx(AddressView, { address, format, className: "text-sm text-foreground" }),
416
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: formatAddressString(address, format) })
415
+ /* @__PURE__ */ jsx(AddressView, { address, format, className: "text-sm text-foreground" })
417
416
  ] }),
418
417
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 opacity-100 md:opacity-0 transition-opacity md:group-hover:opacity-100 focus-within:opacity-100", children: [
419
418
  /* @__PURE__ */ jsx(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/detail/AddressTiles.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { normalizeCoordinateInput, validateCoordinateInput } from '@open-mercato/shared/lib/location/coordinates'\nimport { AddressView, formatAddressString, type AddressFormatStrategy } from './addressFormat'\nimport AddressEditor, { type AddressTypesAdapter } from './AddressEditor'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\n\nexport type Translator = (\n key: string,\n fallback?: string,\n params?: Record<string, string | number>,\n) => string\n\nexport type AddressInput = {\n name?: string\n purpose?: string\n companyName?: string\n addressLine1: string\n addressLine2?: string\n buildingNumber?: string\n flatNumber?: string\n city?: string\n region?: string\n postalCode?: string\n country?: string\n latitude?: number | null\n longitude?: number | null\n isPrimary?: boolean\n}\n\nexport type AddressValue = AddressInput & {\n id: string\n purpose?: string | null\n companyName?: string | null\n latitude?: number | null\n longitude?: number | null\n}\n\ntype AddressTilesProps<C = unknown> = {\n addresses: AddressValue[]\n onCreate: (payload: AddressInput) => Promise<void> | void\n onUpdate?: (id: string, payload: AddressInput) => Promise<void> | void\n onDelete?: (id: string) => Promise<void> | void\n t: Translator\n emptyLabel: string\n isSubmitting?: boolean\n gridClassName?: string\n hideAddButton?: boolean\n onAddActionChange?: (action: { openCreateForm: () => void; addDisabled: boolean } | null) => void\n emptyStateTitle?: string\n emptyStateActionLabel?: string\n labelPrefix?: string\n showCoordinateFields?: boolean\n addressTypesAdapter?: AddressTypesAdapter<C>\n addressTypesContext?: C\n loadFormat?: (context?: C) => Promise<AddressFormatStrategy>\n formatContext?: C\n}\n\ntype DraftAddressState = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n latitude: string\n longitude: string\n isPrimary: boolean\n}\n\ntype DraftFieldKey = keyof DraftAddressState\n\ntype AddressValidationDetail = {\n path?: Array<string | number>\n code?: string\n message?: string\n minimum?: number\n maximum?: number\n type?: string\n}\n\nconst defaultDraft: DraftAddressState = {\n name: '',\n purpose: '',\n companyName: '',\n addressLine1: '',\n addressLine2: '',\n buildingNumber: '',\n flatNumber: '',\n city: '',\n region: '',\n postalCode: '',\n country: '',\n latitude: '',\n longitude: '',\n isPrimary: false,\n}\n\nconst serverFieldMap: Record<string, DraftFieldKey> = {\n name: 'name',\n purpose: 'purpose',\n companyName: 'companyName',\n addressLine1: 'addressLine1',\n addressLine2: 'addressLine2',\n buildingNumber: 'buildingNumber',\n flatNumber: 'flatNumber',\n city: 'city',\n region: 'region',\n postalCode: 'postalCode',\n country: 'country',\n latitude: 'latitude',\n longitude: 'longitude',\n isPrimary: 'isPrimary',\n}\n\nfunction normalizeOptional(value: string): string | undefined {\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nfunction extractValidationDetails(error: unknown): AddressValidationDetail[] {\n if (!error || typeof error !== 'object') return []\n const candidate = (error as { details?: unknown }).details\n if (!Array.isArray(candidate)) return []\n return candidate\n .map((entry) => (entry && typeof entry === 'object' ? (entry as AddressValidationDetail) : null))\n .filter((entry): entry is AddressValidationDetail => entry !== null)\n}\n\nfunction resolveFieldMessage(detail: AddressValidationDetail, fieldLabel: string, t: Translator, prefix: string): string {\n const label = (suffix: string, fallback: string) => t(`${prefix}.${suffix}`, fallback)\n switch (detail.code) {\n case 'invalid_type':\n return label('validation.invalid', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n case 'too_small':\n if (detail.minimum === 1 && detail.type === 'string') {\n return label('validation.required', '{{field}} is required').replace('{{field}}', fieldLabel)\n }\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n case 'too_big':\n if (typeof detail.maximum === 'number') {\n return label('validation.tooLong', '{{field}} is too long').replace('{{field}}', fieldLabel)\n .replace('{{max}}', `${detail.maximum}`)\n }\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n default:\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n }\n}\n\nexport function AddressTiles<C = unknown>({\n addresses,\n onCreate,\n onUpdate,\n onDelete,\n t,\n emptyLabel,\n isSubmitting = false,\n gridClassName = 'grid grid-cols-1 gap-2 sm:gap-4 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',\n hideAddButton = false,\n onAddActionChange,\n emptyStateTitle,\n emptyStateActionLabel,\n labelPrefix = 'customers.people.detail.addresses',\n showCoordinateFields = false,\n addressTypesAdapter,\n addressTypesContext,\n loadFormat,\n formatContext,\n}: AddressTilesProps<C>) {\n const scopeVersion = useOrganizationScopeVersion()\n const [isFormOpen, setIsFormOpen] = React.useState(false)\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draft, setDraft] = React.useState<DraftAddressState>(defaultDraft)\n const [saving, setSaving] = React.useState(false)\n const [deletingId, setDeletingId] = React.useState<string | null>(null)\n const [generalError, setGeneralError] = React.useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = React.useState<Partial<Record<DraftFieldKey, string>>>({})\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [formatLoading, setFormatLoading] = React.useState(false)\n\n const label = React.useCallback(\n (suffix: string, fallback?: string, params?: Record<string, string | number>) =>\n t(`${labelPrefix}.${suffix}`, fallback, params),\n [labelPrefix, t],\n )\n\n const fieldLabels = React.useMemo(\n () => ({\n name: label('fields.label', 'Label'),\n purpose: label('fields.type', 'Address type'),\n companyName: label('fields.companyName', 'Company name'),\n addressLine1: label('fields.line1', 'Address line 1'),\n addressLine2: label('fields.line2', 'Address line 2'),\n street: label('fields.street', 'Street'),\n buildingNumber: label('fields.buildingNumber', 'Building number'),\n flatNumber: label('fields.flatNumber', 'Flat number'),\n city: label('fields.city', 'City'),\n region: label('fields.region', 'Region'),\n postalCode: label('fields.postalCode', 'Postal code'),\n country: label('fields.country', 'Country'),\n latitude: label('fields.latitude', 'Latitude'),\n longitude: label('fields.longitude', 'Longitude'),\n isPrimary: label('fields.primary', 'Primary address'),\n }),\n [label],\n )\n const line1FieldLabel = React.useMemo(\n () => (format === 'street_first' ? fieldLabels.street : fieldLabels.addressLine1),\n [fieldLabels.addressLine1, fieldLabels.street, format],\n )\n\n const resetForm = React.useCallback(() => {\n setDraft(defaultDraft)\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(null)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadFormatValue() {\n if (!loadFormat) {\n setFormat('line_first')\n setFormatLoading(false)\n return\n }\n setFormatLoading(true)\n try {\n const value = await loadFormat(formatContext)\n if (!cancelled && (value === 'street_first' || value === 'line_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setFormatLoading(false)\n }\n }\n void loadFormatValue()\n return () => {\n cancelled = true\n }\n }, [formatContext, label, loadFormat, scopeVersion])\n\n const openCreateForm = React.useCallback(() => {\n resetForm()\n setIsFormOpen(true)\n }, [resetForm])\n\n const handleCancel = React.useCallback(() => {\n resetForm()\n setIsFormOpen(false)\n }, [resetForm])\n\n const handleEdit = React.useCallback((value: AddressValue) => {\n setDraft({\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n latitude: value.latitude != null ? String(value.latitude) : '',\n longitude: value.longitude != null ? String(value.longitude) : '',\n isPrimary: value.isPrimary ?? false,\n })\n setEditingId(value.id)\n setIsFormOpen(true)\n setFieldErrors({})\n setGeneralError(null)\n }, [])\n\n const validate = React.useCallback((): boolean => {\n const errors: Partial<Record<DraftFieldKey, string>> = {}\n if (!draft.addressLine1.trim()) {\n errors.addressLine1 = label('validation.required', '{{field}} is required').replace('{{field}}', line1FieldLabel)\n }\n for (const key of ['latitude', 'longitude'] as const) {\n const result = validateCoordinateInput(key, draft[key])\n if (result.status === 'invalid') {\n errors[key] = label('validation.invalid', 'Invalid value for {{field}}').replace('{{field}}', fieldLabels[key])\n } else if (result.status === 'outOfRange') {\n errors[key] = label('validation.coordinateRange', '{{field}} must be between {{min}} and {{max}}')\n .replace('{{field}}', fieldLabels[key])\n .replace('{{min}}', `${result.min}`)\n .replace('{{max}}', `${result.max}`)\n }\n }\n if (Object.keys(errors).length > 0) {\n setFieldErrors(errors)\n return false\n }\n return true\n }, [draft.addressLine1, draft.latitude, draft.longitude, fieldLabels, label, line1FieldLabel])\n\n const handleSave = React.useCallback(async () => {\n if (!validate()) return\n setSaving(true)\n setGeneralError(null)\n try {\n const payload: AddressInput = {\n name: normalizeOptional(draft.name),\n purpose: normalizeOptional(draft.purpose),\n companyName: normalizeOptional(draft.companyName),\n addressLine1: draft.addressLine1.trim(),\n addressLine2: normalizeOptional(draft.addressLine2),\n buildingNumber: normalizeOptional(draft.buildingNumber),\n flatNumber: normalizeOptional(draft.flatNumber),\n city: normalizeOptional(draft.city),\n region: normalizeOptional(draft.region),\n postalCode: normalizeOptional(draft.postalCode),\n country: normalizeOptional(draft.country)?.toUpperCase(),\n // On edit, an emptied coordinate must be sent as `null` to clear the stored value; omitting\n // it (undefined) makes the partial-update handler keep the old coordinate. On create there is\n // nothing to clear, so leave it undefined (omitted).\n latitude: normalizeCoordinateInput(draft.latitude) ?? (editingId ? null : undefined),\n longitude: normalizeCoordinateInput(draft.longitude) ?? (editingId ? null : undefined),\n isPrimary: draft.isPrimary,\n }\n if (editingId && onUpdate) {\n await onUpdate(editingId, payload)\n } else {\n await onCreate(payload)\n }\n resetForm()\n setIsFormOpen(false)\n } catch (err) {\n const details = extractValidationDetails(err)\n if (details.length) {\n const nextErrors: Partial<Record<DraftFieldKey, string>> = {}\n details.forEach((detail) => {\n const path = Array.isArray(detail.path) ? detail.path : []\n const key = typeof path[0] === 'string' ? path[0] : undefined\n if (!key) return\n const fieldKey = serverFieldMap[key]\n if (!fieldKey) return\n const fieldLabel = fieldKey === 'addressLine1' ? line1FieldLabel : (fieldLabels[fieldKey] ?? key)\n nextErrors[fieldKey] = resolveFieldMessage(detail, fieldLabel, t, labelPrefix)\n })\n setFieldErrors(nextErrors)\n setGeneralError(label('validation.summary', 'Please fix the highlighted fields.'))\n return\n }\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to save address')\n setGeneralError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [draft, editingId, fieldLabels, label, labelPrefix, onCreate, onUpdate, resetForm, t, validate])\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!onDelete) return\n setDeletingId(id)\n try {\n await onDelete(id)\n if (editingId === id) {\n resetForm()\n setIsFormOpen(false)\n }\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to delete address')\n flash(message, 'error')\n } finally {\n setDeletingId(null)\n }\n },\n [editingId, label, onDelete, resetForm]\n )\n\n const disableActions = saving || isSubmitting || deletingId !== null\n const isEditing = editingId !== null\n const addDisabled = disableActions || isEditing\n const hasAddresses = addresses.length > 0\n const emptyTitle = emptyStateTitle ?? emptyLabel\n const emptyActionLabel = emptyStateActionLabel ?? label('add', 'Add address')\n\n React.useEffect(() => {\n if (!onAddActionChange) return\n onAddActionChange({ openCreateForm, addDisabled })\n }, [onAddActionChange, openCreateForm, addDisabled])\n\n React.useEffect(\n () => () => {\n if (onAddActionChange) onAddActionChange(null)\n },\n [onAddActionChange]\n )\n\n const renderFormTile = React.useCallback(\n (key: string) => (\n <div\n key={key}\n className=\"rounded-lg border-2 border-dashed border-muted-foreground/50 bg-muted/30 p-4 text-sm\"\n onKeyDown={(event) => {\n if (!(event.metaKey || event.ctrlKey)) return\n if (event.key !== 'Enter') return\n event.preventDefault()\n if (disableActions) return\n void handleSave()\n }}\n >\n <div className=\"flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <span>\n {editingId\n ? label('editTitle', 'Edit address')\n : label('addTitle', 'Add address')}\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={handleCancel} disabled={disableActions}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n <div className=\"mt-3 space-y-3\">\n {formatLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {label('formatLoading', 'Loading address preferences\u2026')}\n </p>\n ) : null}\n <AddressEditor\n value={draft}\n onChange={(next) => {\n setDraft({ ...next, latitude: next.latitude ?? '', longitude: next.longitude ?? '' })\n if (Object.keys(fieldErrors).length) {\n const nextErrors = { ...fieldErrors }\n ;(Object.keys(nextErrors) as DraftFieldKey[]).forEach((key) => {\n const candidate = (next as Record<string, unknown>)[key]\n if (candidate !== undefined && candidate !== null && `${candidate}`.length) {\n delete nextErrors[key]\n }\n })\n setFieldErrors(nextErrors)\n }\n }}\n format={format}\n t={t}\n disabled={disableActions}\n errors={fieldErrors}\n showFormatHint={!formatLoading}\n showCoordinateFields={showCoordinateFields}\n labelPrefix={labelPrefix}\n addressTypesAdapter={addressTypesAdapter}\n addressTypesContext={addressTypesContext}\n />\n {generalError ? <p className=\"text-xs text-red-600\">{generalError}</p> : null}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={disableActions}>\n {label('cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={disableActions}>\n {saving ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {editingId\n ? label('updating', 'Updating\u2026')\n : label('saving', 'Saving\u2026')}\n </>\n ) : editingId ? (\n label('update', 'Update address')\n ) : (\n label('save', 'Save address')\n )}\n </Button>\n </div>\n </div>\n </div>\n ),\n [\n addressTypesAdapter,\n addressTypesContext,\n disableActions,\n draft,\n editingId,\n fieldErrors,\n format,\n formatLoading,\n handleCancel,\n handleSave,\n generalError,\n label,\n labelPrefix,\n saving,\n showCoordinateFields,\n t,\n ]\n )\n\n return (\n <div className=\"space-y-4\">\n {!hideAddButton && hasAddresses ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openCreateForm}\n disabled={addDisabled}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {label('add', 'Add address')}\n </Button>\n </div>\n ) : null}\n {hasAddresses ? (\n <div className={gridClassName}>\n {addresses.map((address) => {\n if (isFormOpen && editingId === address.id) {\n return renderFormTile(`form-${address.id}`)\n }\n const isDeleting = deletingId === address.id\n return (\n <div\n key={address.id}\n className=\"group rounded-lg border border-border/70 bg-card p-4 text-sm transition hover:border-border\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-sm font-semibold text-foreground\">\n {address.name ?? label('labelFallback', 'Address')}\n </p>\n {address.isPrimary ? (\n <span className=\"rounded-full border border-border bg-muted px-2 py-0.5 text-overline font-semibold uppercase\">\n {label('primaryBadge', 'Primary')}\n </span>\n ) : null}\n </div>\n {address.purpose ? (\n <p className=\"text-xs text-muted-foreground\">\n {address.purpose}\n </p>\n ) : null}\n <AddressView address={address} format={format} className=\"text-sm text-foreground\" />\n <p className=\"text-xs text-muted-foreground\">\n {formatAddressString(address, format)}\n </p>\n </div>\n <div className=\"flex items-center gap-1 opacity-100 md:opacity-0 transition-opacity md:group-hover:opacity-100 focus-within:opacity-100\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleEdit(address)}\n disabled={disableActions}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleDelete(address.id)}\n disabled={disableActions}\n >\n {isDeleting ? (\n <span className=\"relative flex h-4 w-4 items-center justify-center text-destructive\">\n <span className=\"absolute h-4 w-4 animate-spin rounded-full border border-destructive border-t-transparent\" />\n </span>\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n </Button>\n </div>\n </div>\n </div>\n )\n })}\n {isFormOpen && !editingId ? renderFormTile('create') : null}\n </div>\n ) : isFormOpen ? (\n <div className={gridClassName}>\n {renderFormTile('create')}\n </div>\n ) : (\n <TabEmptyState\n title={emptyTitle}\n action={{\n label: emptyActionLabel,\n onClick: openCreateForm,\n disabled: addDisabled,\n }}\n />\n )}\n </div>\n )\n}\n\nexport default AddressTiles\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { normalizeCoordinateInput, validateCoordinateInput } from '@open-mercato/shared/lib/location/coordinates'\nimport { AddressView, type AddressFormatStrategy } from './addressFormat'\nimport AddressEditor, { type AddressTypesAdapter } from './AddressEditor'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\n\nexport type Translator = (\n key: string,\n fallback?: string,\n params?: Record<string, string | number>,\n) => string\n\nexport type AddressInput = {\n name?: string\n purpose?: string\n companyName?: string\n addressLine1: string\n addressLine2?: string\n buildingNumber?: string\n flatNumber?: string\n city?: string\n region?: string\n postalCode?: string\n country?: string\n latitude?: number | null\n longitude?: number | null\n isPrimary?: boolean\n}\n\nexport type AddressValue = AddressInput & {\n id: string\n purpose?: string | null\n companyName?: string | null\n latitude?: number | null\n longitude?: number | null\n}\n\ntype AddressTilesProps<C = unknown> = {\n addresses: AddressValue[]\n onCreate: (payload: AddressInput) => Promise<void> | void\n onUpdate?: (id: string, payload: AddressInput) => Promise<void> | void\n onDelete?: (id: string) => Promise<void> | void\n t: Translator\n emptyLabel: string\n isSubmitting?: boolean\n gridClassName?: string\n hideAddButton?: boolean\n onAddActionChange?: (action: { openCreateForm: () => void; addDisabled: boolean } | null) => void\n emptyStateTitle?: string\n emptyStateActionLabel?: string\n labelPrefix?: string\n showCoordinateFields?: boolean\n addressTypesAdapter?: AddressTypesAdapter<C>\n addressTypesContext?: C\n loadFormat?: (context?: C) => Promise<AddressFormatStrategy>\n formatContext?: C\n}\n\ntype DraftAddressState = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n latitude: string\n longitude: string\n isPrimary: boolean\n}\n\ntype DraftFieldKey = keyof DraftAddressState\n\ntype AddressValidationDetail = {\n path?: Array<string | number>\n code?: string\n message?: string\n minimum?: number\n maximum?: number\n type?: string\n}\n\nconst defaultDraft: DraftAddressState = {\n name: '',\n purpose: '',\n companyName: '',\n addressLine1: '',\n addressLine2: '',\n buildingNumber: '',\n flatNumber: '',\n city: '',\n region: '',\n postalCode: '',\n country: '',\n latitude: '',\n longitude: '',\n isPrimary: false,\n}\n\nconst serverFieldMap: Record<string, DraftFieldKey> = {\n name: 'name',\n purpose: 'purpose',\n companyName: 'companyName',\n addressLine1: 'addressLine1',\n addressLine2: 'addressLine2',\n buildingNumber: 'buildingNumber',\n flatNumber: 'flatNumber',\n city: 'city',\n region: 'region',\n postalCode: 'postalCode',\n country: 'country',\n latitude: 'latitude',\n longitude: 'longitude',\n isPrimary: 'isPrimary',\n}\n\nfunction normalizeOptional(value: string): string | undefined {\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nfunction extractValidationDetails(error: unknown): AddressValidationDetail[] {\n if (!error || typeof error !== 'object') return []\n const candidate = (error as { details?: unknown }).details\n if (!Array.isArray(candidate)) return []\n return candidate\n .map((entry) => (entry && typeof entry === 'object' ? (entry as AddressValidationDetail) : null))\n .filter((entry): entry is AddressValidationDetail => entry !== null)\n}\n\nfunction resolveFieldMessage(detail: AddressValidationDetail, fieldLabel: string, t: Translator, prefix: string): string {\n const label = (suffix: string, fallback: string) => t(`${prefix}.${suffix}`, fallback)\n switch (detail.code) {\n case 'invalid_type':\n return label('validation.invalid', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n case 'too_small':\n if (detail.minimum === 1 && detail.type === 'string') {\n return label('validation.required', '{{field}} is required').replace('{{field}}', fieldLabel)\n }\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n case 'too_big':\n if (typeof detail.maximum === 'number') {\n return label('validation.tooLong', '{{field}} is too long').replace('{{field}}', fieldLabel)\n .replace('{{max}}', `${detail.maximum}`)\n }\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n default:\n return label('validation.generic', 'Invalid value for {{field}}').replace('{{field}}', fieldLabel)\n }\n}\n\nexport function AddressTiles<C = unknown>({\n addresses,\n onCreate,\n onUpdate,\n onDelete,\n t,\n emptyLabel,\n isSubmitting = false,\n gridClassName = 'grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-2 sm:gap-4',\n hideAddButton = false,\n onAddActionChange,\n emptyStateTitle,\n emptyStateActionLabel,\n labelPrefix = 'customers.people.detail.addresses',\n showCoordinateFields = false,\n addressTypesAdapter,\n addressTypesContext,\n loadFormat,\n formatContext,\n}: AddressTilesProps<C>) {\n const scopeVersion = useOrganizationScopeVersion()\n const [isFormOpen, setIsFormOpen] = React.useState(false)\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draft, setDraft] = React.useState<DraftAddressState>(defaultDraft)\n const [saving, setSaving] = React.useState(false)\n const [deletingId, setDeletingId] = React.useState<string | null>(null)\n const [generalError, setGeneralError] = React.useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = React.useState<Partial<Record<DraftFieldKey, string>>>({})\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [formatLoading, setFormatLoading] = React.useState(false)\n\n const label = React.useCallback(\n (suffix: string, fallback?: string, params?: Record<string, string | number>) =>\n t(`${labelPrefix}.${suffix}`, fallback, params),\n [labelPrefix, t],\n )\n\n const fieldLabels = React.useMemo(\n () => ({\n name: label('fields.label', 'Label'),\n purpose: label('fields.type', 'Address type'),\n companyName: label('fields.companyName', 'Company name'),\n addressLine1: label('fields.line1', 'Address line 1'),\n addressLine2: label('fields.line2', 'Address line 2'),\n street: label('fields.street', 'Street'),\n buildingNumber: label('fields.buildingNumber', 'Building number'),\n flatNumber: label('fields.flatNumber', 'Flat number'),\n city: label('fields.city', 'City'),\n region: label('fields.region', 'Region'),\n postalCode: label('fields.postalCode', 'Postal code'),\n country: label('fields.country', 'Country'),\n latitude: label('fields.latitude', 'Latitude'),\n longitude: label('fields.longitude', 'Longitude'),\n isPrimary: label('fields.primary', 'Primary address'),\n }),\n [label],\n )\n const line1FieldLabel = React.useMemo(\n () => (format === 'street_first' ? fieldLabels.street : fieldLabels.addressLine1),\n [fieldLabels.addressLine1, fieldLabels.street, format],\n )\n\n const resetForm = React.useCallback(() => {\n setDraft(defaultDraft)\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(null)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadFormatValue() {\n if (!loadFormat) {\n setFormat('line_first')\n setFormatLoading(false)\n return\n }\n setFormatLoading(true)\n try {\n const value = await loadFormat(formatContext)\n if (!cancelled && (value === 'street_first' || value === 'line_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setFormatLoading(false)\n }\n }\n void loadFormatValue()\n return () => {\n cancelled = true\n }\n }, [formatContext, label, loadFormat, scopeVersion])\n\n const openCreateForm = React.useCallback(() => {\n resetForm()\n setIsFormOpen(true)\n }, [resetForm])\n\n const handleCancel = React.useCallback(() => {\n resetForm()\n setIsFormOpen(false)\n }, [resetForm])\n\n const handleEdit = React.useCallback((value: AddressValue) => {\n setDraft({\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n latitude: value.latitude != null ? String(value.latitude) : '',\n longitude: value.longitude != null ? String(value.longitude) : '',\n isPrimary: value.isPrimary ?? false,\n })\n setEditingId(value.id)\n setIsFormOpen(true)\n setFieldErrors({})\n setGeneralError(null)\n }, [])\n\n const validate = React.useCallback((): boolean => {\n const errors: Partial<Record<DraftFieldKey, string>> = {}\n if (!draft.addressLine1.trim()) {\n errors.addressLine1 = label('validation.required', '{{field}} is required').replace('{{field}}', line1FieldLabel)\n }\n for (const key of ['latitude', 'longitude'] as const) {\n const result = validateCoordinateInput(key, draft[key])\n if (result.status === 'invalid') {\n errors[key] = label('validation.invalid', 'Invalid value for {{field}}').replace('{{field}}', fieldLabels[key])\n } else if (result.status === 'outOfRange') {\n errors[key] = label('validation.coordinateRange', '{{field}} must be between {{min}} and {{max}}')\n .replace('{{field}}', fieldLabels[key])\n .replace('{{min}}', `${result.min}`)\n .replace('{{max}}', `${result.max}`)\n }\n }\n if (Object.keys(errors).length > 0) {\n setFieldErrors(errors)\n return false\n }\n return true\n }, [draft.addressLine1, draft.latitude, draft.longitude, fieldLabels, label, line1FieldLabel])\n\n const handleSave = React.useCallback(async () => {\n if (!validate()) return\n setSaving(true)\n setGeneralError(null)\n try {\n const payload: AddressInput = {\n name: normalizeOptional(draft.name),\n purpose: normalizeOptional(draft.purpose),\n companyName: normalizeOptional(draft.companyName),\n addressLine1: draft.addressLine1.trim(),\n addressLine2: normalizeOptional(draft.addressLine2),\n buildingNumber: normalizeOptional(draft.buildingNumber),\n flatNumber: normalizeOptional(draft.flatNumber),\n city: normalizeOptional(draft.city),\n region: normalizeOptional(draft.region),\n postalCode: normalizeOptional(draft.postalCode),\n country: normalizeOptional(draft.country)?.toUpperCase(),\n // On edit, an emptied coordinate must be sent as `null` to clear the stored value; omitting\n // it (undefined) makes the partial-update handler keep the old coordinate. On create there is\n // nothing to clear, so leave it undefined (omitted).\n latitude: normalizeCoordinateInput(draft.latitude) ?? (editingId ? null : undefined),\n longitude: normalizeCoordinateInput(draft.longitude) ?? (editingId ? null : undefined),\n isPrimary: draft.isPrimary,\n }\n if (editingId && onUpdate) {\n await onUpdate(editingId, payload)\n } else {\n await onCreate(payload)\n }\n resetForm()\n setIsFormOpen(false)\n } catch (err) {\n const details = extractValidationDetails(err)\n if (details.length) {\n const nextErrors: Partial<Record<DraftFieldKey, string>> = {}\n details.forEach((detail) => {\n const path = Array.isArray(detail.path) ? detail.path : []\n const key = typeof path[0] === 'string' ? path[0] : undefined\n if (!key) return\n const fieldKey = serverFieldMap[key]\n if (!fieldKey) return\n const fieldLabel = fieldKey === 'addressLine1' ? line1FieldLabel : (fieldLabels[fieldKey] ?? key)\n nextErrors[fieldKey] = resolveFieldMessage(detail, fieldLabel, t, labelPrefix)\n })\n setFieldErrors(nextErrors)\n setGeneralError(label('validation.summary', 'Please fix the highlighted fields.'))\n return\n }\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to save address')\n setGeneralError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [draft, editingId, fieldLabels, label, labelPrefix, onCreate, onUpdate, resetForm, t, validate])\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!onDelete) return\n setDeletingId(id)\n try {\n await onDelete(id)\n if (editingId === id) {\n resetForm()\n setIsFormOpen(false)\n }\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : label('error', 'Failed to delete address')\n flash(message, 'error')\n } finally {\n setDeletingId(null)\n }\n },\n [editingId, label, onDelete, resetForm]\n )\n\n const disableActions = saving || isSubmitting || deletingId !== null\n const isEditing = editingId !== null\n const addDisabled = disableActions || isEditing\n const hasAddresses = addresses.length > 0\n const emptyTitle = emptyStateTitle ?? emptyLabel\n const emptyActionLabel = emptyStateActionLabel ?? label('add', 'Add address')\n\n React.useEffect(() => {\n if (!onAddActionChange) return\n onAddActionChange({ openCreateForm, addDisabled })\n }, [onAddActionChange, openCreateForm, addDisabled])\n\n React.useEffect(\n () => () => {\n if (onAddActionChange) onAddActionChange(null)\n },\n [onAddActionChange]\n )\n\n const renderFormTile = React.useCallback(\n (key: string) => (\n <div\n key={key}\n className=\"rounded-lg border-2 border-dashed border-muted-foreground/50 bg-muted/30 p-4 text-sm\"\n onKeyDown={(event) => {\n if (!(event.metaKey || event.ctrlKey)) return\n if (event.key !== 'Enter') return\n event.preventDefault()\n if (disableActions) return\n void handleSave()\n }}\n >\n <div className=\"flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <span>\n {editingId\n ? label('editTitle', 'Edit address')\n : label('addTitle', 'Add address')}\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={handleCancel} disabled={disableActions}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n <div className=\"mt-3 space-y-3\">\n {formatLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {label('formatLoading', 'Loading address preferences\u2026')}\n </p>\n ) : null}\n <AddressEditor\n value={draft}\n onChange={(next) => {\n setDraft({ ...next, latitude: next.latitude ?? '', longitude: next.longitude ?? '' })\n if (Object.keys(fieldErrors).length) {\n const nextErrors = { ...fieldErrors }\n ;(Object.keys(nextErrors) as DraftFieldKey[]).forEach((key) => {\n const candidate = (next as Record<string, unknown>)[key]\n if (candidate !== undefined && candidate !== null && `${candidate}`.length) {\n delete nextErrors[key]\n }\n })\n setFieldErrors(nextErrors)\n }\n }}\n format={format}\n t={t}\n disabled={disableActions}\n errors={fieldErrors}\n showFormatHint={!formatLoading}\n showCoordinateFields={showCoordinateFields}\n labelPrefix={labelPrefix}\n addressTypesAdapter={addressTypesAdapter}\n addressTypesContext={addressTypesContext}\n />\n {generalError ? <p className=\"text-xs text-status-error-text\">{generalError}</p> : null}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={disableActions}>\n {label('cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={disableActions}>\n {saving ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {editingId\n ? label('updating', 'Updating\u2026')\n : label('saving', 'Saving\u2026')}\n </>\n ) : editingId ? (\n label('update', 'Update address')\n ) : (\n label('save', 'Save address')\n )}\n </Button>\n </div>\n </div>\n </div>\n ),\n [\n addressTypesAdapter,\n addressTypesContext,\n disableActions,\n draft,\n editingId,\n fieldErrors,\n format,\n formatLoading,\n handleCancel,\n handleSave,\n generalError,\n label,\n labelPrefix,\n saving,\n showCoordinateFields,\n t,\n ]\n )\n\n return (\n <div className=\"space-y-4\">\n {!hideAddButton && hasAddresses ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openCreateForm}\n disabled={addDisabled}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {label('add', 'Add address')}\n </Button>\n </div>\n ) : null}\n {hasAddresses ? (\n <div className={gridClassName}>\n {addresses.map((address) => {\n if (isFormOpen && editingId === address.id) {\n return renderFormTile(`form-${address.id}`)\n }\n const isDeleting = deletingId === address.id\n return (\n <div\n key={address.id}\n className=\"group rounded-lg border border-border/70 bg-card p-4 text-sm transition hover:border-border\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-sm font-semibold text-foreground\">\n {address.name ?? label('labelFallback', 'Address')}\n </p>\n {address.isPrimary ? (\n <span className=\"rounded-full border border-border bg-muted px-2 py-0.5 text-overline font-semibold uppercase\">\n {label('primaryBadge', 'Primary')}\n </span>\n ) : null}\n </div>\n {address.purpose ? (\n <p className=\"text-xs text-muted-foreground\">\n {address.purpose}\n </p>\n ) : null}\n <AddressView address={address} format={format} className=\"text-sm text-foreground\" />\n </div>\n <div className=\"flex items-center gap-1 opacity-100 md:opacity-0 transition-opacity md:group-hover:opacity-100 focus-within:opacity-100\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleEdit(address)}\n disabled={disableActions}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleDelete(address.id)}\n disabled={disableActions}\n >\n {isDeleting ? (\n <span className=\"relative flex h-4 w-4 items-center justify-center text-destructive\">\n <span className=\"absolute h-4 w-4 animate-spin rounded-full border border-destructive border-t-transparent\" />\n </span>\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n </Button>\n </div>\n </div>\n </div>\n )\n })}\n {isFormOpen && !editingId ? renderFormTile('create') : null}\n </div>\n ) : isFormOpen ? (\n <div className={gridClassName}>\n {renderFormTile('create')}\n </div>\n ) : (\n <TabEmptyState\n title={emptyTitle}\n action={{\n label: emptyActionLabel,\n onClick: openCreateForm,\n disabled: addDisabled,\n }}\n />\n )}\n </div>\n )\n}\n\nexport default AddressTiles\n"],
5
+ "mappings": ";AAybQ,SAgDQ,UA/CN,KADF;AAvbR,YAAY,WAAW;AACvB,SAAS,SAAS,QAAQ,MAAM,QAAQ,SAAS;AACjD,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAC9B,SAAS,mCAAmC;AAC5C,SAAS,0BAA0B,+BAA+B;AAClE,SAAS,mBAA+C;AACxD,OAAO,mBAAiD;AA2FxD,MAAM,eAAkC;AAAA,EACtC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AACb;AAEA,MAAM,iBAAgD;AAAA,EACpD,MAAM;AAAA,EACN,SAAS;AAAA,EACT,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AACb;AAEA,SAAS,kBAAkB,OAAmC;AAC5D,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,UAAU;AACpC;AAEA,SAAS,yBAAyB,OAA2C;AAC3E,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO,CAAC;AACjD,QAAM,YAAa,MAAgC;AACnD,MAAI,CAAC,MAAM,QAAQ,SAAS,EAAG,QAAO,CAAC;AACvC,SAAO,UACJ,IAAI,CAAC,UAAW,SAAS,OAAO,UAAU,WAAY,QAAoC,IAAK,EAC/F,OAAO,CAAC,UAA4C,UAAU,IAAI;AACvE;AAEA,SAAS,oBAAoB,QAAiC,YAAoB,GAAe,QAAwB;AACvH,QAAM,QAAQ,CAAC,QAAgB,aAAqB,EAAE,GAAG,MAAM,IAAI,MAAM,IAAI,QAAQ;AACrF,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,aAAO,MAAM,sBAAsB,6BAA6B,EAAE,QAAQ,aAAa,UAAU;AAAA,IACnG,KAAK;AACH,UAAI,OAAO,YAAY,KAAK,OAAO,SAAS,UAAU;AACpD,eAAO,MAAM,uBAAuB,uBAAuB,EAAE,QAAQ,aAAa,UAAU;AAAA,MAC9F;AACA,aAAO,MAAM,sBAAsB,6BAA6B,EAAE,QAAQ,aAAa,UAAU;AAAA,IACnG,KAAK;AACH,UAAI,OAAO,OAAO,YAAY,UAAU;AACtC,eAAO,MAAM,sBAAsB,uBAAuB,EAAE,QAAQ,aAAa,UAAU,EACxF,QAAQ,WAAW,GAAG,OAAO,OAAO,EAAE;AAAA,MAC3C;AACA,aAAO,MAAM,sBAAsB,6BAA6B,EAAE,QAAQ,aAAa,UAAU;AAAA,IACnG;AACE,aAAO,MAAM,sBAAsB,6BAA6B,EAAE,QAAQ,aAAa,UAAU;AAAA,EACrG;AACF;AAEO,SAAS,aAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,uBAAuB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,eAAe,4BAA4B;AACjD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AACxD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAwB,IAAI;AACpE,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAA4B,YAAY;AACxE,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAwB,IAAI;AACtE,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAwB,IAAI;AAC1E,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAiD,CAAC,CAAC;AAC/F,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAgC,YAAY;AAC9E,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,KAAK;AAE9D,QAAM,QAAQ,MAAM;AAAA,IAClB,CAAC,QAAgB,UAAmB,WAClC,EAAE,GAAG,WAAW,IAAI,MAAM,IAAI,UAAU,MAAM;AAAA,IAChD,CAAC,aAAa,CAAC;AAAA,EACjB;AAEA,QAAM,cAAc,MAAM;AAAA,IACxB,OAAO;AAAA,MACL,MAAM,MAAM,gBAAgB,OAAO;AAAA,MACnC,SAAS,MAAM,eAAe,cAAc;AAAA,MAC5C,aAAa,MAAM,sBAAsB,cAAc;AAAA,MACvD,cAAc,MAAM,gBAAgB,gBAAgB;AAAA,MACpD,cAAc,MAAM,gBAAgB,gBAAgB;AAAA,MACpD,QAAQ,MAAM,iBAAiB,QAAQ;AAAA,MACvC,gBAAgB,MAAM,yBAAyB,iBAAiB;AAAA,MAChE,YAAY,MAAM,qBAAqB,aAAa;AAAA,MACpD,MAAM,MAAM,eAAe,MAAM;AAAA,MACjC,QAAQ,MAAM,iBAAiB,QAAQ;AAAA,MACvC,YAAY,MAAM,qBAAqB,aAAa;AAAA,MACpD,SAAS,MAAM,kBAAkB,SAAS;AAAA,MAC1C,UAAU,MAAM,mBAAmB,UAAU;AAAA,MAC7C,WAAW,MAAM,oBAAoB,WAAW;AAAA,MAChD,WAAW,MAAM,kBAAkB,iBAAiB;AAAA,IACtD;AAAA,IACA,CAAC,KAAK;AAAA,EACR;AACA,QAAM,kBAAkB,MAAM;AAAA,IAC5B,MAAO,WAAW,iBAAiB,YAAY,SAAS,YAAY;AAAA,IACpE,CAAC,YAAY,cAAc,YAAY,QAAQ,MAAM;AAAA,EACvD;AAEA,QAAM,YAAY,MAAM,YAAY,MAAM;AACxC,aAAS,YAAY;AACrB,mBAAe,CAAC,CAAC;AACjB,oBAAgB,IAAI;AACpB,iBAAa,IAAI;AAAA,EACnB,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,mBAAe,kBAAkB;AAC/B,UAAI,CAAC,YAAY;AACf,kBAAU,YAAY;AACtB,yBAAiB,KAAK;AACtB;AAAA,MACF;AACA,uBAAiB,IAAI;AACrB,UAAI;AACF,cAAM,QAAQ,MAAM,WAAW,aAAa;AAC5C,YAAI,CAAC,cAAc,UAAU,kBAAkB,UAAU,eAAe;AACtE,oBAAU,KAAK;AAAA,QACjB;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,CAAC,WAAW;AACd,gBAAM,UACJ,eAAe,SAAS,IAAI,UACxB,IAAI,UACJ,MAAM,mBAAmB,sCAAsC;AACrE,gBAAM,SAAS,OAAO;AAAA,QACxB;AAAA,MACF,UAAE;AACA,YAAI,CAAC,UAAW,kBAAiB,KAAK;AAAA,MACxC;AAAA,IACF;AACA,SAAK,gBAAgB;AACrB,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,eAAe,OAAO,YAAY,YAAY,CAAC;AAEnD,QAAM,iBAAiB,MAAM,YAAY,MAAM;AAC7C,cAAU;AACV,kBAAc,IAAI;AAAA,EACpB,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,cAAU;AACV,kBAAc,KAAK;AAAA,EACrB,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,aAAa,MAAM,YAAY,CAAC,UAAwB;AAC5D,aAAS;AAAA,MACP,MAAM,MAAM,QAAQ;AAAA,MACpB,SAAS,MAAM,WAAW;AAAA,MAC1B,aAAa,MAAM,eAAe;AAAA,MAClC,cAAc,MAAM,gBAAgB;AAAA,MACpC,cAAc,MAAM,gBAAgB;AAAA,MACpC,gBAAgB,MAAM,kBAAkB;AAAA,MACxC,YAAY,MAAM,cAAc;AAAA,MAChC,MAAM,MAAM,QAAQ;AAAA,MACpB,QAAQ,MAAM,UAAU;AAAA,MACxB,YAAY,MAAM,cAAc;AAAA,MAChC,SAAS,MAAM,WAAW;AAAA,MAC1B,UAAU,MAAM,YAAY,OAAO,OAAO,MAAM,QAAQ,IAAI;AAAA,MAC5D,WAAW,MAAM,aAAa,OAAO,OAAO,MAAM,SAAS,IAAI;AAAA,MAC/D,WAAW,MAAM,aAAa;AAAA,IAChC,CAAC;AACD,iBAAa,MAAM,EAAE;AACrB,kBAAc,IAAI;AAClB,mBAAe,CAAC,CAAC;AACjB,oBAAgB,IAAI;AAAA,EACtB,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,MAAM,YAAY,MAAe;AAChD,UAAM,SAAiD,CAAC;AACxD,QAAI,CAAC,MAAM,aAAa,KAAK,GAAG;AAC9B,aAAO,eAAe,MAAM,uBAAuB,uBAAuB,EAAE,QAAQ,aAAa,eAAe;AAAA,IAClH;AACA,eAAW,OAAO,CAAC,YAAY,WAAW,GAAY;AACpD,YAAM,SAAS,wBAAwB,KAAK,MAAM,GAAG,CAAC;AACtD,UAAI,OAAO,WAAW,WAAW;AAC/B,eAAO,GAAG,IAAI,MAAM,sBAAsB,6BAA6B,EAAE,QAAQ,aAAa,YAAY,GAAG,CAAC;AAAA,MAChH,WAAW,OAAO,WAAW,cAAc;AACzC,eAAO,GAAG,IAAI,MAAM,8BAA8B,+CAA+C,EAC9F,QAAQ,aAAa,YAAY,GAAG,CAAC,EACrC,QAAQ,WAAW,GAAG,OAAO,GAAG,EAAE,EAClC,QAAQ,WAAW,GAAG,OAAO,GAAG,EAAE;AAAA,MACvC;AAAA,IACF;AACA,QAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAClC,qBAAe,MAAM;AACrB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,cAAc,MAAM,UAAU,MAAM,WAAW,aAAa,OAAO,eAAe,CAAC;AAE7F,QAAM,aAAa,MAAM,YAAY,YAAY;AAC/C,QAAI,CAAC,SAAS,EAAG;AACjB,cAAU,IAAI;AACd,oBAAgB,IAAI;AACpB,QAAI;AACF,YAAM,UAAwB;AAAA,QAC5B,MAAM,kBAAkB,MAAM,IAAI;AAAA,QAClC,SAAS,kBAAkB,MAAM,OAAO;AAAA,QACxC,aAAa,kBAAkB,MAAM,WAAW;AAAA,QAChD,cAAc,MAAM,aAAa,KAAK;AAAA,QACtC,cAAc,kBAAkB,MAAM,YAAY;AAAA,QAClD,gBAAgB,kBAAkB,MAAM,cAAc;AAAA,QACtD,YAAY,kBAAkB,MAAM,UAAU;AAAA,QAC9C,MAAM,kBAAkB,MAAM,IAAI;AAAA,QAClC,QAAQ,kBAAkB,MAAM,MAAM;AAAA,QACtC,YAAY,kBAAkB,MAAM,UAAU;AAAA,QAC9C,SAAS,kBAAkB,MAAM,OAAO,GAAG,YAAY;AAAA;AAAA;AAAA;AAAA,QAIvD,UAAU,yBAAyB,MAAM,QAAQ,MAAM,YAAY,OAAO;AAAA,QAC1E,WAAW,yBAAyB,MAAM,SAAS,MAAM,YAAY,OAAO;AAAA,QAC5E,WAAW,MAAM;AAAA,MACnB;AACA,UAAI,aAAa,UAAU;AACzB,cAAM,SAAS,WAAW,OAAO;AAAA,MACnC,OAAO;AACL,cAAM,SAAS,OAAO;AAAA,MACxB;AACA,gBAAU;AACV,oBAAc,KAAK;AAAA,IACrB,SAAS,KAAK;AACZ,YAAM,UAAU,yBAAyB,GAAG;AAC5C,UAAI,QAAQ,QAAQ;AAClB,cAAM,aAAqD,CAAC;AAC5D,gBAAQ,QAAQ,CAAC,WAAW;AAC1B,gBAAM,OAAO,MAAM,QAAQ,OAAO,IAAI,IAAI,OAAO,OAAO,CAAC;AACzD,gBAAM,MAAM,OAAO,KAAK,CAAC,MAAM,WAAW,KAAK,CAAC,IAAI;AACpD,cAAI,CAAC,IAAK;AACV,gBAAM,WAAW,eAAe,GAAG;AACnC,cAAI,CAAC,SAAU;AACf,gBAAM,aAAa,aAAa,iBAAiB,kBAAmB,YAAY,QAAQ,KAAK;AAC7F,qBAAW,QAAQ,IAAI,oBAAoB,QAAQ,YAAY,GAAG,WAAW;AAAA,QAC/E,CAAC;AACD,uBAAe,UAAU;AACzB,wBAAgB,MAAM,sBAAsB,oCAAoC,CAAC;AACjF;AAAA,MACF;AACA,YAAM,UACJ,eAAe,SAAS,IAAI,UACxB,IAAI,UACJ,MAAM,SAAS,wBAAwB;AAC7C,sBAAgB,OAAO;AACvB,YAAM,SAAS,OAAO;AAAA,IACxB,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,OAAO,WAAW,aAAa,OAAO,aAAa,UAAU,UAAU,WAAW,GAAG,QAAQ,CAAC;AAElG,QAAM,eAAe,MAAM;AAAA,IACzB,OAAO,OAAe;AACpB,UAAI,CAAC,SAAU;AACf,oBAAc,EAAE;AAChB,UAAI;AACF,cAAM,SAAS,EAAE;AACjB,YAAI,cAAc,IAAI;AACpB,oBAAU;AACV,wBAAc,KAAK;AAAA,QACrB;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,UACJ,eAAe,SAAS,IAAI,UACxB,IAAI,UACJ,MAAM,SAAS,0BAA0B;AAC/C,cAAM,SAAS,OAAO;AAAA,MACxB,UAAE;AACA,sBAAc,IAAI;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAAC,WAAW,OAAO,UAAU,SAAS;AAAA,EACxC;AAEA,QAAM,iBAAiB,UAAU,gBAAgB,eAAe;AAChE,QAAM,YAAY,cAAc;AAChC,QAAM,cAAc,kBAAkB;AACtC,QAAM,eAAe,UAAU,SAAS;AACxC,QAAM,aAAa,mBAAmB;AACtC,QAAM,mBAAmB,yBAAyB,MAAM,OAAO,aAAa;AAE5E,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,sBAAkB,EAAE,gBAAgB,YAAY,CAAC;AAAA,EACnD,GAAG,CAAC,mBAAmB,gBAAgB,WAAW,CAAC;AAEnD,QAAM;AAAA,IACJ,MAAM,MAAM;AACV,UAAI,kBAAmB,mBAAkB,IAAI;AAAA,IAC/C;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AAEA,QAAM,iBAAiB,MAAM;AAAA,IAC3B,CAAC,QACC;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QACV,WAAW,CAAC,UAAU;AACpB,cAAI,EAAE,MAAM,WAAW,MAAM,SAAU;AACvC,cAAI,MAAM,QAAQ,QAAS;AAC3B,gBAAM,eAAe;AACrB,cAAI,eAAgB;AACpB,eAAK,WAAW;AAAA,QAClB;AAAA,QAEA;AAAA,+BAAC,SAAI,WAAU,yGACb;AAAA,gCAAC,UACE,sBACG,MAAM,aAAa,cAAc,IACjC,MAAM,YAAY,aAAa,GACrC;AAAA,YACA,oBAAC,UAAO,MAAK,UAAS,SAAQ,SAAQ,MAAK,QAAO,SAAS,cAAc,UAAU,gBACjF,8BAAC,KAAE,WAAU,WAAU,GACzB;AAAA,aACF;AAAA,UACA,qBAAC,SAAI,WAAU,kBACZ;AAAA,4BACC,oBAAC,OAAE,WAAU,iCACV,gBAAM,iBAAiB,mCAA8B,GACxD,IACE;AAAA,YACJ;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO;AAAA,gBACP,UAAU,CAAC,SAAS;AAClB,2BAAS,EAAE,GAAG,MAAM,UAAU,KAAK,YAAY,IAAI,WAAW,KAAK,aAAa,GAAG,CAAC;AACpF,sBAAI,OAAO,KAAK,WAAW,EAAE,QAAQ;AACnC,0BAAM,aAAa,EAAE,GAAG,YAAY;AACnC,oBAAC,OAAO,KAAK,UAAU,EAAsB,QAAQ,CAACA,SAAQ;AAC7D,4BAAM,YAAa,KAAiCA,IAAG;AACvD,0BAAI,cAAc,UAAa,cAAc,QAAQ,GAAG,SAAS,GAAG,QAAQ;AAC1E,+BAAO,WAAWA,IAAG;AAAA,sBACvB;AAAA,oBACF,CAAC;AACD,mCAAe,UAAU;AAAA,kBAC3B;AAAA,gBACF;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,UAAU;AAAA,gBACV,QAAQ;AAAA,gBACR,gBAAgB,CAAC;AAAA,gBACjB;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA;AAAA,YACF;AAAA,YACC,eAAe,oBAAC,OAAE,WAAU,kCAAkC,wBAAa,IAAO;AAAA,YACnF,qBAAC,SAAI,WAAU,oCACb;AAAA,kCAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,cAAc,UAAU,gBACtE,gBAAM,UAAU,QAAQ,GAC3B;AAAA,cACA,oBAAC,UAAO,MAAK,UAAS,SAAS,YAAY,UAAU,gBAClD,mBACC,iCACE;AAAA,oCAAC,WAAQ,WAAU,6BAA4B;AAAA,gBAC9C,YACG,MAAM,YAAY,gBAAW,IAC7B,MAAM,UAAU,cAAS;AAAA,iBAC/B,IACE,YACF,MAAM,UAAU,gBAAgB,IAEhC,MAAM,QAAQ,cAAc,GAEhC;AAAA,eACF;AAAA,aACF;AAAA;AAAA;AAAA,MAvEK;AAAA,IAwEP;AAAA,IAEF;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,WAAU,aACZ;AAAA,KAAC,iBAAiB,eACjB,oBAAC,SAAI,WAAU,oBACb;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,SAAS;AAAA,QACT,UAAU;AAAA,QAEV;AAAA,8BAAC,QAAK,WAAU,gBAAe;AAAA,UAC9B,MAAM,OAAO,aAAa;AAAA;AAAA;AAAA,IAC7B,GACF,IACE;AAAA,IACH,eACC,qBAAC,SAAI,WAAW,eACb;AAAA,gBAAU,IAAI,CAAC,YAAY;AAC1B,YAAI,cAAc,cAAc,QAAQ,IAAI;AAC1C,iBAAO,eAAe,QAAQ,QAAQ,EAAE,EAAE;AAAA,QAC5C;AACA,cAAM,aAAa,eAAe,QAAQ;AAC1C,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YAEV,+BAAC,SAAI,WAAU,0CACb;AAAA,mCAAC,SAAI,WAAU,aACb;AAAA,qCAAC,SAAI,WAAU,qCACb;AAAA,sCAAC,OAAE,WAAU,yCACV,kBAAQ,QAAQ,MAAM,iBAAiB,SAAS,GACnD;AAAA,kBACC,QAAQ,YACP,oBAAC,UAAK,WAAU,gGACb,gBAAM,gBAAgB,SAAS,GAClC,IACE;AAAA,mBACN;AAAA,gBACC,QAAQ,UACP,oBAAC,OAAE,WAAU,iCACV,kBAAQ,SACX,IACE;AAAA,gBACJ,oBAAC,eAAY,SAAkB,QAAgB,WAAU,2BAA0B;AAAA,iBACrF;AAAA,cACA,qBAAC,SAAI,WAAU,2HACb;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAQ;AAAA,oBACR,MAAK;AAAA,oBACL,SAAS,MAAM,WAAW,OAAO;AAAA,oBACjC,UAAU;AAAA,oBAEV,8BAAC,UAAO,WAAU,WAAU;AAAA;AAAA,gBAC9B;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAQ;AAAA,oBACR,MAAK;AAAA,oBACL,SAAS,MAAM,aAAa,QAAQ,EAAE;AAAA,oBACtC,UAAU;AAAA,oBAET,uBACC,oBAAC,UAAK,WAAU,sEACd,8BAAC,UAAK,WAAU,6FAA4F,GAC9G,IAEA,oBAAC,UAAO,WAAU,WAAU;AAAA;AAAA,gBAEhC;AAAA,iBACF;AAAA,eACF;AAAA;AAAA,UAhDK,QAAQ;AAAA,QAiDf;AAAA,MAEJ,CAAC;AAAA,MACA,cAAc,CAAC,YAAY,eAAe,QAAQ,IAAI;AAAA,OACzD,IACE,aACF,oBAAC,SAAI,WAAW,eACb,yBAAe,QAAQ,GAC1B,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,QAAQ;AAAA,UACN,OAAO;AAAA,UACP,SAAS;AAAA,UACT,UAAU;AAAA,QACZ;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;AAEA,IAAO,uBAAQ;",
6
6
  "names": ["key"]
7
7
  }
@@ -1,8 +1,19 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Button } from "../../primitives/button.js";
5
- import { Input } from "../../primitives/input.js";
4
+ import { DateRangePicker } from "../../primitives/date-range-picker.js";
5
+ import { IconButton } from "../../primitives/icon-button.js";
6
+ import {
7
+ SegmentedControl,
8
+ SegmentedControlItem
9
+ } from "../../primitives/segmented-control.js";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue
16
+ } from "../../primitives/select.js";
6
17
  import { cn } from "@open-mercato/shared/lib/utils";
7
18
  import { useT } from "@open-mercato/shared/lib/i18n/context";
8
19
  import { addDays } from "date-fns/addDays";
@@ -24,16 +35,19 @@ const VIEW_OPTIONS = [
24
35
  { id: "month", labelKey: "schedule.view.month", fallback: "Month" },
25
36
  { id: "agenda", labelKey: "schedule.view.agenda", fallback: "Agenda" }
26
37
  ];
27
- function formatDateInputValue(value) {
28
- const year = value.getFullYear();
29
- const month = String(value.getMonth() + 1).padStart(2, "0");
30
- const day = String(value.getDate()).padStart(2, "0");
31
- return `${year}-${month}-${day}`;
32
- }
33
- function parseDateInputValue(value, fallback) {
34
- if (!value) return fallback;
35
- const next = /* @__PURE__ */ new Date(`${value}T00:00:00`);
36
- return Number.isNaN(next.getTime()) ? fallback : next;
38
+ function getTimezoneOptions(current) {
39
+ const options = /* @__PURE__ */ new Set();
40
+ if (current) options.add(current);
41
+ options.add("UTC");
42
+ const resolved = Intl.DateTimeFormat().resolvedOptions().timeZone;
43
+ if (resolved) options.add(resolved);
44
+ const intlWithSupportedValues = Intl;
45
+ if (typeof intlWithSupportedValues.supportedValuesOf === "function") {
46
+ intlWithSupportedValues.supportedValuesOf("timeZone").forEach((timezone) => {
47
+ if (timezone) options.add(timezone);
48
+ });
49
+ }
50
+ return Array.from(options).sort((a, b) => a.localeCompare(b));
37
51
  }
38
52
  function ScheduleToolbar({
39
53
  view,
@@ -103,70 +117,73 @@ function ScheduleToolbar({
103
117
  const nextStart = startOfDay(addDays(range.start, multiplier * rangeLength));
104
118
  onRangeChange({ start: nextStart, end: endOfDay(addDays(nextStart, rangeLength - 1)) });
105
119
  }, [onRangeChange, range.start, rangeLength, view]);
106
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3 rounded-xl border bg-card p-4 md:flex-row md:items-center md:justify-between", className), children: [
107
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: VIEW_OPTIONS.map((option) => /* @__PURE__ */ jsx(
108
- Button,
120
+ const timezoneOptions = React.useMemo(() => getTimezoneOptions(timezone), [timezone]);
121
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-wrap items-center gap-x-4 gap-y-2 rounded-xl border bg-card p-3", className), children: [
122
+ /* @__PURE__ */ jsx(
123
+ SegmentedControl,
109
124
  {
110
- variant: view === option.id ? "default" : "outline",
111
- size: "sm",
112
- onClick: () => {
113
- if (option.id === view) return;
114
- onViewChange(option.id);
115
- onRangeChange(deriveRangeForView(/* @__PURE__ */ new Date(), option.id));
125
+ value: view,
126
+ onValueChange: (value) => {
127
+ const nextView = value;
128
+ if (nextView === view) return;
129
+ onViewChange(nextView);
130
+ onRangeChange(deriveRangeForView(/* @__PURE__ */ new Date(), nextView));
116
131
  },
117
- children: t(option.labelKey, option.fallback)
118
- },
119
- option.id
120
- )) }),
121
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
122
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => shiftRange("prev"), "aria-label": t("schedule.range.prev", "Previous"), children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": true }) }),
123
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-foreground", children: rangeLabel }),
124
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => shiftRange("next"), "aria-label": t("schedule.range.next", "Next"), children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": true }) })
132
+ "aria-label": t("schedule.view.label", "Schedule view"),
133
+ className: "shrink-0",
134
+ children: VIEW_OPTIONS.map((option) => /* @__PURE__ */ jsx(SegmentedControlItem, { value: option.id, children: t(option.labelKey, option.fallback) }, option.id))
135
+ }
136
+ ),
137
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
138
+ /* @__PURE__ */ jsx(
139
+ IconButton,
140
+ {
141
+ type: "button",
142
+ variant: "outline",
143
+ onClick: () => shiftRange("prev"),
144
+ "aria-label": t("schedule.range.prev", "Previous"),
145
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": true })
146
+ }
147
+ ),
148
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 whitespace-nowrap px-1 text-sm font-medium text-foreground", children: rangeLabel }),
149
+ /* @__PURE__ */ jsx(
150
+ IconButton,
151
+ {
152
+ type: "button",
153
+ variant: "outline",
154
+ onClick: () => shiftRange("next"),
155
+ "aria-label": t("schedule.range.next", "Next"),
156
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": true })
157
+ }
158
+ )
125
159
  ] }),
126
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
127
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
128
- /* @__PURE__ */ jsx("span", { children: t("schedule.range.start", "Start") }),
129
- /* @__PURE__ */ jsx(
130
- Input,
131
- {
132
- type: "date",
133
- value: formatDateInputValue(range.start),
134
- onChange: (event) => {
135
- const nextStart = parseDateInputValue(event.target.value, range.start);
136
- onRangeChange({ start: nextStart, end: range.end });
137
- },
138
- className: "h-8 w-full sm:w-[140px]"
139
- }
140
- )
141
- ] }),
142
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
143
- /* @__PURE__ */ jsx("span", { children: t("schedule.range.end", "End") }),
144
- /* @__PURE__ */ jsx(
145
- Input,
146
- {
147
- type: "date",
148
- value: formatDateInputValue(range.end),
149
- onChange: (event) => {
150
- const nextEnd = parseDateInputValue(event.target.value, range.end);
151
- onRangeChange({ start: range.start, end: nextEnd });
152
- },
153
- className: "h-8 w-full sm:w-[140px]"
154
- }
155
- )
156
- ] }),
157
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
158
- /* @__PURE__ */ jsx("span", { children: t("schedule.range.timezone", "Timezone") }),
160
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 md:ml-auto", children: [
161
+ /* @__PURE__ */ jsx(
162
+ DateRangePicker,
163
+ {
164
+ value: range,
165
+ onChange: (next) => {
166
+ if (!next) return;
167
+ onRangeChange({ start: startOfDay(next.start), end: endOfDay(next.end) });
168
+ },
169
+ size: "sm",
170
+ showPresets: false,
171
+ numberOfMonths: 2,
172
+ "aria-label": t("schedule.range.label", "Date range")
173
+ }
174
+ ),
175
+ onTimezoneChange ? /* @__PURE__ */ jsxs(Select, { value: timezone ?? void 0, onValueChange: onTimezoneChange, children: [
159
176
  /* @__PURE__ */ jsx(
160
- Input,
177
+ SelectTrigger,
161
178
  {
162
- type: "text",
163
- value: timezone ?? "",
164
- onChange: (event) => onTimezoneChange?.(event.target.value),
165
- className: "h-8 w-full sm:w-[180px]",
166
- placeholder: t("schedule.range.timezone.placeholder", "UTC")
179
+ size: "sm",
180
+ className: "w-auto min-w-[10rem]",
181
+ "aria-label": t("schedule.range.timezone", "Timezone"),
182
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("schedule.range.timezone.placeholder", "UTC") })
167
183
  }
168
- )
169
- ] })
184
+ ),
185
+ /* @__PURE__ */ jsx(SelectContent, { children: timezoneOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option, children: option }, option)) })
186
+ ] }) : null
170
187
  ] })
171
188
  ] });
172
189
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/schedule/ScheduleToolbar.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../../primitives/button'\nimport { Input } from '../../primitives/input'\nimport type { ScheduleRange, ScheduleViewMode } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { addDays } from 'date-fns/addDays'\nimport { addMonths } from 'date-fns/addMonths'\nimport { addWeeks } from 'date-fns/addWeeks'\nimport { differenceInCalendarDays } from 'date-fns/differenceInCalendarDays'\nimport { endOfDay } from 'date-fns/endOfDay'\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { endOfWeek } from 'date-fns/endOfWeek'\nimport { format } from 'date-fns/format'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { startOfMonth } from 'date-fns/startOfMonth'\nimport { startOfWeek } from 'date-fns/startOfWeek'\nimport { enUS } from 'date-fns/locale/en-US'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\n\nconst VIEW_OPTIONS: Array<{ id: ScheduleViewMode; labelKey: string; fallback: string }> = [\n { id: 'day', labelKey: 'schedule.view.day', fallback: 'Day' },\n { id: 'week', labelKey: 'schedule.view.week', fallback: 'Week' },\n { id: 'month', labelKey: 'schedule.view.month', fallback: 'Month' },\n { id: 'agenda', labelKey: 'schedule.view.agenda', fallback: 'Agenda' },\n]\n\nfunction formatDateInputValue(value: Date): string {\n const year = value.getFullYear()\n const month = String(value.getMonth() + 1).padStart(2, '0')\n const day = String(value.getDate()).padStart(2, '0')\n return `${year}-${month}-${day}`\n}\n\nfunction parseDateInputValue(value: string, fallback: Date): Date {\n if (!value) return fallback\n const next = new Date(`${value}T00:00:00`)\n return Number.isNaN(next.getTime()) ? fallback : next\n}\n\nexport type ScheduleToolbarProps = {\n view: ScheduleViewMode\n range: ScheduleRange\n timezone?: string\n onViewChange: (view: ScheduleViewMode) => void\n onRangeChange: (range: ScheduleRange) => void\n onTimezoneChange?: (timezone: string) => void\n className?: string\n}\n\nexport function ScheduleToolbar({\n view,\n range,\n timezone,\n onViewChange,\n onRangeChange,\n onTimezoneChange,\n className,\n}: ScheduleToolbarProps) {\n const t = useT()\n const rangeLength = React.useMemo(\n () => Math.max(1, differenceInCalendarDays(range.end, range.start) + 1),\n [range.end, range.start],\n )\n const deriveRangeForView = React.useCallback((base: Date, nextView: ScheduleViewMode): ScheduleRange => {\n if (nextView === 'day') {\n const start = startOfDay(base)\n return { start, end: endOfDay(start) }\n }\n if (nextView === 'week') {\n return { start: startOfWeek(base, { locale: enUS }), end: endOfWeek(base, { locale: enUS }) }\n }\n if (nextView === 'month') {\n return { start: startOfMonth(base), end: endOfMonth(base) }\n }\n const start = startOfDay(base)\n return { start, end: endOfDay(addDays(start, rangeLength - 1)) }\n }, [rangeLength])\n const rangeLabel = React.useMemo(() => {\n if (view === 'day') {\n return format(range.start, 'EEE, MMM d')\n }\n if (view === 'week') {\n const startLabel = format(range.start, 'MMM d')\n const endLabel = format(range.end, 'MMM d')\n const yearLabel = format(range.start, 'yyyy')\n return `${startLabel} - ${endLabel}, ${yearLabel}`\n }\n if (view === 'month') {\n return format(range.start, 'MMMM yyyy')\n }\n const startLabel = format(range.start, 'MMM d')\n const endLabel = format(range.end, 'MMM d, yyyy')\n return `${startLabel} - ${endLabel}`\n }, [range.end, range.start, view])\n\n const shiftRange = React.useCallback((direction: 'prev' | 'next') => {\n const multiplier = direction === 'prev' ? -1 : 1\n if (view === 'day') {\n const nextStart = startOfDay(addDays(range.start, multiplier))\n onRangeChange({ start: nextStart, end: endOfDay(nextStart) })\n return\n }\n if (view === 'week') {\n const base = addWeeks(range.start, multiplier)\n onRangeChange({\n start: startOfWeek(base, { locale: enUS }),\n end: endOfWeek(base, { locale: enUS }),\n })\n return\n }\n if (view === 'month') {\n const base = addMonths(range.start, multiplier)\n onRangeChange({ start: startOfMonth(base), end: endOfMonth(base) })\n return\n }\n const nextStart = startOfDay(addDays(range.start, multiplier * rangeLength))\n onRangeChange({ start: nextStart, end: endOfDay(addDays(nextStart, rangeLength - 1)) })\n }, [onRangeChange, range.start, rangeLength, view])\n\n return (\n <div className={cn('flex flex-col gap-3 rounded-xl border bg-card p-4 md:flex-row md:items-center md:justify-between', className)}>\n <div className=\"flex flex-wrap items-center gap-2\">\n {VIEW_OPTIONS.map((option) => (\n <Button\n key={option.id}\n variant={view === option.id ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n if (option.id === view) return\n onViewChange(option.id)\n onRangeChange(deriveRangeForView(new Date(), option.id))\n }}\n >\n {t(option.labelKey, option.fallback)}\n </Button>\n ))}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => shiftRange('prev')} aria-label={t('schedule.range.prev', 'Previous')}>\n <ChevronLeft className=\"size-4\" aria-hidden />\n </Button>\n <div className=\"text-sm font-medium text-foreground\">{rangeLabel}</div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => shiftRange('next')} aria-label={t('schedule.range.next', 'Next')}>\n <ChevronRight className=\"size-4\" aria-hidden />\n </Button>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{t('schedule.range.start', 'Start')}</span>\n <Input\n type=\"date\"\n value={formatDateInputValue(range.start)}\n onChange={(event) => {\n const nextStart = parseDateInputValue(event.target.value, range.start)\n onRangeChange({ start: nextStart, end: range.end })\n }}\n className=\"h-8 w-full sm:w-[140px]\"\n />\n </label>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{t('schedule.range.end', 'End')}</span>\n <Input\n type=\"date\"\n value={formatDateInputValue(range.end)}\n onChange={(event) => {\n const nextEnd = parseDateInputValue(event.target.value, range.end)\n onRangeChange({ start: range.start, end: nextEnd })\n }}\n className=\"h-8 w-full sm:w-[140px]\"\n />\n </label>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{t('schedule.range.timezone', 'Timezone')}</span>\n <Input\n type=\"text\"\n value={timezone ?? ''}\n onChange={(event) => onTimezoneChange?.(event.target.value)}\n className=\"h-8 w-full sm:w-[180px]\"\n placeholder={t('schedule.range.timezone.placeholder', 'UTC')}\n />\n </label>\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { DateRangePicker } from '../../primitives/date-range-picker'\nimport { IconButton } from '../../primitives/icon-button'\nimport {\n SegmentedControl,\n SegmentedControlItem,\n} from '../../primitives/segmented-control'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport type { ScheduleRange, ScheduleViewMode } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { addDays } from 'date-fns/addDays'\nimport { addMonths } from 'date-fns/addMonths'\nimport { addWeeks } from 'date-fns/addWeeks'\nimport { differenceInCalendarDays } from 'date-fns/differenceInCalendarDays'\nimport { endOfDay } from 'date-fns/endOfDay'\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { endOfWeek } from 'date-fns/endOfWeek'\nimport { format } from 'date-fns/format'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { startOfMonth } from 'date-fns/startOfMonth'\nimport { startOfWeek } from 'date-fns/startOfWeek'\nimport { enUS } from 'date-fns/locale/en-US'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\n\nconst VIEW_OPTIONS: Array<{ id: ScheduleViewMode; labelKey: string; fallback: string }> = [\n { id: 'day', labelKey: 'schedule.view.day', fallback: 'Day' },\n { id: 'week', labelKey: 'schedule.view.week', fallback: 'Week' },\n { id: 'month', labelKey: 'schedule.view.month', fallback: 'Month' },\n { id: 'agenda', labelKey: 'schedule.view.agenda', fallback: 'Agenda' },\n]\n\nfunction getTimezoneOptions(current?: string): string[] {\n const options = new Set<string>()\n if (current) options.add(current)\n options.add('UTC')\n const resolved = Intl.DateTimeFormat().resolvedOptions().timeZone\n if (resolved) options.add(resolved)\n const intlWithSupportedValues = Intl as typeof Intl & { supportedValuesOf?: (input: 'timeZone') => string[] }\n if (typeof intlWithSupportedValues.supportedValuesOf === 'function') {\n intlWithSupportedValues.supportedValuesOf('timeZone').forEach((timezone) => {\n if (timezone) options.add(timezone)\n })\n }\n return Array.from(options).sort((a, b) => a.localeCompare(b))\n}\n\nexport type ScheduleToolbarProps = {\n view: ScheduleViewMode\n range: ScheduleRange\n timezone?: string\n onViewChange: (view: ScheduleViewMode) => void\n onRangeChange: (range: ScheduleRange) => void\n onTimezoneChange?: (timezone: string) => void\n className?: string\n}\n\nexport function ScheduleToolbar({\n view,\n range,\n timezone,\n onViewChange,\n onRangeChange,\n onTimezoneChange,\n className,\n}: ScheduleToolbarProps) {\n const t = useT()\n const rangeLength = React.useMemo(\n () => Math.max(1, differenceInCalendarDays(range.end, range.start) + 1),\n [range.end, range.start],\n )\n const deriveRangeForView = React.useCallback((base: Date, nextView: ScheduleViewMode): ScheduleRange => {\n if (nextView === 'day') {\n const start = startOfDay(base)\n return { start, end: endOfDay(start) }\n }\n if (nextView === 'week') {\n return { start: startOfWeek(base, { locale: enUS }), end: endOfWeek(base, { locale: enUS }) }\n }\n if (nextView === 'month') {\n return { start: startOfMonth(base), end: endOfMonth(base) }\n }\n const start = startOfDay(base)\n return { start, end: endOfDay(addDays(start, rangeLength - 1)) }\n }, [rangeLength])\n const rangeLabel = React.useMemo(() => {\n if (view === 'day') {\n return format(range.start, 'EEE, MMM d')\n }\n if (view === 'week') {\n const startLabel = format(range.start, 'MMM d')\n const endLabel = format(range.end, 'MMM d')\n const yearLabel = format(range.start, 'yyyy')\n return `${startLabel} - ${endLabel}, ${yearLabel}`\n }\n if (view === 'month') {\n return format(range.start, 'MMMM yyyy')\n }\n const startLabel = format(range.start, 'MMM d')\n const endLabel = format(range.end, 'MMM d, yyyy')\n return `${startLabel} - ${endLabel}`\n }, [range.end, range.start, view])\n\n const shiftRange = React.useCallback((direction: 'prev' | 'next') => {\n const multiplier = direction === 'prev' ? -1 : 1\n if (view === 'day') {\n const nextStart = startOfDay(addDays(range.start, multiplier))\n onRangeChange({ start: nextStart, end: endOfDay(nextStart) })\n return\n }\n if (view === 'week') {\n const base = addWeeks(range.start, multiplier)\n onRangeChange({\n start: startOfWeek(base, { locale: enUS }),\n end: endOfWeek(base, { locale: enUS }),\n })\n return\n }\n if (view === 'month') {\n const base = addMonths(range.start, multiplier)\n onRangeChange({ start: startOfMonth(base), end: endOfMonth(base) })\n return\n }\n const nextStart = startOfDay(addDays(range.start, multiplier * rangeLength))\n onRangeChange({ start: nextStart, end: endOfDay(addDays(nextStart, rangeLength - 1)) })\n }, [onRangeChange, range.start, rangeLength, view])\n\n const timezoneOptions = React.useMemo(() => getTimezoneOptions(timezone), [timezone])\n\n return (\n <div className={cn('flex flex-wrap items-center gap-x-4 gap-y-2 rounded-xl border bg-card p-3', className)}>\n <SegmentedControl\n value={view}\n onValueChange={(value) => {\n const nextView = value as ScheduleViewMode\n if (nextView === view) return\n onViewChange(nextView)\n onRangeChange(deriveRangeForView(new Date(), nextView))\n }}\n aria-label={t('schedule.view.label', 'Schedule view')}\n className=\"shrink-0\"\n >\n {VIEW_OPTIONS.map((option) => (\n <SegmentedControlItem key={option.id} value={option.id}>\n {t(option.labelKey, option.fallback)}\n </SegmentedControlItem>\n ))}\n </SegmentedControl>\n <div className=\"flex shrink-0 items-center gap-1\">\n <IconButton\n type=\"button\"\n variant=\"outline\"\n onClick={() => shiftRange('prev')}\n aria-label={t('schedule.range.prev', 'Previous')}\n >\n <ChevronLeft className=\"size-4\" aria-hidden />\n </IconButton>\n <div className=\"min-w-0 whitespace-nowrap px-1 text-sm font-medium text-foreground\">{rangeLabel}</div>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n onClick={() => shiftRange('next')}\n aria-label={t('schedule.range.next', 'Next')}\n >\n <ChevronRight className=\"size-4\" aria-hidden />\n </IconButton>\n </div>\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 md:ml-auto\">\n <DateRangePicker\n value={range}\n onChange={(next) => {\n if (!next) return\n onRangeChange({ start: startOfDay(next.start), end: endOfDay(next.end) })\n }}\n size=\"sm\"\n showPresets={false}\n numberOfMonths={2}\n aria-label={t('schedule.range.label', 'Date range')}\n />\n {onTimezoneChange ? (\n <Select value={timezone ?? undefined} onValueChange={onTimezoneChange}>\n <SelectTrigger\n size=\"sm\"\n className=\"w-auto min-w-[10rem]\"\n aria-label={t('schedule.range.timezone', 'Timezone')}\n >\n <SelectValue placeholder={t('schedule.range.timezone.placeholder', 'UTC')} />\n </SelectTrigger>\n <SelectContent>\n {timezoneOptions.map((option) => (\n <SelectItem key={option} value={option}>\n {option}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : null}\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["start", "startLabel", "endLabel", "nextStart"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.7-develop.6788.1.c2a1520a30",
3
+ "version": "0.6.7-develop.6795.1.8a3f27921c",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -137,31 +137,31 @@
137
137
  "@dnd-kit/sortable": "^10.0.0",
138
138
  "@dnd-kit/utilities": "^3.2.2",
139
139
  "@mdxeditor/editor": "^4.0.4",
140
- "@radix-ui/react-accordion": "^1.2.16",
141
- "@radix-ui/react-dialog": "^1.1.19",
142
- "@radix-ui/react-popover": "^1.1.19",
143
- "@radix-ui/react-radio-group": "^1.4.3",
144
- "@radix-ui/react-scroll-area": "^1.2.14",
145
- "@radix-ui/react-select": "^2.3.3",
146
- "@radix-ui/react-slider": "^1.4.3",
147
- "@radix-ui/react-tooltip": "^1.2.12",
140
+ "@radix-ui/react-accordion": "^1.2.20",
141
+ "@radix-ui/react-dialog": "^1.1.23",
142
+ "@radix-ui/react-popover": "^1.1.23",
143
+ "@radix-ui/react-radio-group": "^1.4.7",
144
+ "@radix-ui/react-scroll-area": "^1.2.18",
145
+ "@radix-ui/react-select": "^2.3.7",
146
+ "@radix-ui/react-slider": "^1.4.7",
147
+ "@radix-ui/react-tooltip": "^1.2.16",
148
148
  "@tanstack/react-virtual": "^3.14.6",
149
149
  "cmdk": "^1.0.0",
150
150
  "date-fns": "4.4.0",
151
151
  "react-big-calendar": "^1.20.0",
152
152
  "react-day-picker": "^10.0.1",
153
153
  "react-markdown": "^10.1.0",
154
- "recharts": "^3.9.2",
154
+ "recharts": "^3.10.1",
155
155
  "remark-gfm": "^4.0.1"
156
156
  },
157
157
  "peerDependencies": {
158
- "@open-mercato/shared": "0.6.7-develop.6788.1.c2a1520a30",
158
+ "@open-mercato/shared": "0.6.7-develop.6795.1.8a3f27921c",
159
159
  "react": ">=18.0.0",
160
160
  "react-dom": ">=18.0.0",
161
161
  "react-is": ">=18.0.0"
162
162
  },
163
163
  "devDependencies": {
164
- "@open-mercato/shared": "0.6.7-develop.6788.1.c2a1520a30",
164
+ "@open-mercato/shared": "0.6.7-develop.6795.1.8a3f27921c",
165
165
  "@testing-library/dom": "^10.4.1",
166
166
  "@testing-library/jest-dom": "^6.9.1",
167
167
  "@testing-library/react": "^16.3.1",
@@ -170,10 +170,10 @@
170
170
  "@types/react-dom": "^19.2.3",
171
171
  "jest": "^30.4.2",
172
172
  "jest-environment-jsdom": "^30.4.1",
173
- "react": "19.2.7",
174
- "react-dom": "19.2.7",
175
- "react-is": "^19.2.7",
176
- "ts-jest": "^29.4.11",
173
+ "react": "19.2.8",
174
+ "react-dom": "19.2.8",
175
+ "react-is": "^19.2.8",
176
+ "ts-jest": "^29.4.12",
177
177
  "ts-morph": "^28.0.0",
178
178
  "typescript": "7.0.2"
179
179
  },
@@ -42,16 +42,16 @@ type NextStepCalloutProps = {
42
42
  const STEP_STYLES: Record<NonNullable<NextStepCalloutStep['state']>, string> = {
43
43
  pending: '',
44
44
  active: 'border-primary/40 bg-primary/10 text-primary',
45
- completed: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-200',
45
+ completed: 'border-status-success-border bg-status-success-bg text-status-success-text',
46
46
  }
47
47
 
48
48
  const STATUS_STYLES: Record<NextStepCalloutStatusTone, string> = {
49
49
  default: 'border-border bg-background/80 text-foreground',
50
50
  info: 'border-primary/20 bg-primary/5 text-primary',
51
- success: 'border-emerald-500/20 bg-emerald-500/5 text-emerald-200',
52
- warning: 'border-amber-500/20 bg-amber-500/5 text-amber-200',
51
+ success: 'border-status-success-border bg-status-success-bg text-status-success-text',
52
+ warning: 'border-status-warning-border bg-status-warning-bg text-status-warning-text',
53
53
  danger: 'border-destructive/20 bg-destructive/5 text-destructive',
54
- muted: 'border-zinc-500/20 bg-zinc-500/5 text-zinc-200',
54
+ muted: 'border-status-neutral-border bg-status-neutral-bg text-status-neutral-text',
55
55
  }
56
56
 
57
57
  export function NextStepCallout({
@@ -7,7 +7,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
7
7
  import { TabEmptyState } from '@open-mercato/ui/backend/detail'
8
8
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
9
9
  import { normalizeCoordinateInput, validateCoordinateInput } from '@open-mercato/shared/lib/location/coordinates'
10
- import { AddressView, formatAddressString, type AddressFormatStrategy } from './addressFormat'
10
+ import { AddressView, type AddressFormatStrategy } from './addressFormat'
11
11
  import AddressEditor, { type AddressTypesAdapter } from './AddressEditor'
12
12
  import {
13
13
  Dialog,
@@ -176,7 +176,7 @@ export function AddressTiles<C = unknown>({
176
176
  t,
177
177
  emptyLabel,
178
178
  isSubmitting = false,
179
- gridClassName = 'grid grid-cols-1 gap-2 sm:gap-4 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',
179
+ gridClassName = 'grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-2 sm:gap-4',
180
180
  hideAddButton = false,
181
181
  onAddActionChange,
182
182
  emptyStateTitle,
@@ -480,7 +480,7 @@ export function AddressTiles<C = unknown>({
480
480
  addressTypesAdapter={addressTypesAdapter}
481
481
  addressTypesContext={addressTypesContext}
482
482
  />
483
- {generalError ? <p className="text-xs text-red-600">{generalError}</p> : null}
483
+ {generalError ? <p className="text-xs text-status-error-text">{generalError}</p> : null}
484
484
  <div className="flex flex-wrap justify-end gap-2">
485
485
  <Button type="button" variant="outline" onClick={handleCancel} disabled={disableActions}>
486
486
  {label('cancel', 'Cancel')}
@@ -569,9 +569,6 @@ export function AddressTiles<C = unknown>({
569
569
  </p>
570
570
  ) : null}
571
571
  <AddressView address={address} format={format} className="text-sm text-foreground" />
572
- <p className="text-xs text-muted-foreground">
573
- {formatAddressString(address, format)}
574
- </p>
575
572
  </div>
576
573
  <div className="flex items-center gap-1 opacity-100 md:opacity-0 transition-opacity md:group-hover:opacity-100 focus-within:opacity-100">
577
574
  <Button
@@ -1,8 +1,19 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { Button } from '../../primitives/button'
5
- import { Input } from '../../primitives/input'
4
+ import { DateRangePicker } from '../../primitives/date-range-picker'
5
+ import { IconButton } from '../../primitives/icon-button'
6
+ import {
7
+ SegmentedControl,
8
+ SegmentedControlItem,
9
+ } from '../../primitives/segmented-control'
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from '../../primitives/select'
6
17
  import type { ScheduleRange, ScheduleViewMode } from './types'
7
18
  import { cn } from '@open-mercato/shared/lib/utils'
8
19
  import { useT } from '@open-mercato/shared/lib/i18n/context'
@@ -27,17 +38,19 @@ const VIEW_OPTIONS: Array<{ id: ScheduleViewMode; labelKey: string; fallback: st
27
38
  { id: 'agenda', labelKey: 'schedule.view.agenda', fallback: 'Agenda' },
28
39
  ]
29
40
 
30
- function formatDateInputValue(value: Date): string {
31
- const year = value.getFullYear()
32
- const month = String(value.getMonth() + 1).padStart(2, '0')
33
- const day = String(value.getDate()).padStart(2, '0')
34
- return `${year}-${month}-${day}`
35
- }
36
-
37
- function parseDateInputValue(value: string, fallback: Date): Date {
38
- if (!value) return fallback
39
- const next = new Date(`${value}T00:00:00`)
40
- return Number.isNaN(next.getTime()) ? fallback : next
41
+ function getTimezoneOptions(current?: string): string[] {
42
+ const options = new Set<string>()
43
+ if (current) options.add(current)
44
+ options.add('UTC')
45
+ const resolved = Intl.DateTimeFormat().resolvedOptions().timeZone
46
+ if (resolved) options.add(resolved)
47
+ const intlWithSupportedValues = Intl as typeof Intl & { supportedValuesOf?: (input: 'timeZone') => string[] }
48
+ if (typeof intlWithSupportedValues.supportedValuesOf === 'function') {
49
+ intlWithSupportedValues.supportedValuesOf('timeZone').forEach((timezone) => {
50
+ if (timezone) options.add(timezone)
51
+ })
52
+ }
53
+ return Array.from(options).sort((a, b) => a.localeCompare(b))
41
54
  }
42
55
 
43
56
  export type ScheduleToolbarProps = {
@@ -120,68 +133,76 @@ export function ScheduleToolbar({
120
133
  onRangeChange({ start: nextStart, end: endOfDay(addDays(nextStart, rangeLength - 1)) })
121
134
  }, [onRangeChange, range.start, rangeLength, view])
122
135
 
136
+ const timezoneOptions = React.useMemo(() => getTimezoneOptions(timezone), [timezone])
137
+
123
138
  return (
124
- <div className={cn('flex flex-col gap-3 rounded-xl border bg-card p-4 md:flex-row md:items-center md:justify-between', className)}>
125
- <div className="flex flex-wrap items-center gap-2">
139
+ <div className={cn('flex flex-wrap items-center gap-x-4 gap-y-2 rounded-xl border bg-card p-3', className)}>
140
+ <SegmentedControl
141
+ value={view}
142
+ onValueChange={(value) => {
143
+ const nextView = value as ScheduleViewMode
144
+ if (nextView === view) return
145
+ onViewChange(nextView)
146
+ onRangeChange(deriveRangeForView(new Date(), nextView))
147
+ }}
148
+ aria-label={t('schedule.view.label', 'Schedule view')}
149
+ className="shrink-0"
150
+ >
126
151
  {VIEW_OPTIONS.map((option) => (
127
- <Button
128
- key={option.id}
129
- variant={view === option.id ? 'default' : 'outline'}
130
- size="sm"
131
- onClick={() => {
132
- if (option.id === view) return
133
- onViewChange(option.id)
134
- onRangeChange(deriveRangeForView(new Date(), option.id))
135
- }}
136
- >
152
+ <SegmentedControlItem key={option.id} value={option.id}>
137
153
  {t(option.labelKey, option.fallback)}
138
- </Button>
154
+ </SegmentedControlItem>
139
155
  ))}
140
- </div>
141
- <div className="flex flex-wrap items-center gap-2">
142
- <Button type="button" variant="outline" size="sm" onClick={() => shiftRange('prev')} aria-label={t('schedule.range.prev', 'Previous')}>
156
+ </SegmentedControl>
157
+ <div className="flex shrink-0 items-center gap-1">
158
+ <IconButton
159
+ type="button"
160
+ variant="outline"
161
+ onClick={() => shiftRange('prev')}
162
+ aria-label={t('schedule.range.prev', 'Previous')}
163
+ >
143
164
  <ChevronLeft className="size-4" aria-hidden />
144
- </Button>
145
- <div className="text-sm font-medium text-foreground">{rangeLabel}</div>
146
- <Button type="button" variant="outline" size="sm" onClick={() => shiftRange('next')} aria-label={t('schedule.range.next', 'Next')}>
165
+ </IconButton>
166
+ <div className="min-w-0 whitespace-nowrap px-1 text-sm font-medium text-foreground">{rangeLabel}</div>
167
+ <IconButton
168
+ type="button"
169
+ variant="outline"
170
+ onClick={() => shiftRange('next')}
171
+ aria-label={t('schedule.range.next', 'Next')}
172
+ >
147
173
  <ChevronRight className="size-4" aria-hidden />
148
- </Button>
174
+ </IconButton>
149
175
  </div>
150
- <div className="flex flex-wrap items-center gap-3">
151
- <label className="flex items-center gap-2 text-xs text-muted-foreground">
152
- <span>{t('schedule.range.start', 'Start')}</span>
153
- <Input
154
- type="date"
155
- value={formatDateInputValue(range.start)}
156
- onChange={(event) => {
157
- const nextStart = parseDateInputValue(event.target.value, range.start)
158
- onRangeChange({ start: nextStart, end: range.end })
159
- }}
160
- className="h-8 w-full sm:w-[140px]"
161
- />
162
- </label>
163
- <label className="flex items-center gap-2 text-xs text-muted-foreground">
164
- <span>{t('schedule.range.end', 'End')}</span>
165
- <Input
166
- type="date"
167
- value={formatDateInputValue(range.end)}
168
- onChange={(event) => {
169
- const nextEnd = parseDateInputValue(event.target.value, range.end)
170
- onRangeChange({ start: range.start, end: nextEnd })
171
- }}
172
- className="h-8 w-full sm:w-[140px]"
173
- />
174
- </label>
175
- <label className="flex items-center gap-2 text-xs text-muted-foreground">
176
- <span>{t('schedule.range.timezone', 'Timezone')}</span>
177
- <Input
178
- type="text"
179
- value={timezone ?? ''}
180
- onChange={(event) => onTimezoneChange?.(event.target.value)}
181
- className="h-8 w-full sm:w-[180px]"
182
- placeholder={t('schedule.range.timezone.placeholder', 'UTC')}
183
- />
184
- </label>
176
+ <div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 md:ml-auto">
177
+ <DateRangePicker
178
+ value={range}
179
+ onChange={(next) => {
180
+ if (!next) return
181
+ onRangeChange({ start: startOfDay(next.start), end: endOfDay(next.end) })
182
+ }}
183
+ size="sm"
184
+ showPresets={false}
185
+ numberOfMonths={2}
186
+ aria-label={t('schedule.range.label', 'Date range')}
187
+ />
188
+ {onTimezoneChange ? (
189
+ <Select value={timezone ?? undefined} onValueChange={onTimezoneChange}>
190
+ <SelectTrigger
191
+ size="sm"
192
+ className="w-auto min-w-[10rem]"
193
+ aria-label={t('schedule.range.timezone', 'Timezone')}
194
+ >
195
+ <SelectValue placeholder={t('schedule.range.timezone.placeholder', 'UTC')} />
196
+ </SelectTrigger>
197
+ <SelectContent>
198
+ {timezoneOptions.map((option) => (
199
+ <SelectItem key={option} value={option}>
200
+ {option}
201
+ </SelectItem>
202
+ ))}
203
+ </SelectContent>
204
+ </Select>
205
+ ) : null}
185
206
  </div>
186
207
  </div>
187
208
  )