@open-mercato/core 0.6.6-develop.6253.1.a8c2b49d90 → 0.6.6-develop.6256.1.9fc16aedc4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/customers/components/calendar/TimeGrid.js +3 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/lib/calendar/grid.js +5 -2
- package/dist/modules/customers/lib/calendar/grid.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +20 -9
- package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +43 -6
- package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/route.js +21 -4
- package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
- package/dist/modules/perspectives/api/openapi.js +1 -0
- package/dist/modules/perspectives/api/openapi.js.map +2 -2
- package/dist/modules/perspectives/data/validators.js +2 -0
- package/dist/modules/perspectives/data/validators.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +135 -6
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +5 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/components/calendar/TimeGrid.tsx +3 -2
- package/src/modules/customers/lib/calendar/grid.ts +10 -2
- package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +20 -9
- package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +45 -6
- package/src/modules/perspectives/api/[tableId]/route.ts +23 -1
- package/src/modules/perspectives/api/openapi.ts +1 -1
- package/src/modules/perspectives/data/validators.ts +2 -0
- package/src/modules/perspectives/services/perspectiveService.ts +171 -7
- package/src/modules/query_index/lib/engine.ts +5 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -124,14 +124,15 @@ function TimeGrid({
|
|
|
124
124
|
const scrollRef = React.useRef(null);
|
|
125
125
|
const nowMs = Date.now();
|
|
126
126
|
const today = startOfDay(/* @__PURE__ */ new Date());
|
|
127
|
+
const todayMs = today.getTime();
|
|
127
128
|
const anchorMs = anchor.getTime();
|
|
128
129
|
const [selectedId, setSelectedId] = React.useState(null);
|
|
129
130
|
const [drag, setDrag] = React.useState(null);
|
|
130
131
|
const dayStarts = React.useMemo(() => {
|
|
131
132
|
const rangeStart = getVisibleRange(days === 7 ? "week" : "day", new Date(anchorMs), 0).from;
|
|
132
133
|
const all = Array.from({ length: days }, (_, index) => addDays(rangeStart, index));
|
|
133
|
-
return days === 7 ? applyWeekendVisibility(all, showWeekends) : all;
|
|
134
|
-
}, [days, anchorMs, showWeekends]);
|
|
134
|
+
return days === 7 ? applyWeekendVisibility(all, showWeekends, new Date(todayMs)) : all;
|
|
135
|
+
}, [days, anchorMs, showWeekends, todayMs]);
|
|
135
136
|
const dayColumns = React.useMemo(
|
|
136
137
|
() => dayStarts.map((dayStart) => buildDayColumn(dayStart, items)),
|
|
137
138
|
[dayStarts, items]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/calendar/TimeGrid.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { CalendarDays, ChevronLeft, ChevronRight } from 'lucide-react'\nimport { addDays } from 'date-fns/addDays'\nimport { isSameDay } from 'date-fns/isSameDay'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { packOverlaps } from '../../lib/calendar/layout'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport {\n applyWeekendVisibility,\n buildDragRange,\n DRAG_SNAP_MINUTES,\n isWeekendDay,\n offsetYToMinutes,\n} from '../../lib/calendar/grid'\nimport { EventBlock, formatTimeRange, resolveEventTone } from './EventBlock'\nimport { EventPeekPopover } from './EventPeekPopover'\nimport type { CalendarItem, TimeGridProps } from './types'\n\nconst HOUR_HEIGHT_PX = 120\nconst HOURS_PER_DAY = 24\nconst GRID_BODY_MAX_HEIGHT_PX = 654\nconst INITIAL_SCROLL_HOUR = 8\nconst BLOCK_VERTICAL_GAP_PX = 1\nconst MIN_BLOCK_HEIGHT_PX = 32\nconst PACKED_COLUMN_GAP_PX = 2\n\nconst NON_WORKING_HATCH_BACKGROUND =\n 'repeating-linear-gradient(45deg, transparent 0px, transparent 8px, var(--border) 8px, var(--border) 9px)'\n\ntype PositionedBlock = {\n item: CalendarItem\n top: number\n height: number\n insetInlineStart: string\n width: string\n}\n\ntype DayColumnData = {\n dayStart: Date\n allDayItems: CalendarItem[]\n blocks: PositionedBlock[]\n}\n\ntype DragState = { dayMs: number; startMin: number; endMin: number; moved: boolean }\n\nfunction buildDayColumn(dayStart: Date, items: CalendarItem[]): DayColumnData {\n const dayEnd = addDays(dayStart, 1)\n const dayStartMs = dayStart.getTime()\n const dayEndMs = dayEnd.getTime()\n const allDayItems: CalendarItem[] = []\n const segments: Array<{ original: CalendarItem; start: Date; end: Date }> = []\n\n for (const item of items) {\n const startMs = item.start.getTime()\n const endMs = item.end.getTime()\n if (startMs >= dayEndMs || endMs <= dayStartMs) continue\n if (item.allDay) {\n allDayItems.push(item)\n continue\n }\n segments.push({\n original: item,\n start: startMs < dayStartMs ? dayStart : item.start,\n end: endMs > dayEndMs ? dayEnd : item.end,\n })\n }\n\n allDayItems.sort((first, second) => first.start.getTime() - second.start.getTime())\n\n const clones = segments.map((segment) => ({ ...segment.original, start: segment.start, end: segment.end }))\n const originalByClone = new Map<CalendarItem, CalendarItem>()\n clones.forEach((clone, index) => originalByClone.set(clone, segments[index].original))\n\n const blocks: PositionedBlock[] = packOverlaps(clones).map(({ item: clone, column, columns }) => {\n const startMinutes = (clone.start.getTime() - dayStartMs) / 60000\n const durationMinutes = (clone.end.getTime() - clone.start.getTime()) / 60000\n const rawTop = (startMinutes / 60) * HOUR_HEIGHT_PX\n const rawHeight = (durationMinutes / 60) * HOUR_HEIGHT_PX\n const widthPct = 100 / columns\n return {\n item: originalByClone.get(clone) ?? clone,\n top: rawTop + BLOCK_VERTICAL_GAP_PX,\n height: Math.max(MIN_BLOCK_HEIGHT_PX, rawHeight - BLOCK_VERTICAL_GAP_PX * 2),\n insetInlineStart: `calc(${column * widthPct}% + ${column * PACKED_COLUMN_GAP_PX}px)`,\n width: `calc(${widthPct}% - ${((columns - 1) * PACKED_COLUMN_GAP_PX) / columns}px)`,\n }\n })\n\n return { dayStart, allDayItems, blocks }\n}\n\ntype ConflictBadgeProps = { count: number }\n\nfunction ConflictBadge({ count }: ConflictBadgeProps) {\n const t = useT()\n const label =\n count === 1\n ? t('customers.calendar.grid.conflictCount', '1 conflict')\n : t('customers.calendar.grid.conflictsCount', '{count} conflicts', { count })\n return (\n <span className=\"pointer-events-none absolute left-2 top-1 z-40 inline-flex items-center gap-1 rounded-full bg-status-error-bg px-2 py-0.5 text-overline font-medium uppercase tracking-wide text-status-error-text\">\n <span aria-hidden className=\"size-1.5 rounded-full bg-status-error-icon\" />\n {label}\n </span>\n )\n}\n\ntype AllDayChipProps = {\n item: CalendarItem\n conflicted: boolean\n highlighted: boolean\n selected: boolean\n nowMs: number\n} & Omit<React.ComponentProps<typeof Button>, 'style' | 'children'>\n\nconst AllDayChip = React.forwardRef<HTMLButtonElement, AllDayChipProps>(function AllDayChip(\n { item, conflicted, highlighted, selected, nowMs, className, ...buttonProps },\n ref,\n) {\n const t = useT()\n const tone = resolveEventTone(item, nowMs)\n const title = item.title || t('customers.calendar.grid.untitled', 'Untitled')\n return (\n <Button\n ref={ref}\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title}, ${t('customers.calendar.grid.allDay', 'All day')}`}\n className={cn(\n 'h-auto w-full min-w-0 justify-start rounded-sm px-2 py-0.5 text-start hover:bg-muted/70',\n tone.surfaceClassName,\n conflicted && 'ring-1 ring-status-warning-icon',\n selected && 'shadow-md ring-2 ring-foreground',\n highlighted && 'motion-safe:animate-pulse',\n 'focus-visible:ring-2 focus-visible:ring-ring',\n className,\n )}\n style={tone.style}\n {...buttonProps}\n >\n <span className={cn('truncate text-xs font-medium', tone.titleClassName)}>{title}</span>\n </Button>\n )\n})\n\nAllDayChip.displayName = 'AllDayChip'\n\nexport function TimeGrid({\n days,\n anchor,\n items,\n conflictIds,\n showWeekends,\n showConflicts,\n aiSummaries,\n highlightItemId,\n onItemClick,\n onJoin,\n onNavigate,\n onCreate,\n onCreateRange,\n}: TimeGridProps) {\n const t = useT()\n const locale = useLocale()\n const scrollRef = React.useRef<HTMLDivElement | null>(null)\n const nowMs = Date.now()\n const today = startOfDay(new Date())\n const anchorMs = anchor.getTime()\n const [selectedId, setSelectedId] = React.useState<string | null>(null)\n const [drag, setDrag] = React.useState<DragState | null>(null)\n\n const dayStarts = React.useMemo(() => {\n const rangeStart = getVisibleRange(days === 7 ? 'week' : 'day', new Date(anchorMs), 0).from\n const all = Array.from({ length: days }, (_, index) => addDays(rangeStart, index))\n return days === 7 ? applyWeekendVisibility(all, showWeekends) : all\n }, [days, anchorMs, showWeekends])\n\n const dayColumns = React.useMemo(\n () => dayStarts.map((dayStart) => buildDayColumn(dayStart, items)),\n [dayStarts, items],\n )\n\n const resolveJoinUrl = React.useCallback((location: string | null): string | null => {\n const trimmed = location?.trim() ?? ''\n if (!trimmed) return null\n if (/^https?:\\/\\//i.test(trimmed)) return trimmed\n if (/^www\\./i.test(trimmed)) return `https://${trimmed}`\n return null\n }, [])\n\n const hasAllDayLane = dayColumns.some((column) => column.allDayItems.length > 0)\n\n const formatters = React.useMemo(\n () => ({\n dayNumber: new Intl.DateTimeFormat(locale, { day: '2-digit' }),\n weekdayShort: new Intl.DateTimeFormat(locale, { weekday: 'short' }),\n weekdayLong: new Intl.DateTimeFormat(locale, { weekday: 'long' }),\n }),\n [locale],\n )\n\n const hourLabels = React.useMemo(() => {\n const formatter = new Intl.DateTimeFormat(locale, { hour: 'numeric' })\n return Array.from({ length: HOURS_PER_DAY }, (_, hour) => formatter.format(new Date(2024, 0, 1, hour)))\n }, [locale])\n\n React.useEffect(() => {\n const node = scrollRef.current\n if (!node) return\n node.scrollTop = INITIAL_SCROLL_HOUR * HOUR_HEIGHT_PX\n }, [])\n\n React.useEffect(() => {\n if (!highlightItemId) return\n const node = scrollRef.current\n if (!node) return\n const target = items.find((item) => item.id === highlightItemId && !item.allDay)\n if (!target) return\n const minutes = (target.start.getTime() - startOfDay(target.start).getTime()) / 60000\n const top = (minutes / 60) * HOUR_HEIGHT_PX\n const reduceMotion =\n typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches\n node.scrollTo({ top: Math.max(0, top - HOUR_HEIGHT_PX), behavior: reduceMotion ? 'auto' : 'smooth' })\n }, [highlightItemId, items])\n\n const nonWorkingLabel = t('customers.calendar.grid.nonWorking', 'Non-working day')\n const previousLabel =\n days === 7\n ? t('customers.calendar.previousWeek', 'Previous week')\n : t('customers.calendar.grid.previousDay', 'Previous day')\n const nextLabel =\n days === 7 ? t('customers.calendar.nextWeek', 'Next week') : t('customers.calendar.grid.nextDay', 'Next day')\n\n const headerLabelFor = (dayStart: Date): string => {\n const dayNumber = formatters.dayNumber.format(dayStart)\n if (days === 1) return `${formatters.weekdayLong.format(dayStart).toUpperCase()} \u00B7 ${dayNumber}`\n return `${dayNumber} ${formatters.weekdayShort.format(dayStart).toUpperCase()}`\n }\n\n const canCreateRange = Boolean(onCreateRange)\n\n const beginDrag = (event: React.PointerEvent<HTMLDivElement>, dayStart: Date) => {\n if (!canCreateRange || event.button !== 0) return\n const layer = event.currentTarget\n const rect = layer.getBoundingClientRect()\n const minute = offsetYToMinutes(event.clientY - rect.top, HOUR_HEIGHT_PX)\n try {\n layer.setPointerCapture(event.pointerId)\n } catch {\n // Pointer capture is best-effort; ignore environments that reject it.\n }\n setSelectedId(null)\n setDrag({ dayMs: dayStart.getTime(), startMin: minute, endMin: minute, moved: false })\n }\n\n const moveDrag = (event: React.PointerEvent<HTMLDivElement>) => {\n // Read the layer rect synchronously: `event.currentTarget` is only valid during\n // event dispatch, but the setDrag updater below runs later during re-render.\n const rect = event.currentTarget.getBoundingClientRect()\n const minute = offsetYToMinutes(event.clientY - rect.top, HOUR_HEIGHT_PX)\n setDrag((previous) => {\n if (!previous) return previous\n return {\n ...previous,\n endMin: minute,\n moved: previous.moved || Math.abs(minute - previous.startMin) >= DRAG_SNAP_MINUTES,\n }\n })\n }\n\n const endDrag = (dayStart: Date) => {\n setDrag((previous) => {\n if (previous && previous.moved && onCreateRange) {\n const range = buildDragRange(dayStart, previous.startMin, previous.endMin)\n onCreateRange(range.start, range.end)\n }\n return null\n })\n }\n\n return (\n <div className=\"relative flex flex-col overflow-hidden rounded-lg border border-border bg-card\">\n <div\n ref={scrollRef}\n className=\"overflow-auto overscroll-contain\"\n style={{ maxHeight: GRID_BODY_MAX_HEIGHT_PX }}\n >\n <div className=\"sticky top-0 z-30 min-w-full bg-card max-md:w-max\">\n <div className=\"flex border-b border-border\">\n <div className=\"sticky start-0 z-10 flex w-14 shrink-0 bg-card md:w-26\">\n <span className=\"flex flex-1 items-center justify-center border-e border-border\">\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" aria-label={previousLabel} onClick={() => onNavigate(-days)}>\n <ChevronLeft aria-hidden />\n </IconButton>\n </span>\n <span className=\"flex flex-1 items-center justify-center border-e border-border\">\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" aria-label={nextLabel} onClick={() => onNavigate(days)}>\n <ChevronRight aria-hidden />\n </IconButton>\n </span>\n </div>\n {dayColumns.map(({ dayStart }) => {\n const isToday = isSameDay(dayStart, today)\n return (\n <div\n key={dayStart.getTime()}\n className={cn(\n 'flex min-w-0 flex-1 items-center justify-center border-e border-border bg-muted px-1 py-2 last:border-e-0',\n days === 7 && 'min-w-[120px] max-md:max-w-[120px] md:min-w-0',\n )}\n >\n <span\n className={cn(\n 'truncate text-xs uppercase tracking-wide',\n isToday ? 'font-semibold text-foreground' : 'font-medium text-muted-foreground',\n )}\n >\n {headerLabelFor(dayStart)}\n </span>\n </div>\n )\n })}\n </div>\n {hasAllDayLane ? (\n <div className=\"flex border-b border-border\">\n <div className=\"sticky start-0 z-10 flex w-14 shrink-0 items-center justify-center border-e border-border bg-card py-1 md:w-26\">\n <span className=\"truncate text-overline uppercase tracking-wide text-muted-foreground\">\n {t('customers.calendar.grid.allDay', 'All day')}\n </span>\n </div>\n {dayColumns.map(({ dayStart, allDayItems }) => (\n <div\n key={dayStart.getTime()}\n className={cn(\n 'min-w-0 flex-1 space-y-1 border-e border-border p-1 last:border-e-0',\n days === 7 && 'min-w-[120px] max-md:max-w-[120px] md:min-w-0',\n )}\n >\n {allDayItems.map((item) => (\n <EventPeekPopover\n key={item.id}\n item={item}\n open={selectedId === item.id}\n joinUrl={resolveJoinUrl(item.location)}\n aiSummaries={aiSummaries}\n onOpenChange={(open) => setSelectedId(open ? item.id : null)}\n onJoin={onJoin}\n onEdit={onItemClick}\n >\n <AllDayChip\n item={item}\n conflicted={showConflicts && conflictIds.has(item.id)}\n highlighted={highlightItemId === item.id}\n selected={selectedId === item.id}\n nowMs={nowMs}\n />\n </EventPeekPopover>\n ))}\n </div>\n ))}\n </div>\n ) : null}\n </div>\n <div className=\"flex min-w-full max-md:w-max\" style={{ height: HOURS_PER_DAY * HOUR_HEIGHT_PX }}>\n <div className=\"sticky start-0 z-10 w-14 shrink-0 border-e border-border bg-card md:w-26\">\n <div className=\"relative h-full\">\n {hourLabels.map((label, hour) =>\n hour === 0 ? null : (\n <span\n key={hour}\n className=\"absolute w-full -translate-y-1/2 px-1 text-center text-xs font-medium text-muted-foreground md:px-3 md:text-sm\"\n style={{ top: hour * HOUR_HEIGHT_PX }}\n >\n {label}\n </span>\n ),\n )}\n </div>\n </div>\n {dayColumns.map(({ dayStart, blocks }) => {\n const nonWorking = isWeekendDay(dayStart)\n const conflictCount = showConflicts\n ? blocks.filter((block) => conflictIds.has(block.item.id)).length\n : 0\n const dragActive = drag && drag.moved && drag.dayMs === dayStart.getTime()\n const dragRange = dragActive ? buildDragRange(dayStart, drag.startMin, drag.endMin) : null\n const dragStartMinutes = dragRange\n ? (dragRange.start.getTime() - startOfDay(dragRange.start).getTime()) / 60000\n : 0\n const dragDurationMinutes = dragRange\n ? (dragRange.end.getTime() - dragRange.start.getTime()) / 60000\n : 0\n return (\n <div\n key={dayStart.getTime()}\n className={cn(\n 'relative min-w-0 flex-1 border-e border-border last:border-e-0',\n days === 7 && 'min-w-[120px] max-md:max-w-[120px] md:min-w-0',\n )}\n title={nonWorking ? nonWorkingLabel : undefined}\n >\n {nonWorking ? <span className=\"sr-only\">{nonWorkingLabel}</span> : null}\n <div aria-hidden className=\"absolute inset-0\">\n {Array.from({ length: HOURS_PER_DAY }, (_, hour) => (\n <div key={hour} className=\"relative h-30 border-b border-border\">\n <span className=\"absolute inset-x-0 top-1/2 h-px bg-border/50\" />\n </div>\n ))}\n </div>\n {nonWorking ? (\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-0\"\n style={{ backgroundImage: NON_WORKING_HATCH_BACKGROUND }}\n />\n ) : null}\n {canCreateRange ? (\n <div\n className=\"absolute inset-0 cursor-cell\"\n onPointerDown={(event) => beginDrag(event, dayStart)}\n onPointerMove={moveDrag}\n onPointerUp={() => endDrag(dayStart)}\n onPointerCancel={() => setDrag(null)}\n aria-hidden\n />\n ) : null}\n {dragRange ? (\n <div\n aria-hidden\n className=\"pointer-events-none absolute z-30 flex flex-col gap-0.5 overflow-hidden rounded-md border-2 border-dashed border-foreground bg-accent-indigo/10 px-2 pt-1.5\"\n style={{\n top: (dragStartMinutes / 60) * HOUR_HEIGHT_PX,\n height: Math.max(MIN_BLOCK_HEIGHT_PX, (dragDurationMinutes / 60) * HOUR_HEIGHT_PX),\n insetInlineStart: 8,\n insetInlineEnd: 8,\n }}\n >\n <span className=\"text-overline font-semibold text-foreground\">\n {t('customers.calendar.actions.newEvent', 'New event')}\n </span>\n <span className=\"text-overline text-muted-foreground\">\n {formatTimeRange(locale, dragRange.start, dragRange.end)}\n </span>\n </div>\n ) : null}\n {conflictCount > 0 ? <ConflictBadge count={conflictCount} /> : null}\n {/* Click-through container so empty space reaches the drag-to-create layer\n below; each block re-enables pointer events for its own click/peek. */}\n <div className=\"pointer-events-none absolute inset-y-0\" style={{ insetInlineStart: 8, insetInlineEnd: 8 }}>\n {blocks.map((block) => (\n <EventPeekPopover\n key={`${block.item.id}-${block.top}`}\n item={block.item}\n open={selectedId === block.item.id}\n joinUrl={resolveJoinUrl(block.item.location)}\n aiSummaries={aiSummaries}\n onOpenChange={(open) => setSelectedId(open ? block.item.id : null)}\n onJoin={onJoin}\n onEdit={onItemClick}\n >\n <EventBlock\n item={block.item}\n top={block.top}\n height={block.height}\n insetInlineStart={block.insetInlineStart}\n width={block.width}\n conflicted={showConflicts && conflictIds.has(block.item.id)}\n highlighted={highlightItemId === block.item.id}\n selected={selectedId === block.item.id}\n nowMs={nowMs}\n />\n </EventPeekPopover>\n ))}\n </div>\n </div>\n )\n })}\n </div>\n </div>\n {items.length === 0 ? (\n <div className=\"pointer-events-none absolute inset-0 z-40 flex items-center justify-center p-6\">\n <EmptyState\n className=\"pointer-events-auto bg-card/95 shadow-sm\"\n icon={<CalendarDays className=\"h-6 w-6\" aria-hidden />}\n title={\n days === 7\n ? t('customers.calendar.empty.week', 'Nothing scheduled this week')\n : t('customers.calendar.empty.day', 'Nothing scheduled this day')\n }\n description={t('customers.calendar.empty.description', 'Plan a meeting, event or task to fill your week.')}\n actions={\n onCreate ? (\n <Button type=\"button\" size=\"sm\" onClick={onCreate}>\n {t('customers.calendar.actions.newEvent', 'New event')}\n </Button>\n ) : undefined\n }\n />\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { CalendarDays, ChevronLeft, ChevronRight } from 'lucide-react'\nimport { addDays } from 'date-fns/addDays'\nimport { isSameDay } from 'date-fns/isSameDay'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { packOverlaps } from '../../lib/calendar/layout'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport {\n applyWeekendVisibility,\n buildDragRange,\n DRAG_SNAP_MINUTES,\n isWeekendDay,\n offsetYToMinutes,\n} from '../../lib/calendar/grid'\nimport { EventBlock, formatTimeRange, resolveEventTone } from './EventBlock'\nimport { EventPeekPopover } from './EventPeekPopover'\nimport type { CalendarItem, TimeGridProps } from './types'\n\nconst HOUR_HEIGHT_PX = 120\nconst HOURS_PER_DAY = 24\nconst GRID_BODY_MAX_HEIGHT_PX = 654\nconst INITIAL_SCROLL_HOUR = 8\nconst BLOCK_VERTICAL_GAP_PX = 1\nconst MIN_BLOCK_HEIGHT_PX = 32\nconst PACKED_COLUMN_GAP_PX = 2\n\nconst NON_WORKING_HATCH_BACKGROUND =\n 'repeating-linear-gradient(45deg, transparent 0px, transparent 8px, var(--border) 8px, var(--border) 9px)'\n\ntype PositionedBlock = {\n item: CalendarItem\n top: number\n height: number\n insetInlineStart: string\n width: string\n}\n\ntype DayColumnData = {\n dayStart: Date\n allDayItems: CalendarItem[]\n blocks: PositionedBlock[]\n}\n\ntype DragState = { dayMs: number; startMin: number; endMin: number; moved: boolean }\n\nfunction buildDayColumn(dayStart: Date, items: CalendarItem[]): DayColumnData {\n const dayEnd = addDays(dayStart, 1)\n const dayStartMs = dayStart.getTime()\n const dayEndMs = dayEnd.getTime()\n const allDayItems: CalendarItem[] = []\n const segments: Array<{ original: CalendarItem; start: Date; end: Date }> = []\n\n for (const item of items) {\n const startMs = item.start.getTime()\n const endMs = item.end.getTime()\n if (startMs >= dayEndMs || endMs <= dayStartMs) continue\n if (item.allDay) {\n allDayItems.push(item)\n continue\n }\n segments.push({\n original: item,\n start: startMs < dayStartMs ? dayStart : item.start,\n end: endMs > dayEndMs ? dayEnd : item.end,\n })\n }\n\n allDayItems.sort((first, second) => first.start.getTime() - second.start.getTime())\n\n const clones = segments.map((segment) => ({ ...segment.original, start: segment.start, end: segment.end }))\n const originalByClone = new Map<CalendarItem, CalendarItem>()\n clones.forEach((clone, index) => originalByClone.set(clone, segments[index].original))\n\n const blocks: PositionedBlock[] = packOverlaps(clones).map(({ item: clone, column, columns }) => {\n const startMinutes = (clone.start.getTime() - dayStartMs) / 60000\n const durationMinutes = (clone.end.getTime() - clone.start.getTime()) / 60000\n const rawTop = (startMinutes / 60) * HOUR_HEIGHT_PX\n const rawHeight = (durationMinutes / 60) * HOUR_HEIGHT_PX\n const widthPct = 100 / columns\n return {\n item: originalByClone.get(clone) ?? clone,\n top: rawTop + BLOCK_VERTICAL_GAP_PX,\n height: Math.max(MIN_BLOCK_HEIGHT_PX, rawHeight - BLOCK_VERTICAL_GAP_PX * 2),\n insetInlineStart: `calc(${column * widthPct}% + ${column * PACKED_COLUMN_GAP_PX}px)`,\n width: `calc(${widthPct}% - ${((columns - 1) * PACKED_COLUMN_GAP_PX) / columns}px)`,\n }\n })\n\n return { dayStart, allDayItems, blocks }\n}\n\ntype ConflictBadgeProps = { count: number }\n\nfunction ConflictBadge({ count }: ConflictBadgeProps) {\n const t = useT()\n const label =\n count === 1\n ? t('customers.calendar.grid.conflictCount', '1 conflict')\n : t('customers.calendar.grid.conflictsCount', '{count} conflicts', { count })\n return (\n <span className=\"pointer-events-none absolute left-2 top-1 z-40 inline-flex items-center gap-1 rounded-full bg-status-error-bg px-2 py-0.5 text-overline font-medium uppercase tracking-wide text-status-error-text\">\n <span aria-hidden className=\"size-1.5 rounded-full bg-status-error-icon\" />\n {label}\n </span>\n )\n}\n\ntype AllDayChipProps = {\n item: CalendarItem\n conflicted: boolean\n highlighted: boolean\n selected: boolean\n nowMs: number\n} & Omit<React.ComponentProps<typeof Button>, 'style' | 'children'>\n\nconst AllDayChip = React.forwardRef<HTMLButtonElement, AllDayChipProps>(function AllDayChip(\n { item, conflicted, highlighted, selected, nowMs, className, ...buttonProps },\n ref,\n) {\n const t = useT()\n const tone = resolveEventTone(item, nowMs)\n const title = item.title || t('customers.calendar.grid.untitled', 'Untitled')\n return (\n <Button\n ref={ref}\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title}, ${t('customers.calendar.grid.allDay', 'All day')}`}\n className={cn(\n 'h-auto w-full min-w-0 justify-start rounded-sm px-2 py-0.5 text-start hover:bg-muted/70',\n tone.surfaceClassName,\n conflicted && 'ring-1 ring-status-warning-icon',\n selected && 'shadow-md ring-2 ring-foreground',\n highlighted && 'motion-safe:animate-pulse',\n 'focus-visible:ring-2 focus-visible:ring-ring',\n className,\n )}\n style={tone.style}\n {...buttonProps}\n >\n <span className={cn('truncate text-xs font-medium', tone.titleClassName)}>{title}</span>\n </Button>\n )\n})\n\nAllDayChip.displayName = 'AllDayChip'\n\nexport function TimeGrid({\n days,\n anchor,\n items,\n conflictIds,\n showWeekends,\n showConflicts,\n aiSummaries,\n highlightItemId,\n onItemClick,\n onJoin,\n onNavigate,\n onCreate,\n onCreateRange,\n}: TimeGridProps) {\n const t = useT()\n const locale = useLocale()\n const scrollRef = React.useRef<HTMLDivElement | null>(null)\n const nowMs = Date.now()\n const today = startOfDay(new Date())\n const todayMs = today.getTime()\n const anchorMs = anchor.getTime()\n const [selectedId, setSelectedId] = React.useState<string | null>(null)\n const [drag, setDrag] = React.useState<DragState | null>(null)\n\n const dayStarts = React.useMemo(() => {\n const rangeStart = getVisibleRange(days === 7 ? 'week' : 'day', new Date(anchorMs), 0).from\n const all = Array.from({ length: days }, (_, index) => addDays(rangeStart, index))\n return days === 7 ? applyWeekendVisibility(all, showWeekends, new Date(todayMs)) : all\n }, [days, anchorMs, showWeekends, todayMs])\n\n const dayColumns = React.useMemo(\n () => dayStarts.map((dayStart) => buildDayColumn(dayStart, items)),\n [dayStarts, items],\n )\n\n const resolveJoinUrl = React.useCallback((location: string | null): string | null => {\n const trimmed = location?.trim() ?? ''\n if (!trimmed) return null\n if (/^https?:\\/\\//i.test(trimmed)) return trimmed\n if (/^www\\./i.test(trimmed)) return `https://${trimmed}`\n return null\n }, [])\n\n const hasAllDayLane = dayColumns.some((column) => column.allDayItems.length > 0)\n\n const formatters = React.useMemo(\n () => ({\n dayNumber: new Intl.DateTimeFormat(locale, { day: '2-digit' }),\n weekdayShort: new Intl.DateTimeFormat(locale, { weekday: 'short' }),\n weekdayLong: new Intl.DateTimeFormat(locale, { weekday: 'long' }),\n }),\n [locale],\n )\n\n const hourLabels = React.useMemo(() => {\n const formatter = new Intl.DateTimeFormat(locale, { hour: 'numeric' })\n return Array.from({ length: HOURS_PER_DAY }, (_, hour) => formatter.format(new Date(2024, 0, 1, hour)))\n }, [locale])\n\n React.useEffect(() => {\n const node = scrollRef.current\n if (!node) return\n node.scrollTop = INITIAL_SCROLL_HOUR * HOUR_HEIGHT_PX\n }, [])\n\n React.useEffect(() => {\n if (!highlightItemId) return\n const node = scrollRef.current\n if (!node) return\n const target = items.find((item) => item.id === highlightItemId && !item.allDay)\n if (!target) return\n const minutes = (target.start.getTime() - startOfDay(target.start).getTime()) / 60000\n const top = (minutes / 60) * HOUR_HEIGHT_PX\n const reduceMotion =\n typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches\n node.scrollTo({ top: Math.max(0, top - HOUR_HEIGHT_PX), behavior: reduceMotion ? 'auto' : 'smooth' })\n }, [highlightItemId, items])\n\n const nonWorkingLabel = t('customers.calendar.grid.nonWorking', 'Non-working day')\n const previousLabel =\n days === 7\n ? t('customers.calendar.previousWeek', 'Previous week')\n : t('customers.calendar.grid.previousDay', 'Previous day')\n const nextLabel =\n days === 7 ? t('customers.calendar.nextWeek', 'Next week') : t('customers.calendar.grid.nextDay', 'Next day')\n\n const headerLabelFor = (dayStart: Date): string => {\n const dayNumber = formatters.dayNumber.format(dayStart)\n if (days === 1) return `${formatters.weekdayLong.format(dayStart).toUpperCase()} \u00B7 ${dayNumber}`\n return `${dayNumber} ${formatters.weekdayShort.format(dayStart).toUpperCase()}`\n }\n\n const canCreateRange = Boolean(onCreateRange)\n\n const beginDrag = (event: React.PointerEvent<HTMLDivElement>, dayStart: Date) => {\n if (!canCreateRange || event.button !== 0) return\n const layer = event.currentTarget\n const rect = layer.getBoundingClientRect()\n const minute = offsetYToMinutes(event.clientY - rect.top, HOUR_HEIGHT_PX)\n try {\n layer.setPointerCapture(event.pointerId)\n } catch {\n // Pointer capture is best-effort; ignore environments that reject it.\n }\n setSelectedId(null)\n setDrag({ dayMs: dayStart.getTime(), startMin: minute, endMin: minute, moved: false })\n }\n\n const moveDrag = (event: React.PointerEvent<HTMLDivElement>) => {\n // Read the layer rect synchronously: `event.currentTarget` is only valid during\n // event dispatch, but the setDrag updater below runs later during re-render.\n const rect = event.currentTarget.getBoundingClientRect()\n const minute = offsetYToMinutes(event.clientY - rect.top, HOUR_HEIGHT_PX)\n setDrag((previous) => {\n if (!previous) return previous\n return {\n ...previous,\n endMin: minute,\n moved: previous.moved || Math.abs(minute - previous.startMin) >= DRAG_SNAP_MINUTES,\n }\n })\n }\n\n const endDrag = (dayStart: Date) => {\n setDrag((previous) => {\n if (previous && previous.moved && onCreateRange) {\n const range = buildDragRange(dayStart, previous.startMin, previous.endMin)\n onCreateRange(range.start, range.end)\n }\n return null\n })\n }\n\n return (\n <div className=\"relative flex flex-col overflow-hidden rounded-lg border border-border bg-card\">\n <div\n ref={scrollRef}\n className=\"overflow-auto overscroll-contain\"\n style={{ maxHeight: GRID_BODY_MAX_HEIGHT_PX }}\n >\n <div className=\"sticky top-0 z-30 min-w-full bg-card max-md:w-max\">\n <div className=\"flex border-b border-border\">\n <div className=\"sticky start-0 z-10 flex w-14 shrink-0 bg-card md:w-26\">\n <span className=\"flex flex-1 items-center justify-center border-e border-border\">\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" aria-label={previousLabel} onClick={() => onNavigate(-days)}>\n <ChevronLeft aria-hidden />\n </IconButton>\n </span>\n <span className=\"flex flex-1 items-center justify-center border-e border-border\">\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" aria-label={nextLabel} onClick={() => onNavigate(days)}>\n <ChevronRight aria-hidden />\n </IconButton>\n </span>\n </div>\n {dayColumns.map(({ dayStart }) => {\n const isToday = isSameDay(dayStart, today)\n return (\n <div\n key={dayStart.getTime()}\n className={cn(\n 'flex min-w-0 flex-1 items-center justify-center border-e border-border bg-muted px-1 py-2 last:border-e-0',\n days === 7 && 'min-w-[120px] max-md:max-w-[120px] md:min-w-0',\n )}\n >\n <span\n className={cn(\n 'truncate text-xs uppercase tracking-wide',\n isToday ? 'font-semibold text-foreground' : 'font-medium text-muted-foreground',\n )}\n >\n {headerLabelFor(dayStart)}\n </span>\n </div>\n )\n })}\n </div>\n {hasAllDayLane ? (\n <div className=\"flex border-b border-border\">\n <div className=\"sticky start-0 z-10 flex w-14 shrink-0 items-center justify-center border-e border-border bg-card py-1 md:w-26\">\n <span className=\"truncate text-overline uppercase tracking-wide text-muted-foreground\">\n {t('customers.calendar.grid.allDay', 'All day')}\n </span>\n </div>\n {dayColumns.map(({ dayStart, allDayItems }) => (\n <div\n key={dayStart.getTime()}\n className={cn(\n 'min-w-0 flex-1 space-y-1 border-e border-border p-1 last:border-e-0',\n days === 7 && 'min-w-[120px] max-md:max-w-[120px] md:min-w-0',\n )}\n >\n {allDayItems.map((item) => (\n <EventPeekPopover\n key={item.id}\n item={item}\n open={selectedId === item.id}\n joinUrl={resolveJoinUrl(item.location)}\n aiSummaries={aiSummaries}\n onOpenChange={(open) => setSelectedId(open ? item.id : null)}\n onJoin={onJoin}\n onEdit={onItemClick}\n >\n <AllDayChip\n item={item}\n conflicted={showConflicts && conflictIds.has(item.id)}\n highlighted={highlightItemId === item.id}\n selected={selectedId === item.id}\n nowMs={nowMs}\n />\n </EventPeekPopover>\n ))}\n </div>\n ))}\n </div>\n ) : null}\n </div>\n <div className=\"flex min-w-full max-md:w-max\" style={{ height: HOURS_PER_DAY * HOUR_HEIGHT_PX }}>\n <div className=\"sticky start-0 z-10 w-14 shrink-0 border-e border-border bg-card md:w-26\">\n <div className=\"relative h-full\">\n {hourLabels.map((label, hour) =>\n hour === 0 ? null : (\n <span\n key={hour}\n className=\"absolute w-full -translate-y-1/2 px-1 text-center text-xs font-medium text-muted-foreground md:px-3 md:text-sm\"\n style={{ top: hour * HOUR_HEIGHT_PX }}\n >\n {label}\n </span>\n ),\n )}\n </div>\n </div>\n {dayColumns.map(({ dayStart, blocks }) => {\n const nonWorking = isWeekendDay(dayStart)\n const conflictCount = showConflicts\n ? blocks.filter((block) => conflictIds.has(block.item.id)).length\n : 0\n const dragActive = drag && drag.moved && drag.dayMs === dayStart.getTime()\n const dragRange = dragActive ? buildDragRange(dayStart, drag.startMin, drag.endMin) : null\n const dragStartMinutes = dragRange\n ? (dragRange.start.getTime() - startOfDay(dragRange.start).getTime()) / 60000\n : 0\n const dragDurationMinutes = dragRange\n ? (dragRange.end.getTime() - dragRange.start.getTime()) / 60000\n : 0\n return (\n <div\n key={dayStart.getTime()}\n className={cn(\n 'relative min-w-0 flex-1 border-e border-border last:border-e-0',\n days === 7 && 'min-w-[120px] max-md:max-w-[120px] md:min-w-0',\n )}\n title={nonWorking ? nonWorkingLabel : undefined}\n >\n {nonWorking ? <span className=\"sr-only\">{nonWorkingLabel}</span> : null}\n <div aria-hidden className=\"absolute inset-0\">\n {Array.from({ length: HOURS_PER_DAY }, (_, hour) => (\n <div key={hour} className=\"relative h-30 border-b border-border\">\n <span className=\"absolute inset-x-0 top-1/2 h-px bg-border/50\" />\n </div>\n ))}\n </div>\n {nonWorking ? (\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-0\"\n style={{ backgroundImage: NON_WORKING_HATCH_BACKGROUND }}\n />\n ) : null}\n {canCreateRange ? (\n <div\n className=\"absolute inset-0 cursor-cell\"\n onPointerDown={(event) => beginDrag(event, dayStart)}\n onPointerMove={moveDrag}\n onPointerUp={() => endDrag(dayStart)}\n onPointerCancel={() => setDrag(null)}\n aria-hidden\n />\n ) : null}\n {dragRange ? (\n <div\n aria-hidden\n className=\"pointer-events-none absolute z-30 flex flex-col gap-0.5 overflow-hidden rounded-md border-2 border-dashed border-foreground bg-accent-indigo/10 px-2 pt-1.5\"\n style={{\n top: (dragStartMinutes / 60) * HOUR_HEIGHT_PX,\n height: Math.max(MIN_BLOCK_HEIGHT_PX, (dragDurationMinutes / 60) * HOUR_HEIGHT_PX),\n insetInlineStart: 8,\n insetInlineEnd: 8,\n }}\n >\n <span className=\"text-overline font-semibold text-foreground\">\n {t('customers.calendar.actions.newEvent', 'New event')}\n </span>\n <span className=\"text-overline text-muted-foreground\">\n {formatTimeRange(locale, dragRange.start, dragRange.end)}\n </span>\n </div>\n ) : null}\n {conflictCount > 0 ? <ConflictBadge count={conflictCount} /> : null}\n {/* Click-through container so empty space reaches the drag-to-create layer\n below; each block re-enables pointer events for its own click/peek. */}\n <div className=\"pointer-events-none absolute inset-y-0\" style={{ insetInlineStart: 8, insetInlineEnd: 8 }}>\n {blocks.map((block) => (\n <EventPeekPopover\n key={`${block.item.id}-${block.top}`}\n item={block.item}\n open={selectedId === block.item.id}\n joinUrl={resolveJoinUrl(block.item.location)}\n aiSummaries={aiSummaries}\n onOpenChange={(open) => setSelectedId(open ? block.item.id : null)}\n onJoin={onJoin}\n onEdit={onItemClick}\n >\n <EventBlock\n item={block.item}\n top={block.top}\n height={block.height}\n insetInlineStart={block.insetInlineStart}\n width={block.width}\n conflicted={showConflicts && conflictIds.has(block.item.id)}\n highlighted={highlightItemId === block.item.id}\n selected={selectedId === block.item.id}\n nowMs={nowMs}\n />\n </EventPeekPopover>\n ))}\n </div>\n </div>\n )\n })}\n </div>\n </div>\n {items.length === 0 ? (\n <div className=\"pointer-events-none absolute inset-0 z-40 flex items-center justify-center p-6\">\n <EmptyState\n className=\"pointer-events-auto bg-card/95 shadow-sm\"\n icon={<CalendarDays className=\"h-6 w-6\" aria-hidden />}\n title={\n days === 7\n ? t('customers.calendar.empty.week', 'Nothing scheduled this week')\n : t('customers.calendar.empty.day', 'Nothing scheduled this day')\n }\n description={t('customers.calendar.empty.description', 'Plan a meeting, event or task to fill your week.')}\n actions={\n onCreate ? (\n <Button type=\"button\" size=\"sm\" onClick={onCreate}>\n {t('customers.calendar.actions.newEvent', 'New event')}\n </Button>\n ) : undefined\n }\n />\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["AllDayChip"]
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSameDay } from "date-fns/isSameDay";
|
|
1
2
|
const DRAG_SNAP_MINUTES = 15;
|
|
2
3
|
const MIN_DRAG_DURATION_MINUTES = 30;
|
|
3
4
|
const MINUTES_PER_DAY = 24 * 60;
|
|
@@ -5,9 +6,11 @@ function isWeekendDay(date) {
|
|
|
5
6
|
const weekday = date.getDay();
|
|
6
7
|
return weekday === 0 || weekday === 6;
|
|
7
8
|
}
|
|
8
|
-
function applyWeekendVisibility(days, showWeekends) {
|
|
9
|
+
function applyWeekendVisibility(days, showWeekends, keepWeekendDate) {
|
|
9
10
|
if (showWeekends) return days;
|
|
10
|
-
const workingDays = days.filter(
|
|
11
|
+
const workingDays = days.filter(
|
|
12
|
+
(day) => !isWeekendDay(day) || keepWeekendDate != null && isSameDay(day, keepWeekendDate)
|
|
13
|
+
);
|
|
11
14
|
return workingDays.length > 0 ? workingDays : days;
|
|
12
15
|
}
|
|
13
16
|
function clampMinutes(value) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/lib/calendar/grid.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { isSameDay } from 'date-fns/isSameDay'\n\nexport const DRAG_SNAP_MINUTES = 15\nexport const MIN_DRAG_DURATION_MINUTES = 30\nconst MINUTES_PER_DAY = 24 * 60\n\nexport function isWeekendDay(date: Date): boolean {\n const weekday = date.getDay()\n return weekday === 0 || weekday === 6\n}\n\nexport function applyWeekendVisibility(\n days: Date[],\n showWeekends: boolean,\n keepWeekendDate?: Date | null,\n): Date[] {\n if (showWeekends) return days\n const workingDays = days.filter(\n (day) => !isWeekendDay(day) || (keepWeekendDate != null && isSameDay(day, keepWeekendDate)),\n )\n return workingDays.length > 0 ? workingDays : days\n}\n\nfunction clampMinutes(value: number): number {\n if (value < 0) return 0\n if (value > MINUTES_PER_DAY) return MINUTES_PER_DAY\n return value\n}\n\nexport function offsetYToMinutes(\n offsetY: number,\n hourHeightPx: number,\n snapMinutes: number = DRAG_SNAP_MINUTES,\n): number {\n if (hourHeightPx <= 0) return 0\n const rawMinutes = (offsetY / hourHeightPx) * 60\n const snapped = Math.round(rawMinutes / snapMinutes) * snapMinutes\n return clampMinutes(snapped)\n}\n\nexport type DragRange = { start: Date; end: Date }\n\nexport function buildDragRange(dayStart: Date, startMinutes: number, endMinutes: number): DragRange {\n let lower = clampMinutes(Math.min(startMinutes, endMinutes))\n let upper = clampMinutes(Math.max(startMinutes, endMinutes))\n if (upper - lower < MIN_DRAG_DURATION_MINUTES) upper = lower + MIN_DRAG_DURATION_MINUTES\n if (upper > MINUTES_PER_DAY) {\n upper = MINUTES_PER_DAY\n lower = Math.max(0, upper - MIN_DRAG_DURATION_MINUTES)\n }\n const start = new Date(dayStart)\n start.setHours(0, 0, 0, 0)\n start.setMinutes(lower)\n const end = new Date(dayStart)\n end.setHours(0, 0, 0, 0)\n end.setMinutes(upper)\n return { start, end }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,oBAAoB;AAC1B,MAAM,4BAA4B;AACzC,MAAM,kBAAkB,KAAK;AAEtB,SAAS,aAAa,MAAqB;AAChD,QAAM,UAAU,KAAK,OAAO;AAC5B,SAAO,YAAY,KAAK,YAAY;AACtC;AAEO,SAAS,uBACd,MACA,cACA,iBACQ;AACR,MAAI,aAAc,QAAO;AACzB,QAAM,cAAc,KAAK;AAAA,IACvB,CAAC,QAAQ,CAAC,aAAa,GAAG,KAAM,mBAAmB,QAAQ,UAAU,KAAK,eAAe;AAAA,EAC3F;AACA,SAAO,YAAY,SAAS,IAAI,cAAc;AAChD;AAEA,SAAS,aAAa,OAAuB;AAC3C,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,QAAQ,gBAAiB,QAAO;AACpC,SAAO;AACT;AAEO,SAAS,iBACd,SACA,cACA,cAAsB,mBACd;AACR,MAAI,gBAAgB,EAAG,QAAO;AAC9B,QAAM,aAAc,UAAU,eAAgB;AAC9C,QAAM,UAAU,KAAK,MAAM,aAAa,WAAW,IAAI;AACvD,SAAO,aAAa,OAAO;AAC7B;AAIO,SAAS,eAAe,UAAgB,cAAsB,YAA+B;AAClG,MAAI,QAAQ,aAAa,KAAK,IAAI,cAAc,UAAU,CAAC;AAC3D,MAAI,QAAQ,aAAa,KAAK,IAAI,cAAc,UAAU,CAAC;AAC3D,MAAI,QAAQ,QAAQ,0BAA2B,SAAQ,QAAQ;AAC/D,MAAI,QAAQ,iBAAiB;AAC3B,YAAQ;AACR,YAAQ,KAAK,IAAI,GAAG,QAAQ,yBAAyB;AAAA,EACvD;AACA,QAAM,QAAQ,IAAI,KAAK,QAAQ;AAC/B,QAAM,SAAS,GAAG,GAAG,GAAG,CAAC;AACzB,QAAM,WAAW,KAAK;AACtB,QAAM,MAAM,IAAI,KAAK,QAAQ;AAC7B,MAAI,SAAS,GAAG,GAAG,GAAG,CAAC;AACvB,MAAI,WAAW,KAAK;AACpB,SAAO,EAAE,OAAO,IAAI;AACtB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
5
6
|
import {
|
|
6
7
|
runCrudMutationGuardAfterSuccess,
|
|
7
8
|
validateCrudMutationGuard
|
|
@@ -51,15 +52,24 @@ async function DELETE(req, ctx) {
|
|
|
51
52
|
if (guardResult && !guardResult.ok) {
|
|
52
53
|
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
let deleted = false;
|
|
56
|
+
try {
|
|
57
|
+
deleted = await deleteUserPerspective(em, cache, {
|
|
58
|
+
scope: {
|
|
59
|
+
userId: auth.sub,
|
|
60
|
+
tenantId: auth.tenantId ?? null,
|
|
61
|
+
organizationId: auth.orgId ?? null
|
|
62
|
+
},
|
|
63
|
+
tableId,
|
|
64
|
+
perspectiveId,
|
|
65
|
+
request: req
|
|
66
|
+
});
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (isCrudHttpError(err)) {
|
|
69
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
63
73
|
if (deleted && guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
64
74
|
await runCrudMutationGuardAfterSuccess(container, {
|
|
65
75
|
tenantId: auth.tenantId ?? "",
|
|
@@ -89,6 +99,7 @@ const perspectiveDeleteDoc = {
|
|
|
89
99
|
errors: [
|
|
90
100
|
{ status: 400, description: "Invalid identifiers supplied", schema: perspectivesErrorSchema },
|
|
91
101
|
{ status: 401, description: "Authentication required", schema: perspectivesErrorSchema },
|
|
102
|
+
{ status: 409, description: "Optimistic lock conflict", schema: perspectivesErrorSchema },
|
|
92
103
|
{ status: 404, description: "Perspective not found", schema: perspectivesErrorSchema }
|
|
93
104
|
]
|
|
94
105
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/perspectives/api/%5BtableId%5D/%5BperspectiveId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { deleteUserPerspective } from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { perspectivesTag, perspectivesErrorSchema, perspectivesSuccessSchema } from '../../openapi'\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params: { tableId: string; perspectiveId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n const perspectiveId = decodeParam(ctx.params?.perspectiveId).trim()\n if (!tableId || !perspectiveId) {\n return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.perspective',\n resourceId: perspectiveId,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { tableId, perspectiveId },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,6BAA6B;AAEtC,SAAS,iBAAiB,yBAAyB,iCAAiC;AAE7E,MAAM,WAAW;AAAA,EACtB,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,kBAAkB,EAAE;AACrE;AAEA,MAAM,cAAc,CAAC,UAAiD;AACpE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAC9C,MAAI;AACF,WAAO,mBAAmB,GAAG;AAAA,EAC/B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,OAAO,KAAc,KAA6D;AACtG,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE9E,QAAM,UAAU,YAAY,IAAI,QAAQ,OAAO,EAAE,KAAK;AACtD,QAAM,gBAAgB,YAAY,IAAI,QAAQ,aAAa,EAAE,KAAK;AAClE,MAAI,CAAC,WAAW,CAAC,eAAe;AAC9B,WAAO,aAAa,KAAK,EAAE,OAAO,qBAAqB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3E;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,SAAS,MAA0D;AACvE,QAAI;AACF,aAAO,UAAU,QAAQ,OAAO;AAAA,IAClC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAEH,QAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,IAC7D,UAAU,KAAK,YAAY;AAAA,IAC3B,gBAAgB,KAAK,SAAS;AAAA,IAC9B,QAAQ,KAAK;AAAA,IACb,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,eAAe,IAAI;AAAA,IACnB,gBAAgB,IAAI;AAAA,IACpB,iBAAiB,EAAE,SAAS,cAAc;AAAA,EAC5C,CAAC;AACD,MAAI,eAAe,CAAC,YAAY,IAAI;AAClC,WAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,EAC3E;AAEA,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { deleteUserPerspective } from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { perspectivesTag, perspectivesErrorSchema, perspectivesSuccessSchema } from '../../openapi'\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params: { tableId: string; perspectiveId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n const perspectiveId = decodeParam(ctx.params?.perspectiveId).trim()\n if (!tableId || !perspectiveId) {\n return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.perspective',\n resourceId: perspectiveId,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { tableId, perspectiveId },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let deleted = false\n try {\n deleted = await deleteUserPerspective(em, cache, {\n scope: {\n userId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n },\n tableId,\n perspectiveId,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n if (deleted && guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.perspective',\n resourceId: perspectiveId,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ success: true })\n}\n\nconst perspectiveDeletePathParamsSchema = z.object({\n tableId: z.string().min(1),\n perspectiveId: z.string().uuid(),\n})\n\nconst perspectiveDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete a personal perspective',\n description: 'Removes a perspective owned by the current user for the given table.',\n tags: [perspectivesTag],\n responses: [\n { status: 200, description: 'Perspective removed.', schema: perspectivesSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifiers supplied', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: perspectivesErrorSchema },\n { status: 404, description: 'Perspective not found', schema: perspectivesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: perspectivesTag,\n summary: 'Delete personal perspective',\n pathParams: perspectiveDeletePathParamsSchema,\n methods: {\n DELETE: perspectiveDeleteDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
5
6
|
import {
|
|
6
7
|
runCrudMutationGuardAfterSuccess,
|
|
7
8
|
validateCrudMutationGuard
|
|
@@ -21,6 +22,10 @@ const decodeParam = (value) => {
|
|
|
21
22
|
return raw;
|
|
22
23
|
}
|
|
23
24
|
};
|
|
25
|
+
const rolePerspectiveDeleteBodySchema = z.object({
|
|
26
|
+
roleExpectedUpdatedAtByRoleId: z.record(z.string().uuid(), z.string().min(1).nullable()).optional(),
|
|
27
|
+
roleExpectedUpdatedAtByPerspectiveId: z.record(z.string().uuid(), z.string().min(1).nullable()).optional()
|
|
28
|
+
}).optional();
|
|
24
29
|
async function DELETE(req, ctx) {
|
|
25
30
|
const auth = await getAuthFromRequest(req);
|
|
26
31
|
if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
@@ -29,6 +34,21 @@ async function DELETE(req, ctx) {
|
|
|
29
34
|
if (!tableId || !roleId) {
|
|
30
35
|
return NextResponse.json({ error: "Invalid parameters" }, { status: 400 });
|
|
31
36
|
}
|
|
37
|
+
let parsedBody = void 0;
|
|
38
|
+
if (req.body && (req.headers.get("content-type") ?? "").includes("application/json")) {
|
|
39
|
+
try {
|
|
40
|
+
const rawBody = await req.text();
|
|
41
|
+
if (rawBody.trim().length > 0) {
|
|
42
|
+
const parsed = rolePerspectiveDeleteBodySchema.safeParse(JSON.parse(rawBody));
|
|
43
|
+
if (!parsed.success) {
|
|
44
|
+
return NextResponse.json({ error: "Invalid payload", details: parsed.error.flatten() }, { status: 400 });
|
|
45
|
+
}
|
|
46
|
+
parsedBody = parsed.data;
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
32
52
|
const container = await createRequestContainer();
|
|
33
53
|
const em = container.resolve("em");
|
|
34
54
|
const cache = (() => {
|
|
@@ -55,12 +75,23 @@ async function DELETE(req, ctx) {
|
|
|
55
75
|
if (guardResult && !guardResult.ok) {
|
|
56
76
|
return NextResponse.json(guardResult.body, { status: guardResult.status });
|
|
57
77
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
let clearedCount = 0;
|
|
79
|
+
try {
|
|
80
|
+
clearedCount = await clearRolePerspectives(em, cache, {
|
|
81
|
+
tableId,
|
|
82
|
+
tenantId: auth.tenantId ?? null,
|
|
83
|
+
organizationId: auth.orgId ?? null,
|
|
84
|
+
roleIds: [roleId],
|
|
85
|
+
expectedUpdatedAtByRoleId: parsedBody?.roleExpectedUpdatedAtByRoleId,
|
|
86
|
+
expectedUpdatedAtByPerspectiveId: parsedBody?.roleExpectedUpdatedAtByPerspectiveId,
|
|
87
|
+
request: req
|
|
88
|
+
});
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (isCrudHttpError(err)) {
|
|
91
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
64
95
|
if (clearedCount > 0 && guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
65
96
|
await runCrudMutationGuardAfterSuccess(container, {
|
|
66
97
|
tenantId: auth.tenantId ?? "",
|
|
@@ -84,6 +115,11 @@ const rolePerspectiveDeleteDoc = {
|
|
|
84
115
|
summary: "Clear role perspectives for a table",
|
|
85
116
|
description: "Removes all role-level perspectives associated with the provided role identifier for the table.",
|
|
86
117
|
tags: [perspectivesTag],
|
|
118
|
+
requestBody: {
|
|
119
|
+
contentType: "application/json",
|
|
120
|
+
schema: rolePerspectiveDeleteBodySchema,
|
|
121
|
+
description: "Optional per-role or per-perspective optimistic-lock tokens."
|
|
122
|
+
},
|
|
87
123
|
responses: [
|
|
88
124
|
{ status: 200, description: "Role perspectives cleared.", schema: perspectivesSuccessSchema }
|
|
89
125
|
],
|
|
@@ -91,6 +127,7 @@ const rolePerspectiveDeleteDoc = {
|
|
|
91
127
|
{ status: 400, description: "Invalid identifiers supplied", schema: perspectivesErrorSchema },
|
|
92
128
|
{ status: 401, description: "Authentication required", schema: perspectivesErrorSchema },
|
|
93
129
|
{ status: 403, description: "Missing perspectives.role_defaults feature", schema: perspectivesErrorSchema },
|
|
130
|
+
{ status: 409, description: "Optimistic lock conflict", schema: perspectivesErrorSchema },
|
|
94
131
|
{ status: 404, description: "Role not found in scope", schema: perspectivesErrorSchema }
|
|
95
132
|
]
|
|
96
133
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/perspectives/api/%5BtableId%5D/roles/%5BroleId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { clearRolePerspectives } from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { perspectivesTag, perspectivesErrorSchema, perspectivesSuccessSchema } from '../../../openapi'\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['perspectives.role_defaults'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params: { tableId: string; roleId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n const roleId = decodeParam(ctx.params?.roleId).trim()\n if (!tableId || !roleId) {\n return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n\n const scope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n\n const role = await em.findOne(Role, { id: roleId, deletedAt: null, ...(scope as any) } as any)\n if (!role) return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.role_perspective',\n resourceId: roleId,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { tableId, roleId },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,6BAA6B;AACtC,SAAS,YAAY;AAErB,SAAS,iBAAiB,yBAAyB,iCAAiC;AAE7E,MAAM,WAAW;AAAA,EACtB,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC/E;AAEA,MAAM,cAAc,CAAC,UAAiD;AACpE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAC9C,MAAI;AACF,WAAO,mBAAmB,GAAG;AAAA,EAC/B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,OAAO,KAAc,KAAsD;AAC/F,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE9E,QAAM,UAAU,YAAY,IAAI,QAAQ,OAAO,EAAE,KAAK;AACtD,QAAM,SAAS,YAAY,IAAI,QAAQ,MAAM,EAAE,KAAK;AACpD,MAAI,CAAC,WAAW,CAAC,QAAQ;AACvB,WAAO,aAAa,KAAK,EAAE,OAAO,qBAAqB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3E;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,SAAS,MAA0D;AACvE,QAAI;AACF,aAAO,UAAU,QAAQ,OAAO;AAAA,IAClC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAEH,QAAM,QAAQ,KAAK,WACf,EAAE,KAAK,CAAC,EAAE,UAAU,KAAK,SAAS,GAAG,EAAE,UAAU,KAAK,CAAC,EAAE,IACzD,EAAE,UAAU,KAAK;AAErB,QAAM,OAAO,MAAM,GAAG,QAAQ,MAAM,EAAE,IAAI,QAAQ,WAAW,MAAM,GAAI,MAAc,CAAQ;AAC7F,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAEhF,QAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,IAC7D,UAAU,KAAK,YAAY;AAAA,IAC3B,gBAAgB,KAAK,SAAS;AAAA,IAC9B,QAAQ,KAAK;AAAA,IACb,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,eAAe,IAAI;AAAA,IACnB,gBAAgB,IAAI;AAAA,IACpB,iBAAiB,EAAE,SAAS,OAAO;AAAA,EACrC,CAAC;AACD,MAAI,eAAe,CAAC,YAAY,IAAI;AAClC,WAAO,aAAa,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC;AAAA,EAC3E;AAEA,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { clearRolePerspectives } from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { perspectivesTag, perspectivesErrorSchema, perspectivesSuccessSchema } from '../../../openapi'\n\nexport const metadata = {\n DELETE: { requireAuth: true, requireFeatures: ['perspectives.role_defaults'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nconst rolePerspectiveDeleteBodySchema = z.object({\n roleExpectedUpdatedAtByRoleId: z.record(z.string().uuid(), z.string().min(1).nullable()).optional(),\n roleExpectedUpdatedAtByPerspectiveId: z.record(z.string().uuid(), z.string().min(1).nullable()).optional(),\n}).optional()\n\nexport async function DELETE(req: Request, ctx: { params: { tableId: string; roleId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n const roleId = decodeParam(ctx.params?.roleId).trim()\n if (!tableId || !roleId) {\n return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 })\n }\n\n let parsedBody: z.infer<typeof rolePerspectiveDeleteBodySchema> = undefined\n if (req.body && (req.headers.get('content-type') ?? '').includes('application/json')) {\n try {\n const rawBody = await req.text()\n if (rawBody.trim().length > 0) {\n const parsed = rolePerspectiveDeleteBodySchema.safeParse(JSON.parse(rawBody))\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n parsedBody = parsed.data\n }\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n\n const scope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n\n const role = await em.findOne(Role, { id: roleId, deletedAt: null, ...(scope as any) } as any)\n if (!role) return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.role_perspective',\n resourceId: roleId,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { tableId, roleId },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let clearedCount = 0\n try {\n clearedCount = await clearRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n roleIds: [roleId],\n expectedUpdatedAtByRoleId: parsedBody?.roleExpectedUpdatedAtByRoleId,\n expectedUpdatedAtByPerspectiveId: parsedBody?.roleExpectedUpdatedAtByPerspectiveId,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n if (clearedCount > 0 && guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.role_perspective',\n resourceId: roleId,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ success: true })\n}\n\nconst rolePerspectiveDeleteParamsSchema = z.object({\n tableId: z.string().min(1),\n roleId: z.string().uuid(),\n})\n\nconst rolePerspectiveDeleteDoc: OpenApiMethodDoc = {\n summary: 'Clear role perspectives for a table',\n description: 'Removes all role-level perspectives associated with the provided role identifier for the table.',\n tags: [perspectivesTag],\n requestBody: {\n contentType: 'application/json',\n schema: rolePerspectiveDeleteBodySchema,\n description: 'Optional per-role or per-perspective optimistic-lock tokens.',\n },\n responses: [\n { status: 200, description: 'Role perspectives cleared.', schema: perspectivesSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifiers supplied', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n { status: 403, description: 'Missing perspectives.role_defaults feature', schema: perspectivesErrorSchema },\n { status: 409, description: 'Optimistic lock conflict', schema: perspectivesErrorSchema },\n { status: 404, description: 'Role not found in scope', schema: perspectivesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: perspectivesTag,\n summary: 'Delete role-level perspectives',\n pathParams: rolePerspectiveDeleteParamsSchema,\n methods: {\n DELETE: rolePerspectiveDeleteDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -71,13 +71,19 @@ async function GET(_req, ctx) {
|
|
|
71
71
|
) ?? false;
|
|
72
72
|
const roleScope = auth.tenantId ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] } : { tenantId: null };
|
|
73
73
|
const availableRoles = canApplyToRoles ? await em.find(Role, { ...roleScope, deletedAt: null }, { orderBy: { name: "asc" } }) : assignedRoles;
|
|
74
|
+
const manageableRoleIds = canApplyToRoles ? availableRoles.map((role) => role.id) : assignedRoleIds;
|
|
74
75
|
const state = await loadPerspectivesState(em, cache, {
|
|
75
76
|
scope: buildScope(auth),
|
|
76
77
|
tableId,
|
|
77
78
|
roleIds: assignedRoleIds
|
|
78
79
|
});
|
|
80
|
+
const manageableState = manageableRoleIds.length === assignedRoleIds.length && manageableRoleIds.every((roleId) => assignedRoleIds.includes(roleId)) ? state : await loadPerspectivesState(em, cache, {
|
|
81
|
+
scope: buildScope(auth),
|
|
82
|
+
tableId,
|
|
83
|
+
roleIds: manageableRoleIds
|
|
84
|
+
});
|
|
79
85
|
const rolePerspectiveByRole = /* @__PURE__ */ new Map();
|
|
80
|
-
for (const item of
|
|
86
|
+
for (const item of manageableState.rolePerspectives) {
|
|
81
87
|
const entry = rolePerspectiveByRole.get(item.roleId) ?? { hasDefault: false, count: 0 };
|
|
82
88
|
entry.count += 1;
|
|
83
89
|
entry.hasDefault = entry.hasDefault || item.isDefault;
|
|
@@ -91,6 +97,10 @@ async function GET(_req, ctx) {
|
|
|
91
97
|
...rp,
|
|
92
98
|
roleName: availableRoles.find((role) => role.id === rp.roleId)?.name ?? assignedRoles.find((role) => role.id === rp.roleId)?.name ?? null
|
|
93
99
|
})),
|
|
100
|
+
manageableRolePerspectives: manageableState.rolePerspectives.map((rp) => ({
|
|
101
|
+
...rp,
|
|
102
|
+
roleName: availableRoles.find((role) => role.id === rp.roleId)?.name ?? assignedRoles.find((role) => role.id === rp.roleId)?.name ?? null
|
|
103
|
+
})),
|
|
94
104
|
roles: availableRoles.map((role) => {
|
|
95
105
|
const stats = rolePerspectiveByRole.get(role.id);
|
|
96
106
|
return {
|
|
@@ -217,7 +227,10 @@ async function POST(req, ctx) {
|
|
|
217
227
|
name: parsed.data.name,
|
|
218
228
|
settings: parsed.data.settings,
|
|
219
229
|
setDefault: parsed.data.setRoleDefault ?? false
|
|
220
|
-
}
|
|
230
|
+
},
|
|
231
|
+
expectedUpdatedAtByRoleId: parsed.data.roleExpectedUpdatedAtByRoleId,
|
|
232
|
+
expectedUpdatedAtByPerspectiveId: parsed.data.roleExpectedUpdatedAtByPerspectiveId,
|
|
233
|
+
request: req
|
|
221
234
|
});
|
|
222
235
|
}
|
|
223
236
|
},
|
|
@@ -227,7 +240,10 @@ async function POST(req, ctx) {
|
|
|
227
240
|
tableId,
|
|
228
241
|
tenantId: auth.tenantId ?? null,
|
|
229
242
|
organizationId: auth.orgId ?? null,
|
|
230
|
-
roleIds: clearRoleIds
|
|
243
|
+
roleIds: clearRoleIds,
|
|
244
|
+
expectedUpdatedAtByRoleId: parsed.data.roleExpectedUpdatedAtByRoleId,
|
|
245
|
+
expectedUpdatedAtByPerspectiveId: parsed.data.roleExpectedUpdatedAtByPerspectiveId,
|
|
246
|
+
request: req
|
|
231
247
|
});
|
|
232
248
|
}
|
|
233
249
|
}
|
|
@@ -301,7 +317,8 @@ const perspectivesPostDoc = {
|
|
|
301
317
|
errors: [
|
|
302
318
|
{ status: 400, description: "Validation failed or invalid roles provided", schema: perspectivesErrorSchema },
|
|
303
319
|
{ status: 401, description: "Authentication required", schema: perspectivesErrorSchema },
|
|
304
|
-
{ status: 403, description: "Missing perspectives.role_defaults feature for role updates", schema: perspectivesErrorSchema }
|
|
320
|
+
{ status: 403, description: "Missing perspectives.role_defaults feature for role updates", schema: perspectivesErrorSchema },
|
|
321
|
+
{ status: 409, description: "Optimistic lock conflict", schema: perspectivesErrorSchema }
|
|
305
322
|
]
|
|
306
323
|
};
|
|
307
324
|
const openApi = {
|