payload-reserve 1.5.0 → 1.6.0

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.
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useConfig, useTranslation } from '@payloadcms/ui';
4
4
  import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
5
+ import { useTenantFilter } from '../../utilities/useTenantFilter.js';
5
6
  import styles from './AvailabilityOverview.module.css';
6
7
  const DAY_MAP = {
7
8
  fri: 5,
@@ -31,6 +32,9 @@ export const AvailabilityOverview = ()=>{
31
32
  'pending',
32
33
  'confirmed'
33
34
  ];
35
+ const resourcesTenantParams = useTenantFilter(slugs?.resources ?? 'resources');
36
+ const schedulesTenantParams = useTenantFilter(slugs?.schedules ?? 'schedules');
37
+ const reservationsTenantParams = useTenantFilter(slugs?.reservations ?? 'reservations');
34
38
  const DAY_NAMES = useMemo(()=>[
35
39
  t('reservation:dayShortSun'),
36
40
  t('reservation:dayShortMon'),
@@ -83,16 +87,28 @@ export const AvailabilityOverview = ()=>{
83
87
  // Payload's REST API accepts a comma-separated list.
84
88
  const blockingIn = blockingStatuses.join(',');
85
89
  try {
90
+ const resourcesParams = new URLSearchParams({
91
+ limit: '100',
92
+ 'where[active][equals]': 'true',
93
+ ...resourcesTenantParams
94
+ });
95
+ const schedulesParams = new URLSearchParams({
96
+ limit: '500',
97
+ 'where[active][equals]': 'true',
98
+ ...schedulesTenantParams
99
+ });
100
+ const reservationsParams = new URLSearchParams({
101
+ depth: '0',
102
+ limit: '500',
103
+ 'where[startTime][greater_than_equal]': weekStart.toISOString(),
104
+ 'where[startTime][less_than_equal]': weekEnd.toISOString(),
105
+ 'where[status][in]': blockingIn,
106
+ ...reservationsTenantParams
107
+ });
86
108
  const [resourcesRes, schedulesRes, reservationsRes] = await Promise.all([
87
- fetch(`${apiBase}/${slugs.resources}?where[active][equals]=true&limit=100`),
88
- fetch(`${apiBase}/${slugs.schedules}?where[active][equals]=true&limit=500`),
89
- fetch(`${apiBase}/${slugs.reservations}?${new URLSearchParams({
90
- depth: '0',
91
- limit: '500',
92
- 'where[startTime][greater_than_equal]': weekStart.toISOString(),
93
- 'where[startTime][less_than_equal]': weekEnd.toISOString(),
94
- 'where[status][in]': blockingIn
95
- })}`)
109
+ fetch(`${apiBase}/${slugs.resources}?${resourcesParams}`),
110
+ fetch(`${apiBase}/${slugs.schedules}?${schedulesParams}`),
111
+ fetch(`${apiBase}/${slugs.reservations}?${reservationsParams}`)
96
112
  ]);
97
113
  const [rData, sData, resData] = await Promise.all([
98
114
  resourcesRes.json(),
@@ -119,7 +135,10 @@ export const AvailabilityOverview = ()=>{
119
135
  config.routes.api,
120
136
  config.serverURL,
121
137
  slugs,
122
- blockingStatuses.join(',')
138
+ blockingStatuses.join(','),
139
+ resourcesTenantParams,
140
+ schedulesTenantParams,
141
+ reservationsTenantParams
123
142
  ]);
124
143
  const navigateWeek = useCallback((direction)=>{
125
144
  setWeekStart((prev)=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/AvailabilityOverview/index.tsx"],"sourcesContent":["'use client'\nimport type { AdminViewServerProps } from 'payload'\n\nimport { useConfig, useTranslation } from '@payloadcms/ui'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\n\nimport styles from './AvailabilityOverview.module.css'\n\ntype Resource = {\n active?: boolean\n capacityMode?: 'per-guest' | 'per-reservation'\n id: string\n name: string\n quantity?: number\n}\n\ntype Schedule = {\n active?: boolean\n exceptions?: Array<{ date: string; reason?: string }>\n id: string\n manualSlots?: Array<{ date: string; endTime: string; startTime: string }>\n recurringSlots?: Array<{ day: string; endTime: string; startTime: string }>\n resource: { id: string } | string\n scheduleType: 'manual' | 'recurring'\n}\n\ntype Reservation = {\n endTime?: string\n id: string\n resource: { id: string } | string\n startTime: string\n status: string\n}\n\nconst DAY_MAP: Record<string, number> = {\n fri: 5,\n mon: 1,\n sat: 6,\n sun: 0,\n thu: 4,\n tue: 2,\n wed: 3,\n}\n\n/** Return the CSS class for a capacity badge based on utilization ratio. */\nfunction capacityClass(booked: number, total: number): string {\n if (booked >= total) {return styles.slotCapacityFull}\n if (booked / total >= 0.5) {return styles.slotCapacityMid}\n return styles.slotCapacityLow\n}\n\nexport const AvailabilityOverview: React.FC<AdminViewServerProps> = () => {\n const { config } = useConfig()\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n const slugs = config.admin?.custom?.reservationSlugs\n const statusMachine = config.admin?.custom?.reservationStatusMachine\n const blockingStatuses: string[] = statusMachine?.blockingStatuses ?? ['pending', 'confirmed']\n\n const DAY_NAMES = useMemo(\n () => [\n t('reservation:dayShortSun'),\n t('reservation:dayShortMon'),\n t('reservation:dayShortTue'),\n t('reservation:dayShortWed'),\n t('reservation:dayShortThu'),\n t('reservation:dayShortFri'),\n t('reservation:dayShortSat'),\n ],\n [t],\n )\n\n const [weekStart, setWeekStart] = useState(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n return d\n })\n\n const [resources, setResources] = useState<Resource[]>([])\n const [schedules, setSchedules] = useState<Schedule[]>([])\n const [reservations, setReservations] = useState<Reservation[]>([])\n const [loading, setLoading] = useState(true)\n\n const weekDays = useMemo(() => {\n return Array.from({ length: 7 }, (_, i) => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + i)\n return d\n })\n }, [weekStart])\n\n const weekEnd = useMemo(() => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + 6)\n d.setHours(23, 59, 59, 999)\n return d\n }, [weekStart])\n\n useEffect(() => {\n if (!slugs) {return}\n\n const fetchData = async () => {\n setLoading(true)\n const apiBase = `${config.serverURL ?? ''}${config.routes.api}`\n\n // Build a query that fetches only blocking-status reservations so the\n // component doesn't need to filter client-side. The `in` operator on\n // Payload's REST API accepts a comma-separated list.\n const blockingIn = blockingStatuses.join(',')\n\n try {\n const [resourcesRes, schedulesRes, reservationsRes] = await Promise.all([\n fetch(`${apiBase}/${slugs.resources}?where[active][equals]=true&limit=100`),\n fetch(`${apiBase}/${slugs.schedules}?where[active][equals]=true&limit=500`),\n fetch(\n `${apiBase}/${slugs.reservations}?${new URLSearchParams({\n depth: '0',\n limit: '500',\n 'where[startTime][greater_than_equal]': weekStart.toISOString(),\n 'where[startTime][less_than_equal]': weekEnd.toISOString(),\n 'where[status][in]': blockingIn,\n })}`,\n ),\n ])\n\n const [rData, sData, resData] = await Promise.all([\n resourcesRes.json(),\n schedulesRes.json(),\n reservationsRes.json(),\n ])\n\n setResources(rData.docs ?? [])\n setSchedules(sData.docs ?? [])\n setReservations(resData.docs ?? [])\n } catch {\n setResources([])\n setSchedules([])\n setReservations([])\n }\n setLoading(false)\n }\n\n void fetchData()\n // blockingStatuses is derived from config which is stable; stringify to\n // avoid object-reference churn causing infinite loops.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [weekStart, weekEnd, config.routes.api, config.serverURL, slugs, blockingStatuses.join(',')])\n\n const navigateWeek = useCallback((direction: -1 | 1) => {\n setWeekStart((prev) => {\n const next = new Date(prev)\n next.setDate(next.getDate() + 7 * direction)\n return next\n })\n }, [])\n\n const goToThisWeek = useCallback(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n setWeekStart(d)\n }, [])\n\n const getResourceId = (r: { id: string } | string) =>\n typeof r === 'object' ? r.id : r\n\n const getSlotsForResourceDay = (resourceId: string, day: Date) => {\n const resourceSchedules = schedules.filter(\n (s) => getResourceId(s.resource) === resourceId,\n )\n const dateStr = day.toISOString().split('T')[0]\n const dayOfWeek = day.getDay()\n\n const slots: Array<{ label: string; type: 'available' | 'exception' }> = []\n\n for (const schedule of resourceSchedules) {\n // Check for exceptions\n const exception = schedule.exceptions?.find((e) => {\n const excDate = new Date(e.date).toISOString().split('T')[0]\n return excDate === dateStr\n })\n\n if (exception) {\n slots.push({\n type: 'exception',\n label: exception.reason || t('reservation:availabilityUnavailable'),\n })\n continue\n }\n\n if (schedule.scheduleType === 'recurring') {\n for (const slot of schedule.recurringSlots ?? []) {\n if (DAY_MAP[slot.day] === dayOfWeek) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n } else if (schedule.scheduleType === 'manual') {\n for (const slot of schedule.manualSlots ?? []) {\n const slotDate = new Date(slot.date).toISOString().split('T')[0]\n if (slotDate === dateStr) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n }\n }\n\n return slots\n }\n\n /** Returns all blocking-status reservations for a resource on a given day. */\n const getBookingsForResourceDay = (resourceId: string, day: Date) => {\n return reservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n getResourceId(r.resource) === resourceId &&\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate()\n )\n })\n }\n\n if (!slugs) {\n return <div className={styles.noResources}>{t('reservation:availabilityNotConfigured')}</div>\n }\n\n if (loading) {\n return <div className={styles.loading}>{t('reservation:availabilityLoading')}</div>\n }\n\n const weekLabel = `${weekDays[0].toLocaleDateString([], { day: 'numeric', month: 'short' })} - ${weekDays[6].toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' })}`\n\n const gridColumns = `150px repeat(7, 1fr)`\n\n return (\n <div className={styles.wrapper}>\n <h2 className={styles.title}>{t('reservation:availabilityTitle')}</h2>\n <div className={styles.navigation}>\n <button className={styles.navButton} onClick={() => navigateWeek(-1)} type=\"button\">\n &larr;\n </button>\n <button className={styles.navButton} onClick={goToThisWeek} type=\"button\">\n {t('reservation:availabilityThisWeek')}\n </button>\n <button className={styles.navButton} onClick={() => navigateWeek(1)} type=\"button\">\n &rarr;\n </button>\n <span className={styles.weekLabel}>{weekLabel}</span>\n </div>\n\n {resources.length === 0 ? (\n <div className={styles.noResources}>{t('reservation:availabilityNoResources')}</div>\n ) : (\n <div className={styles.grid} style={{ gridTemplateColumns: gridColumns }}>\n {/* Header row */}\n <div className={styles.headerCell}>{t('reservation:availabilityResource')}</div>\n {weekDays.map((day, i) => (\n <div className={styles.headerCell} key={i}>\n {DAY_NAMES[day.getDay()]} {day.getDate()}\n </div>\n ))}\n\n {/* Resource rows */}\n {resources.map((resource) => {\n const quantity = resource.quantity ?? 1\n return (\n <Fragment key={resource.id}>\n <div className={styles.resourceName}>\n {resource.name}\n {quantity > 1 && (\n <span style={{ fontWeight: 400, marginLeft: 4, opacity: 0.6 }}>\n {' '}(&times;{quantity})\n </span>\n )}\n </div>\n {weekDays.map((day, di) => {\n const slots = getSlotsForResourceDay(resource.id, day)\n const bookings = getBookingsForResourceDay(resource.id, day)\n const bookedCount = bookings.length\n\n return (\n <div className={styles.cell} key={`cell-${resource.id}-${di}`}>\n {slots.map((slot, si) => (\n <div\n className={\n slot.type === 'exception'\n ? styles.slotException\n : styles.slotAvailable\n }\n key={`slot-${si}`}\n >\n {slot.label}\n </div>\n ))}\n {quantity > 1 ? (\n /* Multi-unit resource: show X/Y booked with graduated color */\n bookedCount > 0 && (\n <div\n className={capacityClass(bookedCount, quantity)}\n title={t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n >\n {t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n </div>\n )\n ) : (\n /* Single-unit resource: show individual booking times */\n bookings.map((b) => (\n <div className={styles.slotBooked} key={b.id}>\n {new Date(b.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}{' '}\n {t('reservation:availabilityBooked')}\n </div>\n ))\n )}\n </div>\n )\n })}\n </Fragment>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n"],"names":["useConfig","useTranslation","Fragment","useCallback","useEffect","useMemo","useState","styles","DAY_MAP","fri","mon","sat","sun","thu","tue","wed","capacityClass","booked","total","slotCapacityFull","slotCapacityMid","slotCapacityLow","AvailabilityOverview","config","t","_t","slugs","admin","custom","reservationSlugs","statusMachine","reservationStatusMachine","blockingStatuses","DAY_NAMES","weekStart","setWeekStart","now","Date","d","getFullYear","getMonth","getDate","setDate","getDay","resources","setResources","schedules","setSchedules","reservations","setReservations","loading","setLoading","weekDays","Array","from","length","_","i","weekEnd","setHours","fetchData","apiBase","serverURL","routes","api","blockingIn","join","resourcesRes","schedulesRes","reservationsRes","Promise","all","fetch","URLSearchParams","depth","limit","toISOString","rData","sData","resData","json","docs","navigateWeek","direction","prev","next","goToThisWeek","getResourceId","r","id","getSlotsForResourceDay","resourceId","day","resourceSchedules","filter","s","resource","dateStr","split","dayOfWeek","slots","schedule","exception","exceptions","find","e","excDate","date","push","type","label","reason","scheduleType","slot","recurringSlots","startTime","endTime","manualSlots","slotDate","getBookingsForResourceDay","rDate","div","className","noResources","weekLabel","toLocaleDateString","month","year","gridColumns","wrapper","h2","title","navigation","button","navButton","onClick","span","grid","style","gridTemplateColumns","headerCell","map","quantity","resourceName","name","fontWeight","marginLeft","opacity","di","bookings","bookedCount","cell","si","slotException","slotAvailable","b","slotBooked","toLocaleTimeString","hour","minute"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/AvailabilityOverview/index.tsx"],"sourcesContent":["'use client'\nimport type { AdminViewServerProps } from 'payload'\n\nimport { useConfig, useTranslation } from '@payloadcms/ui'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\n\nimport { useTenantFilter } from '../../utilities/useTenantFilter.js'\nimport styles from './AvailabilityOverview.module.css'\n\ntype Resource = {\n active?: boolean\n capacityMode?: 'per-guest' | 'per-reservation'\n id: string\n name: string\n quantity?: number\n}\n\ntype Schedule = {\n active?: boolean\n exceptions?: Array<{ date: string; reason?: string }>\n id: string\n manualSlots?: Array<{ date: string; endTime: string; startTime: string }>\n recurringSlots?: Array<{ day: string; endTime: string; startTime: string }>\n resource: { id: string } | string\n scheduleType: 'manual' | 'recurring'\n}\n\ntype Reservation = {\n endTime?: string\n id: string\n resource: { id: string } | string\n startTime: string\n status: string\n}\n\nconst DAY_MAP: Record<string, number> = {\n fri: 5,\n mon: 1,\n sat: 6,\n sun: 0,\n thu: 4,\n tue: 2,\n wed: 3,\n}\n\n/** Return the CSS class for a capacity badge based on utilization ratio. */\nfunction capacityClass(booked: number, total: number): string {\n if (booked >= total) {return styles.slotCapacityFull}\n if (booked / total >= 0.5) {return styles.slotCapacityMid}\n return styles.slotCapacityLow\n}\n\nexport const AvailabilityOverview: React.FC<AdminViewServerProps> = () => {\n const { config } = useConfig()\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n const slugs = config.admin?.custom?.reservationSlugs\n const statusMachine = config.admin?.custom?.reservationStatusMachine\n const blockingStatuses: string[] = statusMachine?.blockingStatuses ?? ['pending', 'confirmed']\n\n const resourcesTenantParams = useTenantFilter(slugs?.resources ?? 'resources')\n const schedulesTenantParams = useTenantFilter(slugs?.schedules ?? 'schedules')\n const reservationsTenantParams = useTenantFilter(slugs?.reservations ?? 'reservations')\n\n const DAY_NAMES = useMemo(\n () => [\n t('reservation:dayShortSun'),\n t('reservation:dayShortMon'),\n t('reservation:dayShortTue'),\n t('reservation:dayShortWed'),\n t('reservation:dayShortThu'),\n t('reservation:dayShortFri'),\n t('reservation:dayShortSat'),\n ],\n [t],\n )\n\n const [weekStart, setWeekStart] = useState(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n return d\n })\n\n const [resources, setResources] = useState<Resource[]>([])\n const [schedules, setSchedules] = useState<Schedule[]>([])\n const [reservations, setReservations] = useState<Reservation[]>([])\n const [loading, setLoading] = useState(true)\n\n const weekDays = useMemo(() => {\n return Array.from({ length: 7 }, (_, i) => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + i)\n return d\n })\n }, [weekStart])\n\n const weekEnd = useMemo(() => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + 6)\n d.setHours(23, 59, 59, 999)\n return d\n }, [weekStart])\n\n useEffect(() => {\n if (!slugs) {return}\n\n const fetchData = async () => {\n setLoading(true)\n const apiBase = `${config.serverURL ?? ''}${config.routes.api}`\n\n // Build a query that fetches only blocking-status reservations so the\n // component doesn't need to filter client-side. The `in` operator on\n // Payload's REST API accepts a comma-separated list.\n const blockingIn = blockingStatuses.join(',')\n\n try {\n const resourcesParams = new URLSearchParams({\n limit: '100',\n 'where[active][equals]': 'true',\n ...resourcesTenantParams,\n })\n const schedulesParams = new URLSearchParams({\n limit: '500',\n 'where[active][equals]': 'true',\n ...schedulesTenantParams,\n })\n const reservationsParams = new URLSearchParams({\n depth: '0',\n limit: '500',\n 'where[startTime][greater_than_equal]': weekStart.toISOString(),\n 'where[startTime][less_than_equal]': weekEnd.toISOString(),\n 'where[status][in]': blockingIn,\n ...reservationsTenantParams,\n })\n const [resourcesRes, schedulesRes, reservationsRes] = await Promise.all([\n fetch(`${apiBase}/${slugs.resources}?${resourcesParams}`),\n fetch(`${apiBase}/${slugs.schedules}?${schedulesParams}`),\n fetch(`${apiBase}/${slugs.reservations}?${reservationsParams}`),\n ])\n\n const [rData, sData, resData] = await Promise.all([\n resourcesRes.json(),\n schedulesRes.json(),\n reservationsRes.json(),\n ])\n\n setResources(rData.docs ?? [])\n setSchedules(sData.docs ?? [])\n setReservations(resData.docs ?? [])\n } catch {\n setResources([])\n setSchedules([])\n setReservations([])\n }\n setLoading(false)\n }\n\n void fetchData()\n // blockingStatuses is derived from config which is stable; stringify to\n // avoid object-reference churn causing infinite loops.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [weekStart, weekEnd, config.routes.api, config.serverURL, slugs, blockingStatuses.join(','), resourcesTenantParams, schedulesTenantParams, reservationsTenantParams])\n\n const navigateWeek = useCallback((direction: -1 | 1) => {\n setWeekStart((prev) => {\n const next = new Date(prev)\n next.setDate(next.getDate() + 7 * direction)\n return next\n })\n }, [])\n\n const goToThisWeek = useCallback(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n setWeekStart(d)\n }, [])\n\n const getResourceId = (r: { id: string } | string) =>\n typeof r === 'object' ? r.id : r\n\n const getSlotsForResourceDay = (resourceId: string, day: Date) => {\n const resourceSchedules = schedules.filter(\n (s) => getResourceId(s.resource) === resourceId,\n )\n const dateStr = day.toISOString().split('T')[0]\n const dayOfWeek = day.getDay()\n\n const slots: Array<{ label: string; type: 'available' | 'exception' }> = []\n\n for (const schedule of resourceSchedules) {\n // Check for exceptions\n const exception = schedule.exceptions?.find((e) => {\n const excDate = new Date(e.date).toISOString().split('T')[0]\n return excDate === dateStr\n })\n\n if (exception) {\n slots.push({\n type: 'exception',\n label: exception.reason || t('reservation:availabilityUnavailable'),\n })\n continue\n }\n\n if (schedule.scheduleType === 'recurring') {\n for (const slot of schedule.recurringSlots ?? []) {\n if (DAY_MAP[slot.day] === dayOfWeek) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n } else if (schedule.scheduleType === 'manual') {\n for (const slot of schedule.manualSlots ?? []) {\n const slotDate = new Date(slot.date).toISOString().split('T')[0]\n if (slotDate === dateStr) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n }\n }\n\n return slots\n }\n\n /** Returns all blocking-status reservations for a resource on a given day. */\n const getBookingsForResourceDay = (resourceId: string, day: Date) => {\n return reservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n getResourceId(r.resource) === resourceId &&\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate()\n )\n })\n }\n\n if (!slugs) {\n return <div className={styles.noResources}>{t('reservation:availabilityNotConfigured')}</div>\n }\n\n if (loading) {\n return <div className={styles.loading}>{t('reservation:availabilityLoading')}</div>\n }\n\n const weekLabel = `${weekDays[0].toLocaleDateString([], { day: 'numeric', month: 'short' })} - ${weekDays[6].toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' })}`\n\n const gridColumns = `150px repeat(7, 1fr)`\n\n return (\n <div className={styles.wrapper}>\n <h2 className={styles.title}>{t('reservation:availabilityTitle')}</h2>\n <div className={styles.navigation}>\n <button className={styles.navButton} onClick={() => navigateWeek(-1)} type=\"button\">\n &larr;\n </button>\n <button className={styles.navButton} onClick={goToThisWeek} type=\"button\">\n {t('reservation:availabilityThisWeek')}\n </button>\n <button className={styles.navButton} onClick={() => navigateWeek(1)} type=\"button\">\n &rarr;\n </button>\n <span className={styles.weekLabel}>{weekLabel}</span>\n </div>\n\n {resources.length === 0 ? (\n <div className={styles.noResources}>{t('reservation:availabilityNoResources')}</div>\n ) : (\n <div className={styles.grid} style={{ gridTemplateColumns: gridColumns }}>\n {/* Header row */}\n <div className={styles.headerCell}>{t('reservation:availabilityResource')}</div>\n {weekDays.map((day, i) => (\n <div className={styles.headerCell} key={i}>\n {DAY_NAMES[day.getDay()]} {day.getDate()}\n </div>\n ))}\n\n {/* Resource rows */}\n {resources.map((resource) => {\n const quantity = resource.quantity ?? 1\n return (\n <Fragment key={resource.id}>\n <div className={styles.resourceName}>\n {resource.name}\n {quantity > 1 && (\n <span style={{ fontWeight: 400, marginLeft: 4, opacity: 0.6 }}>\n {' '}(&times;{quantity})\n </span>\n )}\n </div>\n {weekDays.map((day, di) => {\n const slots = getSlotsForResourceDay(resource.id, day)\n const bookings = getBookingsForResourceDay(resource.id, day)\n const bookedCount = bookings.length\n\n return (\n <div className={styles.cell} key={`cell-${resource.id}-${di}`}>\n {slots.map((slot, si) => (\n <div\n className={\n slot.type === 'exception'\n ? styles.slotException\n : styles.slotAvailable\n }\n key={`slot-${si}`}\n >\n {slot.label}\n </div>\n ))}\n {quantity > 1 ? (\n /* Multi-unit resource: show X/Y booked with graduated color */\n bookedCount > 0 && (\n <div\n className={capacityClass(bookedCount, quantity)}\n title={t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n >\n {t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n </div>\n )\n ) : (\n /* Single-unit resource: show individual booking times */\n bookings.map((b) => (\n <div className={styles.slotBooked} key={b.id}>\n {new Date(b.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}{' '}\n {t('reservation:availabilityBooked')}\n </div>\n ))\n )}\n </div>\n )\n })}\n </Fragment>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n"],"names":["useConfig","useTranslation","Fragment","useCallback","useEffect","useMemo","useState","useTenantFilter","styles","DAY_MAP","fri","mon","sat","sun","thu","tue","wed","capacityClass","booked","total","slotCapacityFull","slotCapacityMid","slotCapacityLow","AvailabilityOverview","config","t","_t","slugs","admin","custom","reservationSlugs","statusMachine","reservationStatusMachine","blockingStatuses","resourcesTenantParams","resources","schedulesTenantParams","schedules","reservationsTenantParams","reservations","DAY_NAMES","weekStart","setWeekStart","now","Date","d","getFullYear","getMonth","getDate","setDate","getDay","setResources","setSchedules","setReservations","loading","setLoading","weekDays","Array","from","length","_","i","weekEnd","setHours","fetchData","apiBase","serverURL","routes","api","blockingIn","join","resourcesParams","URLSearchParams","limit","schedulesParams","reservationsParams","depth","toISOString","resourcesRes","schedulesRes","reservationsRes","Promise","all","fetch","rData","sData","resData","json","docs","navigateWeek","direction","prev","next","goToThisWeek","getResourceId","r","id","getSlotsForResourceDay","resourceId","day","resourceSchedules","filter","s","resource","dateStr","split","dayOfWeek","slots","schedule","exception","exceptions","find","e","excDate","date","push","type","label","reason","scheduleType","slot","recurringSlots","startTime","endTime","manualSlots","slotDate","getBookingsForResourceDay","rDate","div","className","noResources","weekLabel","toLocaleDateString","month","year","gridColumns","wrapper","h2","title","navigation","button","navButton","onClick","span","grid","style","gridTemplateColumns","headerCell","map","quantity","resourceName","name","fontWeight","marginLeft","opacity","di","bookings","bookedCount","cell","si","slotException","slotAvailable","b","slotBooked","toLocaleTimeString","hour","minute"],"mappings":"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"}
@@ -5,6 +5,7 @@ import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } fr
5
5
  import { computeSlotStates } from '../../utilities/computeSlotStates.js';
6
6
  import { statusToI18nKey } from '../../utilities/i18nUtils.js';
7
7
  import { localDayKey } from '../../utilities/slotUtils.js';
8
+ import { useTenantFilter } from '../../utilities/useTenantFilter.js';
8
9
  import styles from './CalendarView.module.css';
9
10
  import { LaneTimelineView } from './LaneTimelineView.js';
10
11
  import { useResourceAvailability } from './useResourceAvailability.js';
@@ -40,6 +41,9 @@ export const CalendarView = ()=>{
40
41
  const reservationSlug = slugs?.reservations ?? 'reservations';
41
42
  const apiUrl = `${config.serverURL ?? ''}${config.routes.api}/${reservationSlug}`;
42
43
  const apiBase = `${config.serverURL ?? ''}${config.routes.api}`;
44
+ const resourceSlug = slugs?.resources ?? 'resources';
45
+ const reservationTenantParams = useTenantFilter(reservationSlug);
46
+ const resourceTenantParams = useTenantFilter(resourceSlug);
43
47
  const statusMachine = config.admin?.custom?.reservationStatusMachine;
44
48
  // The initial/pending status (what "pending" view shows)
45
49
  const defaultStatus = statusMachine?.defaultStatus ?? 'pending';
@@ -130,12 +134,12 @@ export const CalendarView = ()=>{
130
134
  useEffect(()=>{
131
135
  const fetchResources = async ()=>{
132
136
  try {
133
- const resourceSlug = slugs?.resources ?? 'resources';
134
137
  const params = new URLSearchParams({
135
138
  depth: '0',
136
139
  limit: '100',
137
140
  sort: 'name',
138
- 'where[active][equals]': 'true'
141
+ 'where[active][equals]': 'true',
142
+ ...resourceTenantParams
139
143
  });
140
144
  const url = `${config.serverURL ?? ''}${config.routes.api}/${resourceSlug}?${params}`;
141
145
  const response = await fetch(url);
@@ -153,7 +157,8 @@ export const CalendarView = ()=>{
153
157
  }, [
154
158
  config.routes.api,
155
159
  config.serverURL,
156
- slugs?.resources
160
+ resourceSlug,
161
+ resourceTenantParams
157
162
  ]);
158
163
  const { rangeEnd, rangeStart } = useMemo(()=>{
159
164
  const start = new Date(currentDate);
@@ -188,7 +193,8 @@ export const CalendarView = ()=>{
188
193
  limit: '500',
189
194
  sort: 'startTime',
190
195
  'where[startTime][greater_than_equal]': rangeStart.toISOString(),
191
- 'where[startTime][less_than_equal]': rangeEnd.toISOString()
196
+ 'where[startTime][less_than_equal]': rangeEnd.toISOString(),
197
+ ...reservationTenantParams
192
198
  });
193
199
  const response = await fetch(`${apiUrl}?${params}`);
194
200
  const result = await response.json();
@@ -200,7 +206,8 @@ export const CalendarView = ()=>{
200
206
  }, [
201
207
  rangeStart,
202
208
  rangeEnd,
203
- apiUrl
209
+ apiUrl,
210
+ reservationTenantParams
204
211
  ]);
205
212
  useEffect(()=>{
206
213
  void fetchReservations();
@@ -212,7 +219,8 @@ export const CalendarView = ()=>{
212
219
  try {
213
220
  const params = new URLSearchParams({
214
221
  limit: '0',
215
- 'where[status][equals]': defaultStatus
222
+ 'where[status][equals]': defaultStatus,
223
+ ...reservationTenantParams
216
224
  });
217
225
  const response = await fetch(`${apiUrl}?${params}`);
218
226
  const result = await response.json();
@@ -222,7 +230,8 @@ export const CalendarView = ()=>{
222
230
  }
223
231
  }, [
224
232
  apiUrl,
225
- defaultStatus
233
+ defaultStatus,
234
+ reservationTenantParams
226
235
  ]);
227
236
  useEffect(()=>{
228
237
  void fetchPendingCount();
@@ -236,7 +245,8 @@ export const CalendarView = ()=>{
236
245
  depth: '1',
237
246
  limit: '500',
238
247
  sort: 'startTime',
239
- 'where[status][equals]': defaultStatus
248
+ 'where[status][equals]': defaultStatus,
249
+ ...reservationTenantParams
240
250
  });
241
251
  const response = await fetch(`${apiUrl}?${params}`);
242
252
  const result = await response.json();
@@ -246,7 +256,8 @@ export const CalendarView = ()=>{
246
256
  }
247
257
  }, [
248
258
  apiUrl,
249
- defaultStatus
259
+ defaultStatus,
260
+ reservationTenantParams
250
261
  ]);
251
262
  useEffect(()=>{
252
263
  if (viewMode === 'pending') {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/CalendarView/index.tsx"],"sourcesContent":["'use client'\nimport type { AdminViewServerProps } from 'payload'\n\nimport { useConfig, useDocumentDrawer, useTranslation } from '@payloadcms/ui'\nimport React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { SlotInfo } from '../../utilities/computeSlotStates.js'\n\nimport { computeSlotStates } from '../../utilities/computeSlotStates.js'\nimport { statusToI18nKey } from '../../utilities/i18nUtils.js'\nimport { localDayKey } from '../../utilities/slotUtils.js'\nimport styles from './CalendarView.module.css'\nimport { LaneTimelineView } from './LaneTimelineView.js'\nimport { useResourceAvailability } from './useResourceAvailability.js'\n\ntype ViewMode = 'day' | 'lanes' | 'month' | 'pending' | 'week'\n\ntype ReservationItem = {\n endTime?: string\n guestCount?: number\n resource?: { id?: string; name?: string } | string\n service?: { name?: string } | string\n startTime?: string\n}\n\ntype Reservation = {\n customer?: { firstName?: string; lastName?: string; name?: string } | string\n endTime?: string\n id: string\n items?: ReservationItem[]\n resource?: { id?: string; name?: string } | string\n service?: { name?: string } | string\n startTime: string\n status: string\n}\n\ntype ResourceOption = {\n id: string\n name: string\n}\n\n// Built-in status → CSS class map (for known statuses; custom statuses use inline style)\nconst STATUS_CLASS_MAP: Record<string, string> = {\n cancelled: styles.statusCancelled,\n completed: styles.statusCompleted,\n confirmed: styles.statusConfirmed,\n 'no-show': styles.statusNoShow,\n pending: styles.statusPending,\n}\n\n// Built-in default colors for known statuses\nconst BUILTIN_STATUS_COLORS: Record<string, string> = {\n cancelled: '#e5e7eb',\n completed: '#d1fae5',\n confirmed: '#dbeafe',\n 'no-show': '#fee2e2',\n pending: '#fef3c7',\n}\n\n// Palette for auto-assigning colors to custom statuses\nconst CUSTOM_STATUS_PALETTE = ['#fde68a', '#c7d2fe', '#a7f3d0', '#fca5a5', '#fdba74']\n\nexport const CalendarView: React.FC<AdminViewServerProps> = () => {\n const { config } = useConfig()\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n\n const slugs = config.admin?.custom?.reservationSlugs\n const reservationSlug = slugs?.reservations ?? 'reservations'\n const apiUrl = `${config.serverURL ?? ''}${config.routes.api}/${reservationSlug}`\n const apiBase = `${config.serverURL ?? ''}${config.routes.api}`\n\n const statusMachine = config.admin?.custom?.reservationStatusMachine as\n | {\n blockingStatuses?: string[]\n defaultStatus?: string\n statuses?: string[]\n terminalStatuses?: string[]\n transitions?: Record<string, string[]>\n }\n | undefined\n\n // The initial/pending status (what \"pending\" view shows)\n const defaultStatus = statusMachine?.defaultStatus ?? 'pending'\n\n // Build STATUS_COLORS dynamically: built-ins first, then auto-assign palette for custom statuses\n const STATUS_COLORS = useMemo<Record<string, string>>(() => {\n const colors = { ...BUILTIN_STATUS_COLORS }\n const statuses = statusMachine?.statuses ?? []\n statuses.forEach((s, i) => {\n if (!colors[s]) {\n colors[s] = CUSTOM_STATUS_PALETTE[i % CUSTOM_STATUS_PALETTE.length]\n }\n })\n return colors\n }, [statusMachine])\n\n // Derive confirm/cancel target statuses from config transitions\n // \"confirm\" = first non-terminal transition from defaultStatus\n // \"cancel\" = first terminal transition from defaultStatus (or fallback: 'cancelled')\n const { cancelStatus, confirmStatus } = useMemo(() => {\n const terminalStatuses = statusMachine?.terminalStatuses ?? ['completed', 'cancelled', 'no-show']\n const transitions = statusMachine?.transitions ?? {}\n const defaultTransitions: string[] = transitions[defaultStatus] ?? []\n\n const nonTerminal = defaultTransitions.find((s) => !terminalStatuses.includes(s))\n const terminal = defaultTransitions.find((s) => terminalStatuses.includes(s))\n\n return {\n cancelStatus: terminal ?? 'cancelled',\n confirmStatus: nonTerminal ?? 'confirmed',\n }\n }, [statusMachine, defaultStatus])\n\n const STATUS_LABELS = useMemo<Record<string, string>>(() => {\n const statuses = statusMachine?.statuses ?? [\n 'pending',\n 'confirmed',\n 'completed',\n 'cancelled',\n 'no-show',\n ]\n const labels: Record<string, string> = {}\n for (const s of statuses) {\n // Attempt to look up a translation key, e.g. reservation:statusPending\n const key = statusToI18nKey(s)\n const translated = t(key)\n // If translation returns the key itself, it's missing — fall back to capitalized status name\n labels[s] = translated !== key ? translated : s.charAt(0).toUpperCase() + s.slice(1)\n }\n return labels\n }, [statusMachine, t])\n\n const [currentDate, setCurrentDate] = useState(() => new Date())\n const [viewMode, setViewMode] = useState<ViewMode>('month')\n const [reservations, setReservations] = useState<Reservation[]>([])\n const [loading, setLoading] = useState(true)\n const [drawerDocId, setDrawerDocId] = useState<null | string>(null)\n const [initialData, setInitialData] = useState<Record<string, unknown> | undefined>(undefined)\n\n // Resource filter state\n const [resources, setResources] = useState<ResourceOption[]>([])\n const [selectedResourceId, setSelectedResourceId] = useState<string>('')\n\n // Pending tab state\n const [pendingReservations, setPendingReservations] = useState<Reservation[]>([])\n const [pendingCount, setPendingCount] = useState(0)\n const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set())\n const [confirmingIds, setConfirmingIds] = useState<Set<string>>(() => new Set())\n const [actionFeedback, setActionFeedback] = useState<{\n message: string\n type: 'error' | 'success'\n } | null>(null)\n\n const [DocumentDrawer, , { openDrawer }] = useDocumentDrawer({\n id: drawerDocId ?? undefined,\n collectionSlug: reservationSlug,\n })\n\n const pendingDrawerOpen = useRef(false)\n\n useEffect(() => {\n if (pendingDrawerOpen.current) {\n pendingDrawerOpen.current = false\n openDrawer()\n }\n })\n\n // Fetch active resources for filter dropdown\n useEffect(() => {\n const fetchResources = async () => {\n try {\n const resourceSlug = slugs?.resources ?? 'resources'\n const params = new URLSearchParams({\n depth: '0',\n limit: '100',\n sort: 'name',\n 'where[active][equals]': 'true',\n })\n const url = `${config.serverURL ?? ''}${config.routes.api}/${resourceSlug}?${params}`\n const response = await fetch(url)\n const result = await response.json()\n const docs: Array<{ id: string; name?: string }> = result.docs ?? []\n setResources(docs.map((d) => ({ id: d.id, name: d.name ?? '' })))\n } catch {\n setResources([])\n }\n }\n void fetchResources()\n }, [config.routes.api, config.serverURL, slugs?.resources])\n\n const { rangeEnd, rangeStart } = useMemo(() => {\n const start = new Date(currentDate)\n const end = new Date(currentDate)\n\n if (viewMode === 'month') {\n start.setDate(1)\n start.setDate(start.getDate() - start.getDay())\n end.setMonth(end.getMonth() + 1, 0)\n end.setDate(end.getDate() + (6 - end.getDay()))\n } else if (viewMode === 'week') {\n const dayOfWeek = start.getDay()\n start.setDate(start.getDate() - dayOfWeek)\n end.setDate(start.getDate() + 6)\n }\n start.setHours(0, 0, 0, 0)\n end.setHours(23, 59, 59, 999)\n\n return { rangeEnd: end, rangeStart: start }\n }, [currentDate, viewMode])\n\n // Availability data for the selected resource (null when no resource selected — grid unshaded)\n const { data: availability } = useResourceAvailability(\n apiBase,\n selectedResourceId || undefined,\n rangeStart,\n rangeEnd,\n )\n\n const fetchReservations = useCallback(async () => {\n setLoading(true)\n try {\n const params = new URLSearchParams({\n depth: '1',\n limit: '500',\n sort: 'startTime',\n 'where[startTime][greater_than_equal]': rangeStart.toISOString(),\n 'where[startTime][less_than_equal]': rangeEnd.toISOString(),\n })\n const response = await fetch(`${apiUrl}?${params}`)\n const result = await response.json()\n setReservations(result.docs ?? [])\n } catch {\n setReservations([])\n }\n setLoading(false)\n }, [rangeStart, rangeEnd, apiUrl])\n\n useEffect(() => {\n void fetchReservations()\n }, [fetchReservations])\n\n // Fetch pending count (always, for badge) — uses defaultStatus from config\n const fetchPendingCount = useCallback(async () => {\n try {\n const params = new URLSearchParams({\n limit: '0',\n 'where[status][equals]': defaultStatus,\n })\n const response = await fetch(`${apiUrl}?${params}`)\n const result = await response.json()\n setPendingCount(result.totalDocs ?? 0)\n } catch {\n // silently ignore\n }\n }, [apiUrl, defaultStatus])\n\n useEffect(() => {\n void fetchPendingCount()\n }, [fetchPendingCount])\n\n // Fetch pending reservations when tab is active — uses defaultStatus from config\n const fetchPendingReservations = useCallback(async () => {\n try {\n const params = new URLSearchParams({\n depth: '1',\n limit: '500',\n sort: 'startTime',\n 'where[status][equals]': defaultStatus,\n })\n const response = await fetch(`${apiUrl}?${params}`)\n const result = await response.json()\n setPendingReservations(result.docs ?? [])\n } catch {\n setPendingReservations([])\n }\n }, [apiUrl, defaultStatus])\n\n useEffect(() => {\n if (viewMode === 'pending') {\n void fetchPendingReservations()\n }\n }, [viewMode, fetchPendingReservations])\n\n // Client-side resource filtering\n const matchesResourceFilter = useCallback(\n (r: Reservation): boolean => {\n if (!selectedResourceId) {return true}\n // Check top-level resource\n const topId = typeof r.resource === 'string' ? r.resource : r.resource?.id\n if (topId === selectedResourceId) {return true}\n // Check items array for multi-resource bookings\n if (r.items && r.items.length > 0) {\n return r.items.some((item) => {\n const itemId =\n typeof item.resource === 'string' ? item.resource : item.resource?.id\n return itemId === selectedResourceId\n })\n }\n return false\n },\n [selectedResourceId],\n )\n\n const filteredReservations = useMemo(\n () => reservations.filter(matchesResourceFilter),\n [reservations, matchesResourceFilter],\n )\n\n const filteredPendingReservations = useMemo(\n () => pendingReservations.filter(matchesResourceFilter),\n [pendingReservations, matchesResourceFilter],\n )\n\n // Clear selection when leaving pending view or changing resource filter\n useEffect(() => {\n if (viewMode !== 'pending') {\n setSelectedIds(new Set())\n setActionFeedback(null)\n }\n }, [viewMode])\n\n useEffect(() => {\n setSelectedIds(new Set())\n }, [selectedResourceId])\n\n // Auto-clear feedback toast\n useEffect(() => {\n if (!actionFeedback) {return}\n const timer = setTimeout(() => setActionFeedback(null), 4000)\n return () => clearTimeout(timer)\n }, [actionFeedback])\n\n const patchReservation = useCallback(\n async (id: string, data: Record<string, unknown>): Promise<boolean> => {\n try {\n const response = await fetch(`${apiUrl}/${id}`, {\n body: JSON.stringify(data),\n headers: { 'Content-Type': 'application/json' },\n method: 'PATCH',\n })\n return response.ok\n } catch {\n return false\n }\n },\n [apiUrl],\n )\n\n // Uses confirmStatus derived from config transitions\n const handleQuickConfirm = useCallback(\n async (id: string) => {\n setConfirmingIds((prev) => new Set(prev).add(id))\n const ok = await patchReservation(id, { status: confirmStatus })\n setConfirmingIds((prev) => {\n const next = new Set(prev)\n next.delete(id)\n return next\n })\n setActionFeedback({\n type: ok ? 'success' : 'error',\n message: ok\n ? t('reservation:pendingConfirmSuccess')\n : t('reservation:pendingConfirmError'),\n })\n if (ok) {\n setSelectedIds((prev) => {\n const next = new Set(prev)\n next.delete(id)\n return next\n })\n void fetchPendingReservations()\n void fetchPendingCount()\n }\n },\n [patchReservation, fetchPendingReservations, fetchPendingCount, t, confirmStatus],\n )\n\n // Uses cancelStatus derived from config transitions\n const handleQuickCancel = useCallback(\n async (id: string) => {\n setConfirmingIds((prev) => new Set(prev).add(id))\n const ok = await patchReservation(id, { status: cancelStatus })\n setConfirmingIds((prev) => {\n const next = new Set(prev)\n next.delete(id)\n return next\n })\n setActionFeedback({\n type: ok ? 'success' : 'error',\n message: ok\n ? t('reservation:pendingCancelSuccess')\n : t('reservation:pendingCancelError'),\n })\n if (ok) {\n setSelectedIds((prev) => {\n const next = new Set(prev)\n next.delete(id)\n return next\n })\n void fetchPendingReservations()\n void fetchPendingCount()\n }\n },\n [patchReservation, fetchPendingReservations, fetchPendingCount, t, cancelStatus],\n )\n\n const confirmSelected = useCallback(async () => {\n const ids = Array.from(selectedIds)\n if (ids.length === 0) {return}\n\n setConfirmingIds((prev) => {\n const next = new Set(prev)\n for (const id of ids) {next.add(id)}\n return next\n })\n\n const results = await Promise.allSettled(\n ids.map((id) => patchReservation(id, { status: confirmStatus })),\n )\n\n setConfirmingIds((prev) => {\n const next = new Set(prev)\n for (const id of ids) {next.delete(id)}\n return next\n })\n\n const succeeded = results.filter(\n (r) => r.status === 'fulfilled' && r.value,\n ).length\n const failed = ids.length - succeeded\n\n if (failed === 0) {\n setActionFeedback({\n type: 'success',\n message: `${succeeded} ${t('reservation:pendingConfirmSuccess').toLowerCase()}`,\n })\n } else {\n setActionFeedback({\n type: failed === ids.length ? 'error' : 'success',\n message: t('reservation:pendingBulkConfirmSuccess')\n .replace('{{succeeded}}', String(succeeded))\n .replace('{{failed}}', String(failed)),\n })\n }\n\n setSelectedIds(new Set())\n void fetchPendingReservations()\n void fetchPendingCount()\n }, [selectedIds, patchReservation, fetchPendingReservations, fetchPendingCount, t, confirmStatus])\n\n const handleEventClick = useCallback((e: React.MouseEvent, id: string) => {\n e.stopPropagation()\n setDrawerDocId(id)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }, [])\n\n const handleEventKeyDown = useCallback((e: React.KeyboardEvent, id: string) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n e.stopPropagation()\n setDrawerDocId(id)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }\n }, [])\n\n const handleCreateNew = useCallback(() => {\n setDrawerDocId(null)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }, [])\n\n const handleDateClick = useCallback((date: Date) => {\n setDrawerDocId(null)\n setInitialData({ startTime: date.toISOString() })\n pendingDrawerOpen.current = true\n }, [])\n\n // Click-to-book: open new-reservation drawer pre-filled with startTime + optional resource\n const handleSlotClick = useCallback(\n (startIso: string) => {\n setDrawerDocId(null)\n setInitialData({\n ...(selectedResourceId ? { resource: selectedResourceId } : {}),\n startTime: startIso,\n })\n pendingDrawerOpen.current = true\n },\n [selectedResourceId],\n )\n\n // Lane-specific book: pre-fills both the specific resource and startTime\n const handleLaneBook = useCallback((resourceId: string, startIso: string) => {\n setDrawerDocId(null)\n setInitialData({ resource: resourceId, startTime: startIso })\n pendingDrawerOpen.current = true\n }, [])\n\n const openDocDrawer = useCallback((id: string) => {\n setDrawerDocId(id)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }, [])\n\n const navigate = useCallback(\n (direction: -1 | 1) => {\n setCurrentDate((prev) => {\n const next = new Date(prev)\n if (viewMode === 'month') {\n next.setMonth(next.getMonth() + direction)\n } else if (viewMode === 'week') {\n next.setDate(next.getDate() + 7 * direction)\n } else {\n // day, lanes: step one day at a time\n next.setDate(next.getDate() + direction)\n }\n return next\n })\n },\n [viewMode],\n )\n\n const goToToday = useCallback(() => setCurrentDate(new Date()), [])\n\n const getResName = (field: { name?: string } | string | undefined): string => {\n if (!field) {return ''}\n if (typeof field === 'string') {return ''}\n return field.name ?? ''\n }\n\n const getCustomerName = (field: Reservation['customer']): string => {\n if (!field) {return ''}\n if (typeof field === 'string') {return ''}\n const parts = [field.firstName, field.lastName].filter(Boolean)\n return parts.length > 0 ? parts.join(' ') : (field.name ?? '')\n }\n\n const getEventLabel = (r: Reservation, compact: boolean) => {\n const time = new Date(r.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })\n const serviceName = getResName(r.service)\n if (compact) {\n return `${time} ${serviceName}`.trim()\n }\n const customerName = getCustomerName(r.customer)\n const parts = [time, serviceName, customerName].filter(Boolean)\n return parts.join(' - ')\n }\n\n // Returns all resource names for a reservation — from items array if present, otherwise top-level resource\n const getResourceNames = (r: Reservation): string[] => {\n if (r.items && r.items.length > 0) {\n const names = r.items\n .map((item) => getResName(item.resource))\n .filter((name) => name.length > 0)\n if (names.length > 0) {return names}\n }\n const single = getResName(r.resource)\n return single ? [single] : []\n }\n\n const getEventTooltip = (r: Reservation): string => {\n const serviceName = getResName(r.service) || t('reservation:calendarUnknownService')\n const startStr = new Date(r.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })\n const endStr = r.endTime\n ? new Date(r.endTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n : '?'\n const customerName = getCustomerName(r.customer) || t('reservation:calendarUnknownCustomer')\n const resourceNames = getResourceNames(r)\n const resourceStr =\n resourceNames.length > 0\n ? resourceNames.join(', ')\n : t('reservation:calendarUnknownResource')\n const status = STATUS_LABELS[r.status] ?? r.status\n return [\n serviceName,\n `${startStr} - ${endStr}`,\n `${t('reservation:tooltipCustomer')} ${customerName}`,\n `${t('reservation:tooltipResource')} ${resourceStr}`,\n `${t('reservation:tooltipStatus')} ${status}`,\n ].join('\\n')\n }\n\n const renderEventItem = (r: Reservation, compact: boolean) => {\n // Use CSS class for built-in statuses; also apply inline background for custom statuses\n const cssClass = STATUS_CLASS_MAP[r.status] ?? ''\n const color = STATUS_COLORS[r.status]\n // Only apply inline style when there's no CSS class (custom statuses) or as a supplement\n const inlineStyle = cssClass ? undefined : { background: color }\n const hasItems = Array.isArray(r.items) && r.items.length > 0\n return (\n <div\n className={`${styles.eventItem} ${cssClass} ${hasItems && !compact ? styles.eventItemExpanded : ''}`}\n key={r.id}\n onClick={(e) => handleEventClick(e, r.id)}\n onKeyDown={(e) => handleEventKeyDown(e, r.id)}\n role=\"button\"\n style={inlineStyle}\n tabIndex={0}\n title={getEventTooltip(r)}\n >\n {getEventLabel(r, compact)}\n {hasItems && (\n <div className={styles.itemBadges}>\n {r.items!.map((it, i) => {\n const name = typeof it.resource === 'object' ? it.resource?.name : it.resource\n return (\n <span className={styles.itemBadge} key={i}>\n {String(name ?? '')}\n </span>\n )\n })}\n </div>\n )}\n </div>\n )\n }\n\n // Dynamic legend: iterates all statuses from the status machine config\n const renderStatusLegend = () => {\n const statuses = statusMachine?.statuses ?? Object.keys(BUILTIN_STATUS_COLORS)\n return (\n <div className={styles.statusLegend}>\n {statuses.map((key) => (\n <div className={styles.legendItem} key={key}>\n <span className={styles.legendDot} style={{ background: STATUS_COLORS[key] }} />\n {STATUS_LABELS[key] ?? key}\n </div>\n ))}\n </div>\n )\n }\n\n const renderCurrentTimeLine = (cellDate: Date, cellHour: number) => {\n const now = new Date()\n if (\n now.getFullYear() !== cellDate.getFullYear() ||\n now.getMonth() !== cellDate.getMonth() ||\n now.getDate() !== cellDate.getDate() ||\n now.getHours() !== cellHour\n ) {\n return null\n }\n const topPercent = (now.getMinutes() / 60) * 100\n return <div className={styles.currentTimeLine} style={{ top: `${topPercent}%` }} />\n }\n\n const renderMonthView = () => {\n const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)\n const startDay = new Date(firstDay)\n startDay.setDate(startDay.getDate() - startDay.getDay())\n\n const days: Date[] = []\n const d = new Date(startDay)\n for (let i = 0; i < 42; i++) {\n days.push(new Date(d))\n d.setDate(d.getDate() + 1)\n }\n\n const today = new Date()\n const todayStr = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`\n\n return (\n <div className={styles.monthGrid}>\n {[\n t('reservation:dayShortSun'),\n t('reservation:dayShortMon'),\n t('reservation:dayShortTue'),\n t('reservation:dayShortWed'),\n t('reservation:dayShortThu'),\n t('reservation:dayShortFri'),\n t('reservation:dayShortSat'),\n ].map((d) => (\n <div className={styles.dayHeader} key={d}>\n {d}\n </div>\n ))}\n {days.map((day, i) => {\n const dayStr = `${day.getFullYear()}-${day.getMonth()}-${day.getDate()}`\n const isToday = dayStr === todayStr\n const isOtherMonth = day.getMonth() !== currentDate.getMonth()\n const dayReservations = filteredReservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate()\n )\n })\n\n const clickDate = new Date(day)\n clickDate.setHours(9, 0, 0, 0)\n\n return (\n <div\n className={`${styles.dayCell} ${isOtherMonth ? styles.dayCellOtherMonth : ''} ${isToday ? styles.dayCellToday : ''}`}\n key={i}\n onClick={() => handleDateClick(clickDate)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleDateClick(clickDate)\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n <div className={styles.dayNumber}>{day.getDate()}</div>\n {dayReservations.map((r) => renderEventItem(r, true))}\n </div>\n )\n })}\n </div>\n )\n }\n\n const renderWeekView = () => {\n const startOfWeek = new Date(currentDate)\n startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay())\n startOfWeek.setHours(0, 0, 0, 0)\n\n const weekDays: Date[] = []\n for (let i = 0; i < 7; i++) {\n const d = new Date(startOfWeek)\n d.setDate(d.getDate() + i)\n weekDays.push(d)\n }\n\n const hours = Array.from({ length: 12 }, (_, i) => i + 7)\n // Grid bounds: hour 7 to hour 19 (end of last slot), step 60 min\n const gridStartHour = 7\n const gridEndHour = gridStartHour + hours.length // 19\n const gridStep = 60\n\n // Build per-day slot-state maps when a resource is selected\n const daySlotMaps = availability\n ? new Map(\n weekDays.map((day) => {\n const isoDay = localDayKey(day)\n const dayAvail = availability.days.find((d) => d.date === isoDay)\n const dayStart = new Date(day)\n dayStart.setHours(gridStartHour, 0, 0, 0)\n const dayEnd = new Date(day)\n dayEnd.setHours(gridEndHour, 0, 0, 0)\n const slots = dayAvail\n ? computeSlotStates({\n busy: availability.busy,\n capacityMode: availability.capacityMode,\n dayEnd,\n dayStart,\n quantity: availability.quantity,\n requiredPools: availability.requiredPools,\n shiftWindows: dayAvail.shiftWindows,\n step: gridStep,\n timeOff: dayAvail.timeOff,\n })\n : []\n // Index by slot start ISO for fast lookup\n const slotByStart = new Map(slots.map((s) => [s.start.toISOString(), s]))\n return [isoDay, slotByStart] as const\n }),\n )\n : null\n\n return (\n <div className={styles.weekView}>\n <div className={styles.dayHeader} />\n {weekDays.map((d, i) => (\n <div className={styles.dayHeader} key={i}>\n {d.toLocaleDateString([], { day: 'numeric', month: 'numeric', weekday: 'short' })}\n </div>\n ))}\n {hours.map((hour) => (\n <Fragment key={`row-${hour}`}>\n <div className={styles.timeLabel}>\n {hour.toString().padStart(2, '0')}:00\n </div>\n {weekDays.map((day, di) => {\n const cellReservations = filteredReservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate() &&\n rDate.getHours() === hour\n )\n })\n const clickDate = new Date(day)\n clickDate.setHours(hour, 0, 0, 0)\n\n // Slot state (only when a resource is selected)\n const isoDay = localDayKey(day)\n const slotMap = daySlotMaps?.get(isoDay)\n const slotInfo = slotMap?.get(clickDate.toISOString()) ?? null\n\n // Derive cell CSS class and interactivity based on slot state\n let slotClass = ''\n let isNonInteractive = false\n if (slotInfo) {\n if (slotInfo.state === 'off-shift') {\n slotClass = styles.slotOffShift\n isNonInteractive = true\n } else if (slotInfo.state === 'time-off') {\n slotClass = styles.slotTimeOff\n isNonInteractive = true\n } else if (slotInfo.state === 'full') {\n slotClass = styles.slotFull\n isNonInteractive = true\n } else {\n slotClass = styles.slotFree\n }\n }\n\n // Time-off label: show type/reason from dayAvail when in time-off state\n const dayAvail = availability?.days.find((d) => d.date === isoDay)\n const timeOffEntry =\n slotInfo?.state === 'time-off'\n ? dayAvail?.timeOff.find(\n (to) =>\n new Date(to.start) <= clickDate && clickDate < new Date(to.end),\n )\n : undefined\n const timeOffLabel = timeOffEntry?.type ?? timeOffEntry?.reason ?? null\n\n const handleClick = isNonInteractive\n ? undefined\n : availability\n ? () => handleSlotClick(clickDate.toISOString())\n : () => handleDateClick(clickDate)\n const handleKeyDown = isNonInteractive\n ? undefined\n : (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n if (availability) {\n handleSlotClick(clickDate.toISOString())\n } else {\n handleDateClick(clickDate)\n }\n }\n }\n\n return (\n <div\n className={`${styles.weekCell} ${slotClass}`}\n key={`cell-${hour}-${di}`}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n role=\"button\"\n tabIndex={isNonInteractive ? -1 : 0}\n >\n {renderCurrentTimeLine(day, hour)}\n {timeOffLabel && (\n <span className={styles.timeOffLabel}>{timeOffLabel}</span>\n )}\n {slotInfo && availability && availability.quantity > 1 && (\n <span className={styles.capacityBadge}>\n {slotInfo.occupancy}/{availability.quantity}\n </span>\n )}\n {cellReservations.map((r) => renderEventItem(r, false))}\n </div>\n )\n })}\n </Fragment>\n ))}\n </div>\n )\n }\n\n const renderDayView = () => {\n const hours = Array.from({ length: 14 }, (_, i) => i + 7)\n // Grid bounds: hour 7 to hour 21 (end of last slot), step 60 min\n const gridStartHour = 7\n const gridEndHour = gridStartHour + hours.length // 21\n const gridStep = 60\n\n // Build slot-state map for the current day when a resource is selected\n let daySlotMap: Map<string, SlotInfo> | null = null\n if (availability) {\n const isoDay = localDayKey(currentDate)\n const dayAvail = availability.days.find((d) => d.date === isoDay)\n const dayStart = new Date(currentDate)\n dayStart.setHours(gridStartHour, 0, 0, 0)\n const dayEnd = new Date(currentDate)\n dayEnd.setHours(gridEndHour, 0, 0, 0)\n const slots = dayAvail\n ? computeSlotStates({\n busy: availability.busy,\n capacityMode: availability.capacityMode,\n dayEnd,\n dayStart,\n quantity: availability.quantity,\n requiredPools: availability.requiredPools,\n shiftWindows: dayAvail.shiftWindows,\n step: gridStep,\n timeOff: dayAvail.timeOff,\n })\n : []\n daySlotMap = new Map(slots.map((s) => [s.start.toISOString(), s]))\n }\n\n return (\n <div className={styles.dayView}>\n {hours.map((hour) => {\n const hourReservations = filteredReservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n rDate.getFullYear() === currentDate.getFullYear() &&\n rDate.getMonth() === currentDate.getMonth() &&\n rDate.getDate() === currentDate.getDate() &&\n rDate.getHours() === hour\n )\n })\n const clickDate = new Date(currentDate)\n clickDate.setHours(hour, 0, 0, 0)\n\n // Slot state (only when a resource is selected)\n const slotInfo = daySlotMap?.get(clickDate.toISOString()) ?? null\n\n // Derive cell CSS class and interactivity based on slot state\n let slotClass = ''\n let isNonInteractive = false\n if (slotInfo) {\n if (slotInfo.state === 'off-shift') {\n slotClass = styles.slotOffShift\n isNonInteractive = true\n } else if (slotInfo.state === 'time-off') {\n slotClass = styles.slotTimeOff\n isNonInteractive = true\n } else if (slotInfo.state === 'full') {\n slotClass = styles.slotFull\n isNonInteractive = true\n } else {\n slotClass = styles.slotFree\n }\n }\n\n // Time-off label\n const isoDay = localDayKey(currentDate)\n const dayAvail = availability?.days.find((d) => d.date === isoDay)\n const timeOffEntry =\n slotInfo?.state === 'time-off'\n ? dayAvail?.timeOff.find(\n (to) => new Date(to.start) <= clickDate && clickDate < new Date(to.end),\n )\n : undefined\n const timeOffLabel = timeOffEntry?.type ?? timeOffEntry?.reason ?? null\n\n const handleClick = isNonInteractive\n ? undefined\n : availability\n ? () => handleSlotClick(clickDate.toISOString())\n : () => handleDateClick(clickDate)\n const handleKeyDown = isNonInteractive\n ? undefined\n : (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n if (availability) {\n handleSlotClick(clickDate.toISOString())\n } else {\n handleDateClick(clickDate)\n }\n }\n }\n\n return (\n <Fragment key={`row-${hour}`}>\n <div className={styles.timeLabel}>\n {hour.toString().padStart(2, '0')}:00\n </div>\n <div\n className={`${styles.dayViewCell} ${slotClass}`}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n role=\"button\"\n tabIndex={isNonInteractive ? -1 : 0}\n >\n {renderCurrentTimeLine(currentDate, hour)}\n {timeOffLabel && (\n <span className={styles.timeOffLabel}>{timeOffLabel}</span>\n )}\n {slotInfo && availability && availability.quantity > 1 && (\n <span className={styles.capacityBadge}>\n {slotInfo.occupancy}/{availability.quantity}\n </span>\n )}\n {hourReservations.map((r) => renderEventItem(r, false))}\n </div>\n </Fragment>\n )\n })}\n </div>\n )\n }\n\n const renderPendingView = () => {\n if (filteredPendingReservations.length === 0) {\n return <div className={styles.pendingEmpty}>{t('reservation:pendingEmpty')}</div>\n }\n\n const allSelected =\n filteredPendingReservations.length > 0 &&\n filteredPendingReservations.every((r) => selectedIds.has(r.id))\n\n const toggleSelectAll = () => {\n if (allSelected) {\n setSelectedIds(new Set())\n } else {\n setSelectedIds(new Set(filteredPendingReservations.map((r) => r.id)))\n }\n }\n\n const toggleSelect = (id: string) => {\n setSelectedIds((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }\n\n const formatDateTime = (iso: string) => {\n const d = new Date(iso)\n return d.toLocaleString([], {\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n month: 'short',\n year: 'numeric',\n })\n }\n\n return (\n <div className={styles.pendingView}>\n <div className={styles.pendingToolbar}>\n <label className={styles.selectAllLabel}>\n <input\n aria-label={t('reservation:pendingSelectAll')}\n checked={allSelected}\n onChange={toggleSelectAll}\n type=\"checkbox\"\n />\n {t('reservation:pendingSelectAll')}\n </label>\n {selectedIds.size > 0 && (\n <button\n className={styles.bulkConfirmButton}\n disabled={confirmingIds.size > 0}\n onClick={() => void confirmSelected()}\n type=\"button\"\n >\n {confirmingIds.size > 0\n ? t('reservation:pendingConfirming')\n : t('reservation:pendingConfirmSelected').replace(\n '{{count}}',\n String(selectedIds.size),\n )}\n </button>\n )}\n </div>\n {actionFeedback && (\n <div\n className={`${styles.feedbackToast} ${actionFeedback.type === 'success' ? styles.feedbackSuccess : styles.feedbackError}`}\n >\n {actionFeedback.message}\n </div>\n )}\n <table className={styles.pendingTable}>\n <thead>\n <tr>\n <th aria-label={t('reservation:pendingSelectAll')} className={styles.pendingTh} />\n <th className={styles.pendingTh}>{t('reservation:fieldCustomer')}</th>\n <th className={styles.pendingTh}>{t('reservation:fieldService')}</th>\n <th className={styles.pendingTh}>{t('reservation:fieldResource')}</th>\n <th className={styles.pendingTh}>{t('reservation:pendingDateTime')}</th>\n <th className={styles.pendingTh}>{t('reservation:pendingActions')}</th>\n </tr>\n </thead>\n <tbody>\n {filteredPendingReservations.map((r) => {\n const isConfirming = confirmingIds.has(r.id)\n // Show all resources from items array if present, else top-level resource\n const resourceDisplay =\n getResourceNames(r).join(', ') || t('reservation:calendarUnknownResource')\n return (\n <tr className={styles.pendingRow} key={r.id}>\n <td className={styles.pendingTd}>\n <input\n aria-label={getCustomerName(r.customer) || r.id}\n checked={selectedIds.has(r.id)}\n onChange={() => toggleSelect(r.id)}\n type=\"checkbox\"\n />\n </td>\n <td className={styles.pendingTd}>\n <span\n className={styles.pendingCustomerLink}\n onClick={() => openDocDrawer(r.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n openDocDrawer(r.id)\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n {getCustomerName(r.customer) || t('reservation:calendarUnknownCustomer')}\n </span>\n </td>\n <td className={styles.pendingTd}>\n {getResName(r.service) || t('reservation:calendarUnknownService')}\n </td>\n <td className={styles.pendingTd}>{resourceDisplay}</td>\n <td className={styles.pendingTd}>{formatDateTime(r.startTime)}</td>\n <td className={styles.pendingTd}>\n <button\n className={styles.confirmButton}\n disabled={isConfirming}\n onClick={() => void handleQuickConfirm(r.id)}\n title={t('reservation:pendingConfirm')}\n type=\"button\"\n >\n &#x2713;\n </button>\n <button\n className={styles.cancelButton}\n disabled={isConfirming}\n onClick={() => void handleQuickCancel(r.id)}\n title={t('reservation:pendingCancel')}\n type=\"button\"\n >\n &#x2717;\n </button>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )\n }\n\n const dateLabel = useMemo(() => {\n if (viewMode === 'month') {\n return currentDate.toLocaleDateString([], { month: 'long', year: 'numeric' })\n }\n if (viewMode === 'week') {\n const startOfWeek = new Date(currentDate)\n startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay())\n const endOfWeek = new Date(startOfWeek)\n endOfWeek.setDate(endOfWeek.getDate() + 6)\n return `${startOfWeek.toLocaleDateString([], { day: 'numeric', month: 'short' })} - ${endOfWeek.toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' })}`\n }\n return currentDate.toLocaleDateString([], {\n day: 'numeric',\n month: 'long',\n weekday: 'long',\n year: 'numeric',\n })\n }, [currentDate, viewMode])\n\n const handleDrawerSave = useCallback(() => {\n void fetchReservations()\n void fetchPendingCount()\n if (viewMode === 'pending') {\n void fetchPendingReservations()\n }\n }, [fetchReservations, fetchPendingCount, fetchPendingReservations, viewMode])\n\n return (\n <div className={styles.wrapper}>\n <div className={styles.header}>\n {viewMode !== 'pending' && (\n <div className={styles.navButtons}>\n <button className={styles.navButton} onClick={() => navigate(-1)} type=\"button\">\n &larr;\n </button>\n <button className={styles.navButton} onClick={goToToday} type=\"button\">\n {t('reservation:calendarToday')}\n </button>\n <button className={styles.navButton} onClick={() => navigate(1)} type=\"button\">\n &rarr;\n </button>\n <span className={styles.currentDate}>{dateLabel}</span>\n </div>\n )}\n {viewMode === 'pending' && <div />}\n <div className={styles.viewToggle}>\n <button className={styles.createButton} onClick={handleCreateNew} type=\"button\">\n {t('reservation:calendarCreateNew')}\n </button>\n {([\n { key: 'month' as ViewMode, label: t('reservation:calendarMonth') },\n { key: 'week' as ViewMode, label: t('reservation:calendarWeek') },\n { key: 'day' as ViewMode, label: t('reservation:calendarDay') },\n { key: 'lanes' as ViewMode, label: t('reservation:calendarLanes') },\n { key: 'pending' as ViewMode, label: t('reservation:calendarPending') },\n ]).map(({ key, label }) => (\n <button\n className={`${styles.viewToggleButton} ${viewMode === key ? styles.viewToggleButtonActive : ''}`}\n key={key}\n onClick={() => setViewMode(key)}\n type=\"button\"\n >\n {label}\n {key === 'pending' && pendingCount > 0 && (\n <span className={styles.pendingBadge}>\n {selectedResourceId ? filteredPendingReservations.length : pendingCount}\n </span>\n )}\n </button>\n ))}\n </div>\n </div>\n {viewMode !== 'pending' && renderStatusLegend()}\n {resources.length > 1 && (\n <div className={styles.filterBar}>\n <select\n aria-label={t('reservation:filterByResource')}\n className={styles.resourceFilter}\n onChange={(e) => setSelectedResourceId(e.target.value)}\n value={selectedResourceId}\n >\n <option value=\"\">{t('reservation:filterAllResources')}</option>\n {resources.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n </div>\n )}\n {loading && viewMode !== 'pending' && viewMode !== 'lanes' ? (\n <div className={styles.loading}>{t('reservation:calendarLoading')}</div>\n ) : (\n <>\n {viewMode === 'month' && renderMonthView()}\n {viewMode === 'week' && renderWeekView()}\n {viewMode === 'day' && renderDayView()}\n {viewMode === 'lanes' && (\n <LaneTimelineView\n apiBase={apiBase}\n day={currentDate}\n onBook={handleLaneBook}\n resources={\n selectedResourceId\n ? resources.filter((r) => r.id === selectedResourceId)\n : resources\n }\n />\n )}\n </>\n )}\n {viewMode === 'pending' && renderPendingView()}\n <DocumentDrawer initialData={initialData} onSave={handleDrawerSave} />\n </div>\n )\n}\n"],"names":["useConfig","useDocumentDrawer","useTranslation","React","Fragment","useCallback","useEffect","useMemo","useRef","useState","computeSlotStates","statusToI18nKey","localDayKey","styles","LaneTimelineView","useResourceAvailability","STATUS_CLASS_MAP","cancelled","statusCancelled","completed","statusCompleted","confirmed","statusConfirmed","statusNoShow","pending","statusPending","BUILTIN_STATUS_COLORS","CUSTOM_STATUS_PALETTE","CalendarView","config","t","_t","slugs","admin","custom","reservationSlugs","reservationSlug","reservations","apiUrl","serverURL","routes","api","apiBase","statusMachine","reservationStatusMachine","defaultStatus","STATUS_COLORS","colors","statuses","forEach","s","i","length","cancelStatus","confirmStatus","terminalStatuses","transitions","defaultTransitions","nonTerminal","find","includes","terminal","STATUS_LABELS","labels","key","translated","charAt","toUpperCase","slice","currentDate","setCurrentDate","Date","viewMode","setViewMode","setReservations","loading","setLoading","drawerDocId","setDrawerDocId","initialData","setInitialData","undefined","resources","setResources","selectedResourceId","setSelectedResourceId","pendingReservations","setPendingReservations","pendingCount","setPendingCount","selectedIds","setSelectedIds","Set","confirmingIds","setConfirmingIds","actionFeedback","setActionFeedback","DocumentDrawer","openDrawer","id","collectionSlug","pendingDrawerOpen","current","fetchResources","resourceSlug","params","URLSearchParams","depth","limit","sort","url","response","fetch","result","json","docs","map","d","name","rangeEnd","rangeStart","start","end","setDate","getDate","getDay","setMonth","getMonth","dayOfWeek","setHours","data","availability","fetchReservations","toISOString","fetchPendingCount","totalDocs","fetchPendingReservations","matchesResourceFilter","r","topId","resource","items","some","item","itemId","filteredReservations","filter","filteredPendingReservations","timer","setTimeout","clearTimeout","patchReservation","body","JSON","stringify","headers","method","ok","handleQuickConfirm","prev","add","status","next","delete","type","message","handleQuickCancel","confirmSelected","ids","Array","from","results","Promise","allSettled","succeeded","value","failed","toLowerCase","replace","String","handleEventClick","e","stopPropagation","handleEventKeyDown","preventDefault","handleCreateNew","handleDateClick","date","startTime","handleSlotClick","startIso","handleLaneBook","resourceId","openDocDrawer","navigate","direction","goToToday","getResName","field","getCustomerName","parts","firstName","lastName","Boolean","join","getEventLabel","compact","time","toLocaleTimeString","hour","minute","serviceName","service","trim","customerName","customer","getResourceNames","names","single","getEventTooltip","startStr","endStr","endTime","resourceNames","resourceStr","renderEventItem","cssClass","color","inlineStyle","background","hasItems","isArray","div","className","eventItem","eventItemExpanded","onClick","onKeyDown","role","style","tabIndex","title","itemBadges","it","span","itemBadge","renderStatusLegend","Object","keys","statusLegend","legendItem","legendDot","renderCurrentTimeLine","cellDate","cellHour","now","getFullYear","getHours","topPercent","getMinutes","currentTimeLine","top","renderMonthView","firstDay","startDay","days","push","today","todayStr","monthGrid","dayHeader","day","dayStr","isToday","isOtherMonth","dayReservations","rDate","clickDate","dayCell","dayCellOtherMonth","dayCellToday","dayNumber","renderWeekView","startOfWeek","weekDays","hours","_","gridStartHour","gridEndHour","gridStep","daySlotMaps","Map","isoDay","dayAvail","dayStart","dayEnd","slots","busy","capacityMode","quantity","requiredPools","shiftWindows","step","timeOff","slotByStart","weekView","toLocaleDateString","month","weekday","timeLabel","toString","padStart","di","cellReservations","slotMap","get","slotInfo","slotClass","isNonInteractive","state","slotOffShift","slotTimeOff","slotFull","slotFree","timeOffEntry","to","timeOffLabel","reason","handleClick","handleKeyDown","weekCell","capacityBadge","occupancy","renderDayView","daySlotMap","dayView","hourReservations","dayViewCell","renderPendingView","pendingEmpty","allSelected","every","has","toggleSelectAll","toggleSelect","formatDateTime","iso","toLocaleString","year","pendingView","pendingToolbar","label","selectAllLabel","input","aria-label","checked","onChange","size","button","bulkConfirmButton","disabled","feedbackToast","feedbackSuccess","feedbackError","table","pendingTable","thead","tr","th","pendingTh","tbody","isConfirming","resourceDisplay","pendingRow","td","pendingTd","pendingCustomerLink","confirmButton","cancelButton","dateLabel","endOfWeek","handleDrawerSave","wrapper","header","navButtons","navButton","viewToggle","createButton","viewToggleButton","viewToggleButtonActive","pendingBadge","filterBar","select","resourceFilter","target","option","onBook","onSave"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/CalendarView/index.tsx"],"sourcesContent":["'use client'\nimport type { AdminViewServerProps } from 'payload'\n\nimport { useConfig, useDocumentDrawer, useTranslation } from '@payloadcms/ui'\nimport React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { SlotInfo } from '../../utilities/computeSlotStates.js'\n\nimport { computeSlotStates } from '../../utilities/computeSlotStates.js'\nimport { statusToI18nKey } from '../../utilities/i18nUtils.js'\nimport { localDayKey } from '../../utilities/slotUtils.js'\nimport { useTenantFilter } from '../../utilities/useTenantFilter.js'\nimport styles from './CalendarView.module.css'\nimport { LaneTimelineView } from './LaneTimelineView.js'\nimport { useResourceAvailability } from './useResourceAvailability.js'\n\ntype ViewMode = 'day' | 'lanes' | 'month' | 'pending' | 'week'\n\ntype ReservationItem = {\n endTime?: string\n guestCount?: number\n resource?: { id?: string; name?: string } | string\n service?: { name?: string } | string\n startTime?: string\n}\n\ntype Reservation = {\n customer?: { firstName?: string; lastName?: string; name?: string } | string\n endTime?: string\n id: string\n items?: ReservationItem[]\n resource?: { id?: string; name?: string } | string\n service?: { name?: string } | string\n startTime: string\n status: string\n}\n\ntype ResourceOption = {\n id: string\n name: string\n}\n\n// Built-in status → CSS class map (for known statuses; custom statuses use inline style)\nconst STATUS_CLASS_MAP: Record<string, string> = {\n cancelled: styles.statusCancelled,\n completed: styles.statusCompleted,\n confirmed: styles.statusConfirmed,\n 'no-show': styles.statusNoShow,\n pending: styles.statusPending,\n}\n\n// Built-in default colors for known statuses\nconst BUILTIN_STATUS_COLORS: Record<string, string> = {\n cancelled: '#e5e7eb',\n completed: '#d1fae5',\n confirmed: '#dbeafe',\n 'no-show': '#fee2e2',\n pending: '#fef3c7',\n}\n\n// Palette for auto-assigning colors to custom statuses\nconst CUSTOM_STATUS_PALETTE = ['#fde68a', '#c7d2fe', '#a7f3d0', '#fca5a5', '#fdba74']\n\nexport const CalendarView: React.FC<AdminViewServerProps> = () => {\n const { config } = useConfig()\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n\n const slugs = config.admin?.custom?.reservationSlugs\n const reservationSlug = slugs?.reservations ?? 'reservations'\n const apiUrl = `${config.serverURL ?? ''}${config.routes.api}/${reservationSlug}`\n const apiBase = `${config.serverURL ?? ''}${config.routes.api}`\n const resourceSlug = slugs?.resources ?? 'resources'\n const reservationTenantParams = useTenantFilter(reservationSlug)\n const resourceTenantParams = useTenantFilter(resourceSlug)\n\n const statusMachine = config.admin?.custom?.reservationStatusMachine as\n | {\n blockingStatuses?: string[]\n defaultStatus?: string\n statuses?: string[]\n terminalStatuses?: string[]\n transitions?: Record<string, string[]>\n }\n | undefined\n\n // The initial/pending status (what \"pending\" view shows)\n const defaultStatus = statusMachine?.defaultStatus ?? 'pending'\n\n // Build STATUS_COLORS dynamically: built-ins first, then auto-assign palette for custom statuses\n const STATUS_COLORS = useMemo<Record<string, string>>(() => {\n const colors = { ...BUILTIN_STATUS_COLORS }\n const statuses = statusMachine?.statuses ?? []\n statuses.forEach((s, i) => {\n if (!colors[s]) {\n colors[s] = CUSTOM_STATUS_PALETTE[i % CUSTOM_STATUS_PALETTE.length]\n }\n })\n return colors\n }, [statusMachine])\n\n // Derive confirm/cancel target statuses from config transitions\n // \"confirm\" = first non-terminal transition from defaultStatus\n // \"cancel\" = first terminal transition from defaultStatus (or fallback: 'cancelled')\n const { cancelStatus, confirmStatus } = useMemo(() => {\n const terminalStatuses = statusMachine?.terminalStatuses ?? ['completed', 'cancelled', 'no-show']\n const transitions = statusMachine?.transitions ?? {}\n const defaultTransitions: string[] = transitions[defaultStatus] ?? []\n\n const nonTerminal = defaultTransitions.find((s) => !terminalStatuses.includes(s))\n const terminal = defaultTransitions.find((s) => terminalStatuses.includes(s))\n\n return {\n cancelStatus: terminal ?? 'cancelled',\n confirmStatus: nonTerminal ?? 'confirmed',\n }\n }, [statusMachine, defaultStatus])\n\n const STATUS_LABELS = useMemo<Record<string, string>>(() => {\n const statuses = statusMachine?.statuses ?? [\n 'pending',\n 'confirmed',\n 'completed',\n 'cancelled',\n 'no-show',\n ]\n const labels: Record<string, string> = {}\n for (const s of statuses) {\n // Attempt to look up a translation key, e.g. reservation:statusPending\n const key = statusToI18nKey(s)\n const translated = t(key)\n // If translation returns the key itself, it's missing — fall back to capitalized status name\n labels[s] = translated !== key ? translated : s.charAt(0).toUpperCase() + s.slice(1)\n }\n return labels\n }, [statusMachine, t])\n\n const [currentDate, setCurrentDate] = useState(() => new Date())\n const [viewMode, setViewMode] = useState<ViewMode>('month')\n const [reservations, setReservations] = useState<Reservation[]>([])\n const [loading, setLoading] = useState(true)\n const [drawerDocId, setDrawerDocId] = useState<null | string>(null)\n const [initialData, setInitialData] = useState<Record<string, unknown> | undefined>(undefined)\n\n // Resource filter state\n const [resources, setResources] = useState<ResourceOption[]>([])\n const [selectedResourceId, setSelectedResourceId] = useState<string>('')\n\n // Pending tab state\n const [pendingReservations, setPendingReservations] = useState<Reservation[]>([])\n const [pendingCount, setPendingCount] = useState(0)\n const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set())\n const [confirmingIds, setConfirmingIds] = useState<Set<string>>(() => new Set())\n const [actionFeedback, setActionFeedback] = useState<{\n message: string\n type: 'error' | 'success'\n } | null>(null)\n\n const [DocumentDrawer, , { openDrawer }] = useDocumentDrawer({\n id: drawerDocId ?? undefined,\n collectionSlug: reservationSlug,\n })\n\n const pendingDrawerOpen = useRef(false)\n\n useEffect(() => {\n if (pendingDrawerOpen.current) {\n pendingDrawerOpen.current = false\n openDrawer()\n }\n })\n\n // Fetch active resources for filter dropdown\n useEffect(() => {\n const fetchResources = async () => {\n try {\n const params = new URLSearchParams({\n depth: '0',\n limit: '100',\n sort: 'name',\n 'where[active][equals]': 'true',\n ...resourceTenantParams,\n })\n const url = `${config.serverURL ?? ''}${config.routes.api}/${resourceSlug}?${params}`\n const response = await fetch(url)\n const result = await response.json()\n const docs: Array<{ id: string; name?: string }> = result.docs ?? []\n setResources(docs.map((d) => ({ id: d.id, name: d.name ?? '' })))\n } catch {\n setResources([])\n }\n }\n void fetchResources()\n }, [config.routes.api, config.serverURL, resourceSlug, resourceTenantParams])\n\n const { rangeEnd, rangeStart } = useMemo(() => {\n const start = new Date(currentDate)\n const end = new Date(currentDate)\n\n if (viewMode === 'month') {\n start.setDate(1)\n start.setDate(start.getDate() - start.getDay())\n end.setMonth(end.getMonth() + 1, 0)\n end.setDate(end.getDate() + (6 - end.getDay()))\n } else if (viewMode === 'week') {\n const dayOfWeek = start.getDay()\n start.setDate(start.getDate() - dayOfWeek)\n end.setDate(start.getDate() + 6)\n }\n start.setHours(0, 0, 0, 0)\n end.setHours(23, 59, 59, 999)\n\n return { rangeEnd: end, rangeStart: start }\n }, [currentDate, viewMode])\n\n // Availability data for the selected resource (null when no resource selected — grid unshaded)\n const { data: availability } = useResourceAvailability(\n apiBase,\n selectedResourceId || undefined,\n rangeStart,\n rangeEnd,\n )\n\n const fetchReservations = useCallback(async () => {\n setLoading(true)\n try {\n const params = new URLSearchParams({\n depth: '1',\n limit: '500',\n sort: 'startTime',\n 'where[startTime][greater_than_equal]': rangeStart.toISOString(),\n 'where[startTime][less_than_equal]': rangeEnd.toISOString(),\n ...reservationTenantParams,\n })\n const response = await fetch(`${apiUrl}?${params}`)\n const result = await response.json()\n setReservations(result.docs ?? [])\n } catch {\n setReservations([])\n }\n setLoading(false)\n }, [rangeStart, rangeEnd, apiUrl, reservationTenantParams])\n\n useEffect(() => {\n void fetchReservations()\n }, [fetchReservations])\n\n // Fetch pending count (always, for badge) — uses defaultStatus from config\n const fetchPendingCount = useCallback(async () => {\n try {\n const params = new URLSearchParams({\n limit: '0',\n 'where[status][equals]': defaultStatus,\n ...reservationTenantParams,\n })\n const response = await fetch(`${apiUrl}?${params}`)\n const result = await response.json()\n setPendingCount(result.totalDocs ?? 0)\n } catch {\n // silently ignore\n }\n }, [apiUrl, defaultStatus, reservationTenantParams])\n\n useEffect(() => {\n void fetchPendingCount()\n }, [fetchPendingCount])\n\n // Fetch pending reservations when tab is active — uses defaultStatus from config\n const fetchPendingReservations = useCallback(async () => {\n try {\n const params = new URLSearchParams({\n depth: '1',\n limit: '500',\n sort: 'startTime',\n 'where[status][equals]': defaultStatus,\n ...reservationTenantParams,\n })\n const response = await fetch(`${apiUrl}?${params}`)\n const result = await response.json()\n setPendingReservations(result.docs ?? [])\n } catch {\n setPendingReservations([])\n }\n }, [apiUrl, defaultStatus, reservationTenantParams])\n\n useEffect(() => {\n if (viewMode === 'pending') {\n void fetchPendingReservations()\n }\n }, [viewMode, fetchPendingReservations])\n\n // Client-side resource filtering\n const matchesResourceFilter = useCallback(\n (r: Reservation): boolean => {\n if (!selectedResourceId) {return true}\n // Check top-level resource\n const topId = typeof r.resource === 'string' ? r.resource : r.resource?.id\n if (topId === selectedResourceId) {return true}\n // Check items array for multi-resource bookings\n if (r.items && r.items.length > 0) {\n return r.items.some((item) => {\n const itemId =\n typeof item.resource === 'string' ? item.resource : item.resource?.id\n return itemId === selectedResourceId\n })\n }\n return false\n },\n [selectedResourceId],\n )\n\n const filteredReservations = useMemo(\n () => reservations.filter(matchesResourceFilter),\n [reservations, matchesResourceFilter],\n )\n\n const filteredPendingReservations = useMemo(\n () => pendingReservations.filter(matchesResourceFilter),\n [pendingReservations, matchesResourceFilter],\n )\n\n // Clear selection when leaving pending view or changing resource filter\n useEffect(() => {\n if (viewMode !== 'pending') {\n setSelectedIds(new Set())\n setActionFeedback(null)\n }\n }, [viewMode])\n\n useEffect(() => {\n setSelectedIds(new Set())\n }, [selectedResourceId])\n\n // Auto-clear feedback toast\n useEffect(() => {\n if (!actionFeedback) {return}\n const timer = setTimeout(() => setActionFeedback(null), 4000)\n return () => clearTimeout(timer)\n }, [actionFeedback])\n\n const patchReservation = useCallback(\n async (id: string, data: Record<string, unknown>): Promise<boolean> => {\n try {\n const response = await fetch(`${apiUrl}/${id}`, {\n body: JSON.stringify(data),\n headers: { 'Content-Type': 'application/json' },\n method: 'PATCH',\n })\n return response.ok\n } catch {\n return false\n }\n },\n [apiUrl],\n )\n\n // Uses confirmStatus derived from config transitions\n const handleQuickConfirm = useCallback(\n async (id: string) => {\n setConfirmingIds((prev) => new Set(prev).add(id))\n const ok = await patchReservation(id, { status: confirmStatus })\n setConfirmingIds((prev) => {\n const next = new Set(prev)\n next.delete(id)\n return next\n })\n setActionFeedback({\n type: ok ? 'success' : 'error',\n message: ok\n ? t('reservation:pendingConfirmSuccess')\n : t('reservation:pendingConfirmError'),\n })\n if (ok) {\n setSelectedIds((prev) => {\n const next = new Set(prev)\n next.delete(id)\n return next\n })\n void fetchPendingReservations()\n void fetchPendingCount()\n }\n },\n [patchReservation, fetchPendingReservations, fetchPendingCount, t, confirmStatus],\n )\n\n // Uses cancelStatus derived from config transitions\n const handleQuickCancel = useCallback(\n async (id: string) => {\n setConfirmingIds((prev) => new Set(prev).add(id))\n const ok = await patchReservation(id, { status: cancelStatus })\n setConfirmingIds((prev) => {\n const next = new Set(prev)\n next.delete(id)\n return next\n })\n setActionFeedback({\n type: ok ? 'success' : 'error',\n message: ok\n ? t('reservation:pendingCancelSuccess')\n : t('reservation:pendingCancelError'),\n })\n if (ok) {\n setSelectedIds((prev) => {\n const next = new Set(prev)\n next.delete(id)\n return next\n })\n void fetchPendingReservations()\n void fetchPendingCount()\n }\n },\n [patchReservation, fetchPendingReservations, fetchPendingCount, t, cancelStatus],\n )\n\n const confirmSelected = useCallback(async () => {\n const ids = Array.from(selectedIds)\n if (ids.length === 0) {return}\n\n setConfirmingIds((prev) => {\n const next = new Set(prev)\n for (const id of ids) {next.add(id)}\n return next\n })\n\n const results = await Promise.allSettled(\n ids.map((id) => patchReservation(id, { status: confirmStatus })),\n )\n\n setConfirmingIds((prev) => {\n const next = new Set(prev)\n for (const id of ids) {next.delete(id)}\n return next\n })\n\n const succeeded = results.filter(\n (r) => r.status === 'fulfilled' && r.value,\n ).length\n const failed = ids.length - succeeded\n\n if (failed === 0) {\n setActionFeedback({\n type: 'success',\n message: `${succeeded} ${t('reservation:pendingConfirmSuccess').toLowerCase()}`,\n })\n } else {\n setActionFeedback({\n type: failed === ids.length ? 'error' : 'success',\n message: t('reservation:pendingBulkConfirmSuccess')\n .replace('{{succeeded}}', String(succeeded))\n .replace('{{failed}}', String(failed)),\n })\n }\n\n setSelectedIds(new Set())\n void fetchPendingReservations()\n void fetchPendingCount()\n }, [selectedIds, patchReservation, fetchPendingReservations, fetchPendingCount, t, confirmStatus])\n\n const handleEventClick = useCallback((e: React.MouseEvent, id: string) => {\n e.stopPropagation()\n setDrawerDocId(id)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }, [])\n\n const handleEventKeyDown = useCallback((e: React.KeyboardEvent, id: string) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n e.stopPropagation()\n setDrawerDocId(id)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }\n }, [])\n\n const handleCreateNew = useCallback(() => {\n setDrawerDocId(null)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }, [])\n\n const handleDateClick = useCallback((date: Date) => {\n setDrawerDocId(null)\n setInitialData({ startTime: date.toISOString() })\n pendingDrawerOpen.current = true\n }, [])\n\n // Click-to-book: open new-reservation drawer pre-filled with startTime + optional resource\n const handleSlotClick = useCallback(\n (startIso: string) => {\n setDrawerDocId(null)\n setInitialData({\n ...(selectedResourceId ? { resource: selectedResourceId } : {}),\n startTime: startIso,\n })\n pendingDrawerOpen.current = true\n },\n [selectedResourceId],\n )\n\n // Lane-specific book: pre-fills both the specific resource and startTime\n const handleLaneBook = useCallback((resourceId: string, startIso: string) => {\n setDrawerDocId(null)\n setInitialData({ resource: resourceId, startTime: startIso })\n pendingDrawerOpen.current = true\n }, [])\n\n const openDocDrawer = useCallback((id: string) => {\n setDrawerDocId(id)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }, [])\n\n const navigate = useCallback(\n (direction: -1 | 1) => {\n setCurrentDate((prev) => {\n const next = new Date(prev)\n if (viewMode === 'month') {\n next.setMonth(next.getMonth() + direction)\n } else if (viewMode === 'week') {\n next.setDate(next.getDate() + 7 * direction)\n } else {\n // day, lanes: step one day at a time\n next.setDate(next.getDate() + direction)\n }\n return next\n })\n },\n [viewMode],\n )\n\n const goToToday = useCallback(() => setCurrentDate(new Date()), [])\n\n const getResName = (field: { name?: string } | string | undefined): string => {\n if (!field) {return ''}\n if (typeof field === 'string') {return ''}\n return field.name ?? ''\n }\n\n const getCustomerName = (field: Reservation['customer']): string => {\n if (!field) {return ''}\n if (typeof field === 'string') {return ''}\n const parts = [field.firstName, field.lastName].filter(Boolean)\n return parts.length > 0 ? parts.join(' ') : (field.name ?? '')\n }\n\n const getEventLabel = (r: Reservation, compact: boolean) => {\n const time = new Date(r.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })\n const serviceName = getResName(r.service)\n if (compact) {\n return `${time} ${serviceName}`.trim()\n }\n const customerName = getCustomerName(r.customer)\n const parts = [time, serviceName, customerName].filter(Boolean)\n return parts.join(' - ')\n }\n\n // Returns all resource names for a reservation — from items array if present, otherwise top-level resource\n const getResourceNames = (r: Reservation): string[] => {\n if (r.items && r.items.length > 0) {\n const names = r.items\n .map((item) => getResName(item.resource))\n .filter((name) => name.length > 0)\n if (names.length > 0) {return names}\n }\n const single = getResName(r.resource)\n return single ? [single] : []\n }\n\n const getEventTooltip = (r: Reservation): string => {\n const serviceName = getResName(r.service) || t('reservation:calendarUnknownService')\n const startStr = new Date(r.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })\n const endStr = r.endTime\n ? new Date(r.endTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n : '?'\n const customerName = getCustomerName(r.customer) || t('reservation:calendarUnknownCustomer')\n const resourceNames = getResourceNames(r)\n const resourceStr =\n resourceNames.length > 0\n ? resourceNames.join(', ')\n : t('reservation:calendarUnknownResource')\n const status = STATUS_LABELS[r.status] ?? r.status\n return [\n serviceName,\n `${startStr} - ${endStr}`,\n `${t('reservation:tooltipCustomer')} ${customerName}`,\n `${t('reservation:tooltipResource')} ${resourceStr}`,\n `${t('reservation:tooltipStatus')} ${status}`,\n ].join('\\n')\n }\n\n const renderEventItem = (r: Reservation, compact: boolean) => {\n // Use CSS class for built-in statuses; also apply inline background for custom statuses\n const cssClass = STATUS_CLASS_MAP[r.status] ?? ''\n const color = STATUS_COLORS[r.status]\n // Only apply inline style when there's no CSS class (custom statuses) or as a supplement\n const inlineStyle = cssClass ? undefined : { background: color }\n const hasItems = Array.isArray(r.items) && r.items.length > 0\n return (\n <div\n className={`${styles.eventItem} ${cssClass} ${hasItems && !compact ? styles.eventItemExpanded : ''}`}\n key={r.id}\n onClick={(e) => handleEventClick(e, r.id)}\n onKeyDown={(e) => handleEventKeyDown(e, r.id)}\n role=\"button\"\n style={inlineStyle}\n tabIndex={0}\n title={getEventTooltip(r)}\n >\n {getEventLabel(r, compact)}\n {hasItems && (\n <div className={styles.itemBadges}>\n {r.items!.map((it, i) => {\n const name = typeof it.resource === 'object' ? it.resource?.name : it.resource\n return (\n <span className={styles.itemBadge} key={i}>\n {String(name ?? '')}\n </span>\n )\n })}\n </div>\n )}\n </div>\n )\n }\n\n // Dynamic legend: iterates all statuses from the status machine config\n const renderStatusLegend = () => {\n const statuses = statusMachine?.statuses ?? Object.keys(BUILTIN_STATUS_COLORS)\n return (\n <div className={styles.statusLegend}>\n {statuses.map((key) => (\n <div className={styles.legendItem} key={key}>\n <span className={styles.legendDot} style={{ background: STATUS_COLORS[key] }} />\n {STATUS_LABELS[key] ?? key}\n </div>\n ))}\n </div>\n )\n }\n\n const renderCurrentTimeLine = (cellDate: Date, cellHour: number) => {\n const now = new Date()\n if (\n now.getFullYear() !== cellDate.getFullYear() ||\n now.getMonth() !== cellDate.getMonth() ||\n now.getDate() !== cellDate.getDate() ||\n now.getHours() !== cellHour\n ) {\n return null\n }\n const topPercent = (now.getMinutes() / 60) * 100\n return <div className={styles.currentTimeLine} style={{ top: `${topPercent}%` }} />\n }\n\n const renderMonthView = () => {\n const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)\n const startDay = new Date(firstDay)\n startDay.setDate(startDay.getDate() - startDay.getDay())\n\n const days: Date[] = []\n const d = new Date(startDay)\n for (let i = 0; i < 42; i++) {\n days.push(new Date(d))\n d.setDate(d.getDate() + 1)\n }\n\n const today = new Date()\n const todayStr = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`\n\n return (\n <div className={styles.monthGrid}>\n {[\n t('reservation:dayShortSun'),\n t('reservation:dayShortMon'),\n t('reservation:dayShortTue'),\n t('reservation:dayShortWed'),\n t('reservation:dayShortThu'),\n t('reservation:dayShortFri'),\n t('reservation:dayShortSat'),\n ].map((d) => (\n <div className={styles.dayHeader} key={d}>\n {d}\n </div>\n ))}\n {days.map((day, i) => {\n const dayStr = `${day.getFullYear()}-${day.getMonth()}-${day.getDate()}`\n const isToday = dayStr === todayStr\n const isOtherMonth = day.getMonth() !== currentDate.getMonth()\n const dayReservations = filteredReservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate()\n )\n })\n\n const clickDate = new Date(day)\n clickDate.setHours(9, 0, 0, 0)\n\n return (\n <div\n className={`${styles.dayCell} ${isOtherMonth ? styles.dayCellOtherMonth : ''} ${isToday ? styles.dayCellToday : ''}`}\n key={i}\n onClick={() => handleDateClick(clickDate)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleDateClick(clickDate)\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n <div className={styles.dayNumber}>{day.getDate()}</div>\n {dayReservations.map((r) => renderEventItem(r, true))}\n </div>\n )\n })}\n </div>\n )\n }\n\n const renderWeekView = () => {\n const startOfWeek = new Date(currentDate)\n startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay())\n startOfWeek.setHours(0, 0, 0, 0)\n\n const weekDays: Date[] = []\n for (let i = 0; i < 7; i++) {\n const d = new Date(startOfWeek)\n d.setDate(d.getDate() + i)\n weekDays.push(d)\n }\n\n const hours = Array.from({ length: 12 }, (_, i) => i + 7)\n // Grid bounds: hour 7 to hour 19 (end of last slot), step 60 min\n const gridStartHour = 7\n const gridEndHour = gridStartHour + hours.length // 19\n const gridStep = 60\n\n // Build per-day slot-state maps when a resource is selected\n const daySlotMaps = availability\n ? new Map(\n weekDays.map((day) => {\n const isoDay = localDayKey(day)\n const dayAvail = availability.days.find((d) => d.date === isoDay)\n const dayStart = new Date(day)\n dayStart.setHours(gridStartHour, 0, 0, 0)\n const dayEnd = new Date(day)\n dayEnd.setHours(gridEndHour, 0, 0, 0)\n const slots = dayAvail\n ? computeSlotStates({\n busy: availability.busy,\n capacityMode: availability.capacityMode,\n dayEnd,\n dayStart,\n quantity: availability.quantity,\n requiredPools: availability.requiredPools,\n shiftWindows: dayAvail.shiftWindows,\n step: gridStep,\n timeOff: dayAvail.timeOff,\n })\n : []\n // Index by slot start ISO for fast lookup\n const slotByStart = new Map(slots.map((s) => [s.start.toISOString(), s]))\n return [isoDay, slotByStart] as const\n }),\n )\n : null\n\n return (\n <div className={styles.weekView}>\n <div className={styles.dayHeader} />\n {weekDays.map((d, i) => (\n <div className={styles.dayHeader} key={i}>\n {d.toLocaleDateString([], { day: 'numeric', month: 'numeric', weekday: 'short' })}\n </div>\n ))}\n {hours.map((hour) => (\n <Fragment key={`row-${hour}`}>\n <div className={styles.timeLabel}>\n {hour.toString().padStart(2, '0')}:00\n </div>\n {weekDays.map((day, di) => {\n const cellReservations = filteredReservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate() &&\n rDate.getHours() === hour\n )\n })\n const clickDate = new Date(day)\n clickDate.setHours(hour, 0, 0, 0)\n\n // Slot state (only when a resource is selected)\n const isoDay = localDayKey(day)\n const slotMap = daySlotMaps?.get(isoDay)\n const slotInfo = slotMap?.get(clickDate.toISOString()) ?? null\n\n // Derive cell CSS class and interactivity based on slot state\n let slotClass = ''\n let isNonInteractive = false\n if (slotInfo) {\n if (slotInfo.state === 'off-shift') {\n slotClass = styles.slotOffShift\n isNonInteractive = true\n } else if (slotInfo.state === 'time-off') {\n slotClass = styles.slotTimeOff\n isNonInteractive = true\n } else if (slotInfo.state === 'full') {\n slotClass = styles.slotFull\n isNonInteractive = true\n } else {\n slotClass = styles.slotFree\n }\n }\n\n // Time-off label: show type/reason from dayAvail when in time-off state\n const dayAvail = availability?.days.find((d) => d.date === isoDay)\n const timeOffEntry =\n slotInfo?.state === 'time-off'\n ? dayAvail?.timeOff.find(\n (to) =>\n new Date(to.start) <= clickDate && clickDate < new Date(to.end),\n )\n : undefined\n const timeOffLabel = timeOffEntry?.type ?? timeOffEntry?.reason ?? null\n\n const handleClick = isNonInteractive\n ? undefined\n : availability\n ? () => handleSlotClick(clickDate.toISOString())\n : () => handleDateClick(clickDate)\n const handleKeyDown = isNonInteractive\n ? undefined\n : (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n if (availability) {\n handleSlotClick(clickDate.toISOString())\n } else {\n handleDateClick(clickDate)\n }\n }\n }\n\n return (\n <div\n className={`${styles.weekCell} ${slotClass}`}\n key={`cell-${hour}-${di}`}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n role=\"button\"\n tabIndex={isNonInteractive ? -1 : 0}\n >\n {renderCurrentTimeLine(day, hour)}\n {timeOffLabel && (\n <span className={styles.timeOffLabel}>{timeOffLabel}</span>\n )}\n {slotInfo && availability && availability.quantity > 1 && (\n <span className={styles.capacityBadge}>\n {slotInfo.occupancy}/{availability.quantity}\n </span>\n )}\n {cellReservations.map((r) => renderEventItem(r, false))}\n </div>\n )\n })}\n </Fragment>\n ))}\n </div>\n )\n }\n\n const renderDayView = () => {\n const hours = Array.from({ length: 14 }, (_, i) => i + 7)\n // Grid bounds: hour 7 to hour 21 (end of last slot), step 60 min\n const gridStartHour = 7\n const gridEndHour = gridStartHour + hours.length // 21\n const gridStep = 60\n\n // Build slot-state map for the current day when a resource is selected\n let daySlotMap: Map<string, SlotInfo> | null = null\n if (availability) {\n const isoDay = localDayKey(currentDate)\n const dayAvail = availability.days.find((d) => d.date === isoDay)\n const dayStart = new Date(currentDate)\n dayStart.setHours(gridStartHour, 0, 0, 0)\n const dayEnd = new Date(currentDate)\n dayEnd.setHours(gridEndHour, 0, 0, 0)\n const slots = dayAvail\n ? computeSlotStates({\n busy: availability.busy,\n capacityMode: availability.capacityMode,\n dayEnd,\n dayStart,\n quantity: availability.quantity,\n requiredPools: availability.requiredPools,\n shiftWindows: dayAvail.shiftWindows,\n step: gridStep,\n timeOff: dayAvail.timeOff,\n })\n : []\n daySlotMap = new Map(slots.map((s) => [s.start.toISOString(), s]))\n }\n\n return (\n <div className={styles.dayView}>\n {hours.map((hour) => {\n const hourReservations = filteredReservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n rDate.getFullYear() === currentDate.getFullYear() &&\n rDate.getMonth() === currentDate.getMonth() &&\n rDate.getDate() === currentDate.getDate() &&\n rDate.getHours() === hour\n )\n })\n const clickDate = new Date(currentDate)\n clickDate.setHours(hour, 0, 0, 0)\n\n // Slot state (only when a resource is selected)\n const slotInfo = daySlotMap?.get(clickDate.toISOString()) ?? null\n\n // Derive cell CSS class and interactivity based on slot state\n let slotClass = ''\n let isNonInteractive = false\n if (slotInfo) {\n if (slotInfo.state === 'off-shift') {\n slotClass = styles.slotOffShift\n isNonInteractive = true\n } else if (slotInfo.state === 'time-off') {\n slotClass = styles.slotTimeOff\n isNonInteractive = true\n } else if (slotInfo.state === 'full') {\n slotClass = styles.slotFull\n isNonInteractive = true\n } else {\n slotClass = styles.slotFree\n }\n }\n\n // Time-off label\n const isoDay = localDayKey(currentDate)\n const dayAvail = availability?.days.find((d) => d.date === isoDay)\n const timeOffEntry =\n slotInfo?.state === 'time-off'\n ? dayAvail?.timeOff.find(\n (to) => new Date(to.start) <= clickDate && clickDate < new Date(to.end),\n )\n : undefined\n const timeOffLabel = timeOffEntry?.type ?? timeOffEntry?.reason ?? null\n\n const handleClick = isNonInteractive\n ? undefined\n : availability\n ? () => handleSlotClick(clickDate.toISOString())\n : () => handleDateClick(clickDate)\n const handleKeyDown = isNonInteractive\n ? undefined\n : (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n if (availability) {\n handleSlotClick(clickDate.toISOString())\n } else {\n handleDateClick(clickDate)\n }\n }\n }\n\n return (\n <Fragment key={`row-${hour}`}>\n <div className={styles.timeLabel}>\n {hour.toString().padStart(2, '0')}:00\n </div>\n <div\n className={`${styles.dayViewCell} ${slotClass}`}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n role=\"button\"\n tabIndex={isNonInteractive ? -1 : 0}\n >\n {renderCurrentTimeLine(currentDate, hour)}\n {timeOffLabel && (\n <span className={styles.timeOffLabel}>{timeOffLabel}</span>\n )}\n {slotInfo && availability && availability.quantity > 1 && (\n <span className={styles.capacityBadge}>\n {slotInfo.occupancy}/{availability.quantity}\n </span>\n )}\n {hourReservations.map((r) => renderEventItem(r, false))}\n </div>\n </Fragment>\n )\n })}\n </div>\n )\n }\n\n const renderPendingView = () => {\n if (filteredPendingReservations.length === 0) {\n return <div className={styles.pendingEmpty}>{t('reservation:pendingEmpty')}</div>\n }\n\n const allSelected =\n filteredPendingReservations.length > 0 &&\n filteredPendingReservations.every((r) => selectedIds.has(r.id))\n\n const toggleSelectAll = () => {\n if (allSelected) {\n setSelectedIds(new Set())\n } else {\n setSelectedIds(new Set(filteredPendingReservations.map((r) => r.id)))\n }\n }\n\n const toggleSelect = (id: string) => {\n setSelectedIds((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }\n\n const formatDateTime = (iso: string) => {\n const d = new Date(iso)\n return d.toLocaleString([], {\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n month: 'short',\n year: 'numeric',\n })\n }\n\n return (\n <div className={styles.pendingView}>\n <div className={styles.pendingToolbar}>\n <label className={styles.selectAllLabel}>\n <input\n aria-label={t('reservation:pendingSelectAll')}\n checked={allSelected}\n onChange={toggleSelectAll}\n type=\"checkbox\"\n />\n {t('reservation:pendingSelectAll')}\n </label>\n {selectedIds.size > 0 && (\n <button\n className={styles.bulkConfirmButton}\n disabled={confirmingIds.size > 0}\n onClick={() => void confirmSelected()}\n type=\"button\"\n >\n {confirmingIds.size > 0\n ? t('reservation:pendingConfirming')\n : t('reservation:pendingConfirmSelected').replace(\n '{{count}}',\n String(selectedIds.size),\n )}\n </button>\n )}\n </div>\n {actionFeedback && (\n <div\n className={`${styles.feedbackToast} ${actionFeedback.type === 'success' ? styles.feedbackSuccess : styles.feedbackError}`}\n >\n {actionFeedback.message}\n </div>\n )}\n <table className={styles.pendingTable}>\n <thead>\n <tr>\n <th aria-label={t('reservation:pendingSelectAll')} className={styles.pendingTh} />\n <th className={styles.pendingTh}>{t('reservation:fieldCustomer')}</th>\n <th className={styles.pendingTh}>{t('reservation:fieldService')}</th>\n <th className={styles.pendingTh}>{t('reservation:fieldResource')}</th>\n <th className={styles.pendingTh}>{t('reservation:pendingDateTime')}</th>\n <th className={styles.pendingTh}>{t('reservation:pendingActions')}</th>\n </tr>\n </thead>\n <tbody>\n {filteredPendingReservations.map((r) => {\n const isConfirming = confirmingIds.has(r.id)\n // Show all resources from items array if present, else top-level resource\n const resourceDisplay =\n getResourceNames(r).join(', ') || t('reservation:calendarUnknownResource')\n return (\n <tr className={styles.pendingRow} key={r.id}>\n <td className={styles.pendingTd}>\n <input\n aria-label={getCustomerName(r.customer) || r.id}\n checked={selectedIds.has(r.id)}\n onChange={() => toggleSelect(r.id)}\n type=\"checkbox\"\n />\n </td>\n <td className={styles.pendingTd}>\n <span\n className={styles.pendingCustomerLink}\n onClick={() => openDocDrawer(r.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n openDocDrawer(r.id)\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n {getCustomerName(r.customer) || t('reservation:calendarUnknownCustomer')}\n </span>\n </td>\n <td className={styles.pendingTd}>\n {getResName(r.service) || t('reservation:calendarUnknownService')}\n </td>\n <td className={styles.pendingTd}>{resourceDisplay}</td>\n <td className={styles.pendingTd}>{formatDateTime(r.startTime)}</td>\n <td className={styles.pendingTd}>\n <button\n className={styles.confirmButton}\n disabled={isConfirming}\n onClick={() => void handleQuickConfirm(r.id)}\n title={t('reservation:pendingConfirm')}\n type=\"button\"\n >\n &#x2713;\n </button>\n <button\n className={styles.cancelButton}\n disabled={isConfirming}\n onClick={() => void handleQuickCancel(r.id)}\n title={t('reservation:pendingCancel')}\n type=\"button\"\n >\n &#x2717;\n </button>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )\n }\n\n const dateLabel = useMemo(() => {\n if (viewMode === 'month') {\n return currentDate.toLocaleDateString([], { month: 'long', year: 'numeric' })\n }\n if (viewMode === 'week') {\n const startOfWeek = new Date(currentDate)\n startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay())\n const endOfWeek = new Date(startOfWeek)\n endOfWeek.setDate(endOfWeek.getDate() + 6)\n return `${startOfWeek.toLocaleDateString([], { day: 'numeric', month: 'short' })} - ${endOfWeek.toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' })}`\n }\n return currentDate.toLocaleDateString([], {\n day: 'numeric',\n month: 'long',\n weekday: 'long',\n year: 'numeric',\n })\n }, [currentDate, viewMode])\n\n const handleDrawerSave = useCallback(() => {\n void fetchReservations()\n void fetchPendingCount()\n if (viewMode === 'pending') {\n void fetchPendingReservations()\n }\n }, [fetchReservations, fetchPendingCount, fetchPendingReservations, viewMode])\n\n return (\n <div className={styles.wrapper}>\n <div className={styles.header}>\n {viewMode !== 'pending' && (\n <div className={styles.navButtons}>\n <button className={styles.navButton} onClick={() => navigate(-1)} type=\"button\">\n &larr;\n </button>\n <button className={styles.navButton} onClick={goToToday} type=\"button\">\n {t('reservation:calendarToday')}\n </button>\n <button className={styles.navButton} onClick={() => navigate(1)} type=\"button\">\n &rarr;\n </button>\n <span className={styles.currentDate}>{dateLabel}</span>\n </div>\n )}\n {viewMode === 'pending' && <div />}\n <div className={styles.viewToggle}>\n <button className={styles.createButton} onClick={handleCreateNew} type=\"button\">\n {t('reservation:calendarCreateNew')}\n </button>\n {([\n { key: 'month' as ViewMode, label: t('reservation:calendarMonth') },\n { key: 'week' as ViewMode, label: t('reservation:calendarWeek') },\n { key: 'day' as ViewMode, label: t('reservation:calendarDay') },\n { key: 'lanes' as ViewMode, label: t('reservation:calendarLanes') },\n { key: 'pending' as ViewMode, label: t('reservation:calendarPending') },\n ]).map(({ key, label }) => (\n <button\n className={`${styles.viewToggleButton} ${viewMode === key ? styles.viewToggleButtonActive : ''}`}\n key={key}\n onClick={() => setViewMode(key)}\n type=\"button\"\n >\n {label}\n {key === 'pending' && pendingCount > 0 && (\n <span className={styles.pendingBadge}>\n {selectedResourceId ? filteredPendingReservations.length : pendingCount}\n </span>\n )}\n </button>\n ))}\n </div>\n </div>\n {viewMode !== 'pending' && renderStatusLegend()}\n {resources.length > 1 && (\n <div className={styles.filterBar}>\n <select\n aria-label={t('reservation:filterByResource')}\n className={styles.resourceFilter}\n onChange={(e) => setSelectedResourceId(e.target.value)}\n value={selectedResourceId}\n >\n <option value=\"\">{t('reservation:filterAllResources')}</option>\n {resources.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n </div>\n )}\n {loading && viewMode !== 'pending' && viewMode !== 'lanes' ? (\n <div className={styles.loading}>{t('reservation:calendarLoading')}</div>\n ) : (\n <>\n {viewMode === 'month' && renderMonthView()}\n {viewMode === 'week' && renderWeekView()}\n {viewMode === 'day' && renderDayView()}\n {viewMode === 'lanes' && (\n <LaneTimelineView\n apiBase={apiBase}\n day={currentDate}\n onBook={handleLaneBook}\n resources={\n selectedResourceId\n ? resources.filter((r) => r.id === selectedResourceId)\n : resources\n }\n />\n )}\n </>\n )}\n {viewMode === 'pending' && renderPendingView()}\n <DocumentDrawer initialData={initialData} onSave={handleDrawerSave} />\n </div>\n )\n}\n"],"names":["useConfig","useDocumentDrawer","useTranslation","React","Fragment","useCallback","useEffect","useMemo","useRef","useState","computeSlotStates","statusToI18nKey","localDayKey","useTenantFilter","styles","LaneTimelineView","useResourceAvailability","STATUS_CLASS_MAP","cancelled","statusCancelled","completed","statusCompleted","confirmed","statusConfirmed","statusNoShow","pending","statusPending","BUILTIN_STATUS_COLORS","CUSTOM_STATUS_PALETTE","CalendarView","config","t","_t","slugs","admin","custom","reservationSlugs","reservationSlug","reservations","apiUrl","serverURL","routes","api","apiBase","resourceSlug","resources","reservationTenantParams","resourceTenantParams","statusMachine","reservationStatusMachine","defaultStatus","STATUS_COLORS","colors","statuses","forEach","s","i","length","cancelStatus","confirmStatus","terminalStatuses","transitions","defaultTransitions","nonTerminal","find","includes","terminal","STATUS_LABELS","labels","key","translated","charAt","toUpperCase","slice","currentDate","setCurrentDate","Date","viewMode","setViewMode","setReservations","loading","setLoading","drawerDocId","setDrawerDocId","initialData","setInitialData","undefined","setResources","selectedResourceId","setSelectedResourceId","pendingReservations","setPendingReservations","pendingCount","setPendingCount","selectedIds","setSelectedIds","Set","confirmingIds","setConfirmingIds","actionFeedback","setActionFeedback","DocumentDrawer","openDrawer","id","collectionSlug","pendingDrawerOpen","current","fetchResources","params","URLSearchParams","depth","limit","sort","url","response","fetch","result","json","docs","map","d","name","rangeEnd","rangeStart","start","end","setDate","getDate","getDay","setMonth","getMonth","dayOfWeek","setHours","data","availability","fetchReservations","toISOString","fetchPendingCount","totalDocs","fetchPendingReservations","matchesResourceFilter","r","topId","resource","items","some","item","itemId","filteredReservations","filter","filteredPendingReservations","timer","setTimeout","clearTimeout","patchReservation","body","JSON","stringify","headers","method","ok","handleQuickConfirm","prev","add","status","next","delete","type","message","handleQuickCancel","confirmSelected","ids","Array","from","results","Promise","allSettled","succeeded","value","failed","toLowerCase","replace","String","handleEventClick","e","stopPropagation","handleEventKeyDown","preventDefault","handleCreateNew","handleDateClick","date","startTime","handleSlotClick","startIso","handleLaneBook","resourceId","openDocDrawer","navigate","direction","goToToday","getResName","field","getCustomerName","parts","firstName","lastName","Boolean","join","getEventLabel","compact","time","toLocaleTimeString","hour","minute","serviceName","service","trim","customerName","customer","getResourceNames","names","single","getEventTooltip","startStr","endStr","endTime","resourceNames","resourceStr","renderEventItem","cssClass","color","inlineStyle","background","hasItems","isArray","div","className","eventItem","eventItemExpanded","onClick","onKeyDown","role","style","tabIndex","title","itemBadges","it","span","itemBadge","renderStatusLegend","Object","keys","statusLegend","legendItem","legendDot","renderCurrentTimeLine","cellDate","cellHour","now","getFullYear","getHours","topPercent","getMinutes","currentTimeLine","top","renderMonthView","firstDay","startDay","days","push","today","todayStr","monthGrid","dayHeader","day","dayStr","isToday","isOtherMonth","dayReservations","rDate","clickDate","dayCell","dayCellOtherMonth","dayCellToday","dayNumber","renderWeekView","startOfWeek","weekDays","hours","_","gridStartHour","gridEndHour","gridStep","daySlotMaps","Map","isoDay","dayAvail","dayStart","dayEnd","slots","busy","capacityMode","quantity","requiredPools","shiftWindows","step","timeOff","slotByStart","weekView","toLocaleDateString","month","weekday","timeLabel","toString","padStart","di","cellReservations","slotMap","get","slotInfo","slotClass","isNonInteractive","state","slotOffShift","slotTimeOff","slotFull","slotFree","timeOffEntry","to","timeOffLabel","reason","handleClick","handleKeyDown","weekCell","capacityBadge","occupancy","renderDayView","daySlotMap","dayView","hourReservations","dayViewCell","renderPendingView","pendingEmpty","allSelected","every","has","toggleSelectAll","toggleSelect","formatDateTime","iso","toLocaleString","year","pendingView","pendingToolbar","label","selectAllLabel","input","aria-label","checked","onChange","size","button","bulkConfirmButton","disabled","feedbackToast","feedbackSuccess","feedbackError","table","pendingTable","thead","tr","th","pendingTh","tbody","isConfirming","resourceDisplay","pendingRow","td","pendingTd","pendingCustomerLink","confirmButton","cancelButton","dateLabel","endOfWeek","handleDrawerSave","wrapper","header","navButtons","navButton","viewToggle","createButton","viewToggleButton","viewToggleButtonActive","pendingBadge","filterBar","select","resourceFilter","target","option","onBook","onSave"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { collectionHasTenantField, readCookie, tenantWhereClause } from '../../utilities/tenantFilter.js';
2
3
  import styles from './DashboardWidget.module.css';
3
4
  export const DashboardWidgetServer = async (props)=>{
4
5
  const { req } = props;
@@ -8,6 +9,15 @@ export const DashboardWidgetServer = async (props)=>{
8
9
  if (!slugs) {
9
10
  return null;
10
11
  }
12
+ const tenantConfig = payload.config.admin?.custom?.reservationTenant ?? {};
13
+ const cookieName = tenantConfig.cookieName ?? 'payload-tenant';
14
+ const tenantField = tenantConfig.tenantField ?? 'tenant';
15
+ const reservationsCollection = payload.config.collections?.find((c)=>c.slug === slugs.reservations);
16
+ const tenantWhere = tenantWhereClause({
17
+ hasField: collectionHasTenantField(reservationsCollection, tenantField),
18
+ tenantField,
19
+ tenantId: readCookie(req.headers.get('cookie'), cookieName)
20
+ });
11
21
  // Read status machine from config — never hardcode status values
12
22
  const statusMachine = payload.config.admin?.custom?.reservationStatusMachine;
13
23
  const blockingStatuses = statusMachine?.blockingStatuses ?? [];
@@ -17,16 +27,20 @@ export const DashboardWidgetServer = async (props)=>{
17
27
  const now = new Date();
18
28
  const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
19
29
  const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
30
+ const where = {
31
+ startTime: {
32
+ greater_than_equal: startOfDay.toISOString(),
33
+ less_than: endOfDay.toISOString()
34
+ }
35
+ };
36
+ if (tenantWhere) {
37
+ Object.assign(where, tenantWhere);
38
+ }
20
39
  const { docs: todayReservations } = await payload.find({
21
40
  collection: slugs.reservations,
22
41
  limit: 100,
23
42
  sort: 'startTime',
24
- where: {
25
- startTime: {
26
- greater_than_equal: startOfDay.toISOString(),
27
- less_than: endOfDay.toISOString()
28
- }
29
- }
43
+ where
30
44
  });
31
45
  const total = todayReservations.length;
32
46
  // Active = reservations in blockingStatuses (they hold a slot, past or future)
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/DashboardWidget/DashboardWidgetServer.tsx"],"sourcesContent":["import type { WidgetServerProps } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { StatusMachineConfig } from '../../types.js'\n\nimport styles from './DashboardWidget.module.css'\n\nexport const DashboardWidgetServer = async (props: WidgetServerProps) => {\n const { req } = props\n const { i18n, payload } = req\n const t = i18n.t as PluginT\n\n const slugs = payload.config.admin?.custom?.reservationSlugs\n if (!slugs) {\n return null\n }\n\n // Read status machine from config — never hardcode status values\n const statusMachine: StatusMachineConfig | undefined =\n payload.config.admin?.custom?.reservationStatusMachine\n const blockingStatuses: string[] = statusMachine?.blockingStatuses ?? []\n const terminalStatuses: string[] = statusMachine?.terminalStatuses ?? []\n const blockingSet = new Set(blockingStatuses)\n const terminalSet = new Set(terminalStatuses)\n\n const now = new Date()\n const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)\n\n const { docs: todayReservations } = await payload.find({\n collection: slugs.reservations,\n limit: 100,\n sort: 'startTime',\n where: {\n startTime: {\n greater_than_equal: startOfDay.toISOString(),\n less_than: endOfDay.toISOString(),\n },\n },\n })\n\n const total = todayReservations.length\n\n // Active = reservations in blockingStatuses (they hold a slot, past or future)\n const active = todayReservations.filter((r: Record<string, unknown>) =>\n blockingSet.has(r.status as string),\n ).length\n\n // Upcoming = active (blocking) reservations that haven't started yet\n const upcoming = todayReservations.filter(\n (r: Record<string, unknown>) =>\n blockingSet.has(r.status as string) && new Date(r.startTime as string) > now,\n ).length\n\n // Terminal = reservations in terminalStatuses (completed, cancelled, no-show, etc.)\n const terminal = todayReservations.filter((r: Record<string, unknown>) =>\n terminalSet.has(r.status as string),\n ).length\n\n // Next appointment = the earliest upcoming blocking reservation\n const nextAppointment = todayReservations.find(\n (r: Record<string, unknown>) =>\n blockingSet.has(r.status as string) && new Date(r.startTime as string) > now,\n )\n\n return (\n <div className={styles.wrapper}>\n <h3 className={styles.title}>{t('reservation:dashboardTitle')}</h3>\n <div className={styles.statsGrid}>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{total}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardTotal')}</span>\n </div>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{active}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardActive')}</span>\n </div>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{upcoming}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardUpcoming')}</span>\n </div>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{terminal}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardTerminal')}</span>\n </div>\n </div>\n {nextAppointment ? (\n <div className={styles.nextAppointment}>\n <strong>{t('reservation:dashboardNextAppointment')}</strong>\n <p>\n {t('reservation:dashboardTime')}{' '}\n {new Date(nextAppointment.startTime as string).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}\n </p>\n <p>\n {t('reservation:dashboardStatus')} {nextAppointment.status as string}\n </p>\n </div>\n ) : (\n <p className={styles.noData}>{t('reservation:dashboardNoUpcoming')}</p>\n )}\n </div>\n )\n}\n"],"names":["styles","DashboardWidgetServer","props","req","i18n","payload","t","slugs","config","admin","custom","reservationSlugs","statusMachine","reservationStatusMachine","blockingStatuses","terminalStatuses","blockingSet","Set","terminalSet","now","Date","startOfDay","getFullYear","getMonth","getDate","endOfDay","docs","todayReservations","find","collection","reservations","limit","sort","where","startTime","greater_than_equal","toISOString","less_than","total","length","active","filter","r","has","status","upcoming","terminal","nextAppointment","div","className","wrapper","h3","title","statsGrid","statCard","span","statValue","statLabel","strong","p","toLocaleTimeString","hour","minute","noData"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/DashboardWidget/DashboardWidgetServer.tsx"],"sourcesContent":["import type { WidgetServerProps } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { StatusMachineConfig } from '../../types.js'\n\nimport { collectionHasTenantField, readCookie, tenantWhereClause } from '../../utilities/tenantFilter.js'\nimport styles from './DashboardWidget.module.css'\n\nexport const DashboardWidgetServer = async (props: WidgetServerProps) => {\n const { req } = props\n const { i18n, payload } = req\n const t = i18n.t as PluginT\n\n const slugs = payload.config.admin?.custom?.reservationSlugs\n if (!slugs) {\n return null\n }\n\n const tenantConfig =\n (payload.config.admin?.custom?.reservationTenant as\n | { cookieName?: string; tenantField?: string }\n | undefined) ?? {}\n const cookieName = tenantConfig.cookieName ?? 'payload-tenant'\n const tenantField = tenantConfig.tenantField ?? 'tenant'\n const reservationsCollection = payload.config.collections?.find((c) => c.slug === slugs.reservations)\n const tenantWhere = tenantWhereClause({\n hasField: collectionHasTenantField(reservationsCollection as { fields?: unknown[] } | undefined, tenantField),\n tenantField,\n tenantId: readCookie(req.headers.get('cookie'), cookieName),\n })\n\n // Read status machine from config — never hardcode status values\n const statusMachine: StatusMachineConfig | undefined =\n payload.config.admin?.custom?.reservationStatusMachine\n const blockingStatuses: string[] = statusMachine?.blockingStatuses ?? []\n const terminalStatuses: string[] = statusMachine?.terminalStatuses ?? []\n const blockingSet = new Set(blockingStatuses)\n const terminalSet = new Set(terminalStatuses)\n\n const now = new Date()\n const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)\n\n const where: Parameters<typeof payload.find>[0]['where'] = {\n startTime: {\n greater_than_equal: startOfDay.toISOString(),\n less_than: endOfDay.toISOString(),\n },\n }\n if (tenantWhere) {\n Object.assign(where, tenantWhere)\n }\n\n const { docs: todayReservations } = await payload.find({\n collection: slugs.reservations,\n limit: 100,\n sort: 'startTime',\n where,\n })\n\n const total = todayReservations.length\n\n // Active = reservations in blockingStatuses (they hold a slot, past or future)\n const active = todayReservations.filter((r: Record<string, unknown>) =>\n blockingSet.has(r.status as string),\n ).length\n\n // Upcoming = active (blocking) reservations that haven't started yet\n const upcoming = todayReservations.filter(\n (r: Record<string, unknown>) =>\n blockingSet.has(r.status as string) && new Date(r.startTime as string) > now,\n ).length\n\n // Terminal = reservations in terminalStatuses (completed, cancelled, no-show, etc.)\n const terminal = todayReservations.filter((r: Record<string, unknown>) =>\n terminalSet.has(r.status as string),\n ).length\n\n // Next appointment = the earliest upcoming blocking reservation\n const nextAppointment = todayReservations.find(\n (r: Record<string, unknown>) =>\n blockingSet.has(r.status as string) && new Date(r.startTime as string) > now,\n )\n\n return (\n <div className={styles.wrapper}>\n <h3 className={styles.title}>{t('reservation:dashboardTitle')}</h3>\n <div className={styles.statsGrid}>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{total}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardTotal')}</span>\n </div>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{active}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardActive')}</span>\n </div>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{upcoming}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardUpcoming')}</span>\n </div>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{terminal}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardTerminal')}</span>\n </div>\n </div>\n {nextAppointment ? (\n <div className={styles.nextAppointment}>\n <strong>{t('reservation:dashboardNextAppointment')}</strong>\n <p>\n {t('reservation:dashboardTime')}{' '}\n {new Date(nextAppointment.startTime as string).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}\n </p>\n <p>\n {t('reservation:dashboardStatus')} {nextAppointment.status as string}\n </p>\n </div>\n ) : (\n <p className={styles.noData}>{t('reservation:dashboardNoUpcoming')}</p>\n )}\n </div>\n )\n}\n"],"names":["collectionHasTenantField","readCookie","tenantWhereClause","styles","DashboardWidgetServer","props","req","i18n","payload","t","slugs","config","admin","custom","reservationSlugs","tenantConfig","reservationTenant","cookieName","tenantField","reservationsCollection","collections","find","c","slug","reservations","tenantWhere","hasField","tenantId","headers","get","statusMachine","reservationStatusMachine","blockingStatuses","terminalStatuses","blockingSet","Set","terminalSet","now","Date","startOfDay","getFullYear","getMonth","getDate","endOfDay","where","startTime","greater_than_equal","toISOString","less_than","Object","assign","docs","todayReservations","collection","limit","sort","total","length","active","filter","r","has","status","upcoming","terminal","nextAppointment","div","className","wrapper","h3","title","statsGrid","statCard","span","statValue","statLabel","strong","p","toLocaleTimeString","hour","minute","noData"],"mappings":"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"}
package/dist/defaults.js CHANGED
@@ -97,6 +97,10 @@ export function resolveConfig(pluginOptions) {
97
97
  hooks: pluginOptions.hooks ?? {},
98
98
  leaveTypes: pluginOptions.leaveTypes ?? DEFAULT_LEAVE_TYPES,
99
99
  localized: false,
100
+ multiTenant: {
101
+ cookieName: pluginOptions.multiTenant?.cookieName ?? 'payload-tenant',
102
+ tenantField: pluginOptions.multiTenant?.tenantField ?? 'tenant'
103
+ },
100
104
  resourceOwnerMode: rom ? {
101
105
  adminRoles: rom.adminRoles ?? [],
102
106
  ownedServices: rom.ownedServices ?? false,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["import type {\n ReservationPluginConfig,\n ResolvedReservationPluginConfig,\n ResolvedStaffProvisioningConfig,\n StatusMachineConfig,\n} from './types.js'\n\nimport { DEFAULT_STATUS_MACHINE } from './types.js'\n\nfunction validateStatusMachine(sm: StatusMachineConfig): void {\n if (!sm.statuses.includes(sm.defaultStatus)) {\n throw new Error(`statusMachine.defaultStatus \"${sm.defaultStatus}\" is not in statuses array`)\n }\n for (const s of sm.blockingStatuses) {\n if (!sm.statuses.includes(s)) {\n throw new Error(`statusMachine.blockingStatuses contains \"${s}\" which is not in statuses array`)\n }\n }\n for (const s of sm.terminalStatuses) {\n if (!sm.statuses.includes(s)) {\n throw new Error(`statusMachine.terminalStatuses contains \"${s}\" which is not in statuses array`)\n }\n }\n for (const [from, targets] of Object.entries(sm.transitions)) {\n if (!sm.statuses.includes(from)) {\n throw new Error(`statusMachine.transitions has key \"${from}\" which is not in statuses array`)\n }\n for (const to of targets) {\n if (!sm.statuses.includes(to)) {\n throw new Error(`statusMachine.transitions[\"${from}\"] targets \"${to}\" which is not in statuses array`)\n }\n }\n }\n}\n\nexport const DEFAULT_RESOURCE_TYPES = ['staff', 'equipment', 'room']\nexport const DEFAULT_LEAVE_TYPES = ['vacation', 'sick', 'personal', 'closure', 'other']\n\nfunction resolveStaffProvisioning(\n pluginOptions: ReservationPluginConfig,\n resourceTypes: string[],\n): ResolvedStaffProvisioningConfig | undefined {\n const sp = pluginOptions.staffProvisioning\n if (!sp) {\n return undefined\n }\n\n if (!pluginOptions.resourceOwnerMode) {\n throw new Error('staffProvisioning requires resourceOwnerMode to be enabled')\n }\n if (sp.staffRoles.length === 0) {\n throw new Error('staffProvisioning.staffRoles must be a non-empty array')\n }\n const resourceType = sp.resourceType ?? 'staff'\n if (!resourceTypes.includes(resourceType)) {\n throw new Error(\n `staffProvisioning.resourceType \"${resourceType}\" is not in resourceTypes [${resourceTypes.join(', ')}]`,\n )\n }\n const userCollection = sp.userCollection ?? pluginOptions.userCollection\n if (!userCollection) {\n throw new Error(\n 'staffProvisioning.userCollection is required when top-level userCollection is unset',\n )\n }\n\n return {\n beforeCreate: sp.beforeCreate,\n nameFrom: sp.nameFrom ?? 'name',\n resourceType,\n roleField: sp.roleField ?? 'role',\n staffRoles: sp.staffRoles,\n userCollection,\n }\n}\n\nexport const DEFAULT_SLUGS = {\n customers: 'customers',\n media: 'media',\n reservations: 'reservations',\n resources: 'resources',\n schedules: 'schedules',\n services: 'services',\n} as const\n\nexport const DEFAULT_ADMIN_GROUP = 'Reservations'\nexport const DEFAULT_ALLOW_GUEST_BOOKING = false\nexport const DEFAULT_BUFFER_TIME = 0\nexport const DEFAULT_CANCELLATION_NOTICE_PERIOD = 24\n\nexport function resolveConfig(\n pluginOptions: ReservationPluginConfig,\n): ResolvedReservationPluginConfig {\n if (pluginOptions.resourceTypes !== undefined && pluginOptions.resourceTypes.length === 0) {\n throw new Error('resourceTypes must be a non-empty array')\n }\n if (pluginOptions.leaveTypes !== undefined && pluginOptions.leaveTypes.length === 0) {\n throw new Error('leaveTypes must be a non-empty array')\n }\n\n const resourceTypes = pluginOptions.resourceTypes ?? DEFAULT_RESOURCE_TYPES\n const userStatusMachine = pluginOptions.statusMachine\n const rom = pluginOptions.resourceOwnerMode\n const resolved: ResolvedReservationPluginConfig = {\n access: pluginOptions.access ?? {},\n adminGroup: pluginOptions.adminGroup ?? DEFAULT_ADMIN_GROUP,\n allowGuestBooking: pluginOptions.allowGuestBooking ?? DEFAULT_ALLOW_GUEST_BOOKING,\n cancellationNoticePeriod:\n pluginOptions.cancellationNoticePeriod ?? DEFAULT_CANCELLATION_NOTICE_PERIOD,\n defaultBufferTime: pluginOptions.defaultBufferTime ?? DEFAULT_BUFFER_TIME,\n disabled: pluginOptions.disabled ?? false,\n extraReservationFields: pluginOptions.extraReservationFields ?? [],\n hooks: pluginOptions.hooks ?? {},\n leaveTypes: pluginOptions.leaveTypes ?? DEFAULT_LEAVE_TYPES,\n localized: false,\n resourceOwnerMode: rom\n ? {\n adminRoles: rom.adminRoles ?? [],\n ownedServices: rom.ownedServices ?? false,\n ownerCollection: rom.ownerCollection,\n ownerField: rom.ownerField ?? 'owner',\n }\n : undefined,\n resourceTypes,\n slugs: {\n customers: pluginOptions.slugs?.customers ?? DEFAULT_SLUGS.customers,\n media: pluginOptions.slugs?.media ?? DEFAULT_SLUGS.media,\n reservations: pluginOptions.slugs?.reservations ?? DEFAULT_SLUGS.reservations,\n resources: pluginOptions.slugs?.resources ?? DEFAULT_SLUGS.resources,\n schedules: pluginOptions.slugs?.schedules ?? DEFAULT_SLUGS.schedules,\n services: pluginOptions.slugs?.services ?? DEFAULT_SLUGS.services,\n },\n staffProvisioning: resolveStaffProvisioning(pluginOptions, resourceTypes),\n statusMachine: userStatusMachine\n ? {\n blockingStatuses:\n userStatusMachine.blockingStatuses ?? DEFAULT_STATUS_MACHINE.blockingStatuses,\n defaultStatus: userStatusMachine.defaultStatus ?? DEFAULT_STATUS_MACHINE.defaultStatus,\n statuses: userStatusMachine.statuses ?? DEFAULT_STATUS_MACHINE.statuses,\n terminalStatuses:\n userStatusMachine.terminalStatuses ?? DEFAULT_STATUS_MACHINE.terminalStatuses,\n transitions: userStatusMachine.transitions ?? DEFAULT_STATUS_MACHINE.transitions,\n }\n : { ...DEFAULT_STATUS_MACHINE },\n userCollection: pluginOptions.userCollection ?? undefined,\n }\n\n validateStatusMachine(resolved.statusMachine)\n\n return resolved\n}\n"],"names":["DEFAULT_STATUS_MACHINE","validateStatusMachine","sm","statuses","includes","defaultStatus","Error","s","blockingStatuses","terminalStatuses","from","targets","Object","entries","transitions","to","DEFAULT_RESOURCE_TYPES","DEFAULT_LEAVE_TYPES","resolveStaffProvisioning","pluginOptions","resourceTypes","sp","staffProvisioning","undefined","resourceOwnerMode","staffRoles","length","resourceType","join","userCollection","beforeCreate","nameFrom","roleField","DEFAULT_SLUGS","customers","media","reservations","resources","schedules","services","DEFAULT_ADMIN_GROUP","DEFAULT_ALLOW_GUEST_BOOKING","DEFAULT_BUFFER_TIME","DEFAULT_CANCELLATION_NOTICE_PERIOD","resolveConfig","leaveTypes","userStatusMachine","statusMachine","rom","resolved","access","adminGroup","allowGuestBooking","cancellationNoticePeriod","defaultBufferTime","disabled","extraReservationFields","hooks","localized","adminRoles","ownedServices","ownerCollection","ownerField","slugs"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["import type {\n ReservationPluginConfig,\n ResolvedReservationPluginConfig,\n ResolvedStaffProvisioningConfig,\n StatusMachineConfig,\n} from './types.js'\n\nimport { DEFAULT_STATUS_MACHINE } from './types.js'\n\nfunction validateStatusMachine(sm: StatusMachineConfig): void {\n if (!sm.statuses.includes(sm.defaultStatus)) {\n throw new Error(`statusMachine.defaultStatus \"${sm.defaultStatus}\" is not in statuses array`)\n }\n for (const s of sm.blockingStatuses) {\n if (!sm.statuses.includes(s)) {\n throw new Error(`statusMachine.blockingStatuses contains \"${s}\" which is not in statuses array`)\n }\n }\n for (const s of sm.terminalStatuses) {\n if (!sm.statuses.includes(s)) {\n throw new Error(`statusMachine.terminalStatuses contains \"${s}\" which is not in statuses array`)\n }\n }\n for (const [from, targets] of Object.entries(sm.transitions)) {\n if (!sm.statuses.includes(from)) {\n throw new Error(`statusMachine.transitions has key \"${from}\" which is not in statuses array`)\n }\n for (const to of targets) {\n if (!sm.statuses.includes(to)) {\n throw new Error(`statusMachine.transitions[\"${from}\"] targets \"${to}\" which is not in statuses array`)\n }\n }\n }\n}\n\nexport const DEFAULT_RESOURCE_TYPES = ['staff', 'equipment', 'room']\nexport const DEFAULT_LEAVE_TYPES = ['vacation', 'sick', 'personal', 'closure', 'other']\n\nfunction resolveStaffProvisioning(\n pluginOptions: ReservationPluginConfig,\n resourceTypes: string[],\n): ResolvedStaffProvisioningConfig | undefined {\n const sp = pluginOptions.staffProvisioning\n if (!sp) {\n return undefined\n }\n\n if (!pluginOptions.resourceOwnerMode) {\n throw new Error('staffProvisioning requires resourceOwnerMode to be enabled')\n }\n if (sp.staffRoles.length === 0) {\n throw new Error('staffProvisioning.staffRoles must be a non-empty array')\n }\n const resourceType = sp.resourceType ?? 'staff'\n if (!resourceTypes.includes(resourceType)) {\n throw new Error(\n `staffProvisioning.resourceType \"${resourceType}\" is not in resourceTypes [${resourceTypes.join(', ')}]`,\n )\n }\n const userCollection = sp.userCollection ?? pluginOptions.userCollection\n if (!userCollection) {\n throw new Error(\n 'staffProvisioning.userCollection is required when top-level userCollection is unset',\n )\n }\n\n return {\n beforeCreate: sp.beforeCreate,\n nameFrom: sp.nameFrom ?? 'name',\n resourceType,\n roleField: sp.roleField ?? 'role',\n staffRoles: sp.staffRoles,\n userCollection,\n }\n}\n\nexport const DEFAULT_SLUGS = {\n customers: 'customers',\n media: 'media',\n reservations: 'reservations',\n resources: 'resources',\n schedules: 'schedules',\n services: 'services',\n} as const\n\nexport const DEFAULT_ADMIN_GROUP = 'Reservations'\nexport const DEFAULT_ALLOW_GUEST_BOOKING = false\nexport const DEFAULT_BUFFER_TIME = 0\nexport const DEFAULT_CANCELLATION_NOTICE_PERIOD = 24\n\nexport function resolveConfig(\n pluginOptions: ReservationPluginConfig,\n): ResolvedReservationPluginConfig {\n if (pluginOptions.resourceTypes !== undefined && pluginOptions.resourceTypes.length === 0) {\n throw new Error('resourceTypes must be a non-empty array')\n }\n if (pluginOptions.leaveTypes !== undefined && pluginOptions.leaveTypes.length === 0) {\n throw new Error('leaveTypes must be a non-empty array')\n }\n\n const resourceTypes = pluginOptions.resourceTypes ?? DEFAULT_RESOURCE_TYPES\n const userStatusMachine = pluginOptions.statusMachine\n const rom = pluginOptions.resourceOwnerMode\n const resolved: ResolvedReservationPluginConfig = {\n access: pluginOptions.access ?? {},\n adminGroup: pluginOptions.adminGroup ?? DEFAULT_ADMIN_GROUP,\n allowGuestBooking: pluginOptions.allowGuestBooking ?? DEFAULT_ALLOW_GUEST_BOOKING,\n cancellationNoticePeriod:\n pluginOptions.cancellationNoticePeriod ?? DEFAULT_CANCELLATION_NOTICE_PERIOD,\n defaultBufferTime: pluginOptions.defaultBufferTime ?? DEFAULT_BUFFER_TIME,\n disabled: pluginOptions.disabled ?? false,\n extraReservationFields: pluginOptions.extraReservationFields ?? [],\n hooks: pluginOptions.hooks ?? {},\n leaveTypes: pluginOptions.leaveTypes ?? DEFAULT_LEAVE_TYPES,\n localized: false,\n multiTenant: {\n cookieName: pluginOptions.multiTenant?.cookieName ?? 'payload-tenant',\n tenantField: pluginOptions.multiTenant?.tenantField ?? 'tenant',\n },\n resourceOwnerMode: rom\n ? {\n adminRoles: rom.adminRoles ?? [],\n ownedServices: rom.ownedServices ?? false,\n ownerCollection: rom.ownerCollection,\n ownerField: rom.ownerField ?? 'owner',\n }\n : undefined,\n resourceTypes,\n slugs: {\n customers: pluginOptions.slugs?.customers ?? DEFAULT_SLUGS.customers,\n media: pluginOptions.slugs?.media ?? DEFAULT_SLUGS.media,\n reservations: pluginOptions.slugs?.reservations ?? DEFAULT_SLUGS.reservations,\n resources: pluginOptions.slugs?.resources ?? DEFAULT_SLUGS.resources,\n schedules: pluginOptions.slugs?.schedules ?? DEFAULT_SLUGS.schedules,\n services: pluginOptions.slugs?.services ?? DEFAULT_SLUGS.services,\n },\n staffProvisioning: resolveStaffProvisioning(pluginOptions, resourceTypes),\n statusMachine: userStatusMachine\n ? {\n blockingStatuses:\n userStatusMachine.blockingStatuses ?? DEFAULT_STATUS_MACHINE.blockingStatuses,\n defaultStatus: userStatusMachine.defaultStatus ?? DEFAULT_STATUS_MACHINE.defaultStatus,\n statuses: userStatusMachine.statuses ?? DEFAULT_STATUS_MACHINE.statuses,\n terminalStatuses:\n userStatusMachine.terminalStatuses ?? DEFAULT_STATUS_MACHINE.terminalStatuses,\n transitions: userStatusMachine.transitions ?? DEFAULT_STATUS_MACHINE.transitions,\n }\n : { ...DEFAULT_STATUS_MACHINE },\n userCollection: pluginOptions.userCollection ?? undefined,\n }\n\n validateStatusMachine(resolved.statusMachine)\n\n return resolved\n}\n"],"names":["DEFAULT_STATUS_MACHINE","validateStatusMachine","sm","statuses","includes","defaultStatus","Error","s","blockingStatuses","terminalStatuses","from","targets","Object","entries","transitions","to","DEFAULT_RESOURCE_TYPES","DEFAULT_LEAVE_TYPES","resolveStaffProvisioning","pluginOptions","resourceTypes","sp","staffProvisioning","undefined","resourceOwnerMode","staffRoles","length","resourceType","join","userCollection","beforeCreate","nameFrom","roleField","DEFAULT_SLUGS","customers","media","reservations","resources","schedules","services","DEFAULT_ADMIN_GROUP","DEFAULT_ALLOW_GUEST_BOOKING","DEFAULT_BUFFER_TIME","DEFAULT_CANCELLATION_NOTICE_PERIOD","resolveConfig","leaveTypes","userStatusMachine","statusMachine","rom","resolved","access","adminGroup","allowGuestBooking","cancellationNoticePeriod","defaultBufferTime","disabled","extraReservationFields","hooks","localized","multiTenant","cookieName","tenantField","adminRoles","ownedServices","ownerCollection","ownerField","slugs"],"mappings":"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"}
package/dist/plugin.js CHANGED
@@ -108,6 +108,7 @@ export const payloadReserve = (pluginOptions = {})=>(config)=>{
108
108
  ...resolved.slugs
109
109
  };
110
110
  config.admin.custom.reservationStatusMachine = resolved.statusMachine;
111
+ config.admin.custom.reservationTenant = resolved.multiTenant;
111
112
  // Add dashboard widget
112
113
  if (!config.admin.dashboard) {
113
114
  config.admin.dashboard = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { CollectionSlug, Config, Field } from 'payload'\n\nimport { deepMergeSimple } from 'payload/shared'\n\nimport type { ReservationPluginConfig } from './types.js'\n\nimport { createCustomersCollection } from './collections/Customers.js'\nimport { createReservationsCollection } from './collections/Reservations.js'\nimport { createResourcesCollection } from './collections/Resources.js'\nimport { createSchedulesCollection } from './collections/Schedules.js'\nimport { createServicesCollection } from './collections/Services.js'\nimport { resolveConfig } from './defaults.js'\nimport { createCancelBookingEndpoint } from './endpoints/cancelBooking.js'\nimport { createCheckAvailabilityEndpoint } from './endpoints/checkAvailability.js'\nimport { createBookingEndpoint } from './endpoints/createBooking.js'\nimport { createCustomerSearchEndpoint } from './endpoints/customerSearch.js'\nimport { createGetSlotsEndpoint } from './endpoints/getSlots.js'\nimport { createResourceAvailabilityEndpoint } from './endpoints/resourceAvailability.js'\nimport { provisionStaffResource } from './hooks/users/provisionStaffResource.js'\nimport { type PluginT, translations } from './translations/index.js'\n\nexport const payloadReserve =\n (pluginOptions: ReservationPluginConfig = {}) =>\n (config: Config): Config => {\n const resolved = resolveConfig(pluginOptions)\n\n // Detect localization from the Payload config\n if (config.localization) {\n resolved.localized = true\n }\n\n if (!config.collections) {\n config.collections = []\n }\n\n if (resolved.disabled) {\n return config\n }\n\n if (resolved.userCollection) {\n // Extend the existing auth collection with customer fields\n const targetCollection = config.collections.find(\n (col) => col.slug === resolved.userCollection,\n )\n\n if (targetCollection) {\n // Collect existing field names for deduplication check\n const existingFieldNames = new Set(\n targetCollection.fields\n .map((field) => ('name' in field ? field.name : undefined))\n .filter(Boolean),\n )\n\n // Fields to inject if not already present. `name` is added so that\n // admin.useAsTitle: 'name' works out of the box on the extended user\n // collection (matches the v1.0.0 behaviour documented in README/SKILL).\n const fieldsToAdd: Field[] = [\n {\n name: 'name',\n type: 'text',\n maxLength: 200,\n required: true,\n },\n {\n name: 'phone',\n type: 'text',\n maxLength: 50,\n },\n {\n name: 'notes',\n type: 'textarea',\n },\n {\n name: 'bookings',\n type: 'join',\n collection: resolved.slugs.reservations as unknown as CollectionSlug,\n on: 'customer',\n },\n ]\n\n for (const field of fieldsToAdd) {\n const fieldName = 'name' in field ? field.name : undefined\n if (fieldName && !existingFieldNames.has(fieldName)) {\n targetCollection.fields.push(field)\n }\n }\n }\n\n // Point the customers slug at the user collection so other parts of the\n // plugin (endpoints, hooks) reference the correct collection\n resolved.slugs.customers = resolved.userCollection\n\n // Push only the 4 domain collections (no standalone Customers)\n config.collections.push(\n createServicesCollection(resolved),\n createResourcesCollection(resolved),\n createSchedulesCollection(resolved),\n createReservationsCollection(resolved),\n )\n } else {\n // Default behaviour: push all 5 collections including standalone Customers\n config.collections.push(\n createServicesCollection(resolved),\n createResourcesCollection(resolved),\n createSchedulesCollection(resolved),\n createReservationsCollection(resolved),\n createCustomersCollection(resolved),\n )\n }\n\n // Register custom endpoints\n if (!config.endpoints) {config.endpoints = []}\n config.endpoints.push(\n createCancelBookingEndpoint(resolved),\n createCheckAvailabilityEndpoint(resolved),\n createBookingEndpoint(resolved),\n createCustomerSearchEndpoint(resolved),\n createGetSlotsEndpoint(resolved),\n createResourceAvailabilityEndpoint(resolved),\n )\n\n // Wire staff auto-provisioning onto the staff user collection\n if (resolved.staffProvisioning) {\n const staffUserSlug = resolved.staffProvisioning.userCollection\n const staffCollection = config.collections.find((col) => col.slug === staffUserSlug)\n if (!staffCollection) {\n throw new Error(\n `staffProvisioning.userCollection \"${staffUserSlug}\" was not found in config.collections`,\n )\n }\n staffCollection.hooks = {\n ...staffCollection.hooks,\n afterChange: [\n ...(staffCollection.hooks?.afterChange ?? []),\n provisionStaffResource(resolved),\n ],\n }\n }\n\n // Set up admin configuration\n if (!config.admin) {config.admin = {}}\n if (!config.admin.components) {config.admin.components = {}}\n\n // Store slugs and status machine in admin custom for component access\n if (!config.admin.custom) {config.admin.custom = {}}\n config.admin.custom.reservationSlugs = {\n ...resolved.slugs,\n }\n config.admin.custom.reservationStatusMachine = resolved.statusMachine\n\n // Add dashboard widget\n if (!config.admin.dashboard) {\n config.admin.dashboard = { widgets: [] }\n }\n if (!config.admin.dashboard.widgets) {\n config.admin.dashboard.widgets = []\n }\n config.admin.dashboard.widgets.push({\n slug: 'reservation-todays-reservations',\n Component: 'payload-reserve/rsc#DashboardWidgetServer',\n label: ({ t }) => (t as PluginT)('reservation:dashboardTitle'),\n maxWidth: 'large',\n minWidth: 'medium',\n })\n\n // Add availability overview as custom admin view\n if (!config.admin.components.views) {\n config.admin.components.views = {}\n }\n ;(config.admin.components.views as Record<string, unknown>)['reservation-availability'] = {\n Component: 'payload-reserve/client#AvailabilityOverview',\n path: '/reservation-availability',\n }\n\n // Merge plugin translations (user translations take precedence)\n config.i18n = {\n ...(config.i18n ?? {}),\n translations: deepMergeSimple(\n translations,\n (config.i18n?.translations as Record<string, Record<string, unknown>>) ?? {},\n ),\n }\n\n return config\n }\n"],"names":["deepMergeSimple","createCustomersCollection","createReservationsCollection","createResourcesCollection","createSchedulesCollection","createServicesCollection","resolveConfig","createCancelBookingEndpoint","createCheckAvailabilityEndpoint","createBookingEndpoint","createCustomerSearchEndpoint","createGetSlotsEndpoint","createResourceAvailabilityEndpoint","provisionStaffResource","translations","payloadReserve","pluginOptions","config","resolved","localization","localized","collections","disabled","userCollection","targetCollection","find","col","slug","existingFieldNames","Set","fields","map","field","name","undefined","filter","Boolean","fieldsToAdd","type","maxLength","required","collection","slugs","reservations","on","fieldName","has","push","customers","endpoints","staffProvisioning","staffUserSlug","staffCollection","Error","hooks","afterChange","admin","components","custom","reservationSlugs","reservationStatusMachine","statusMachine","dashboard","widgets","Component","label","t","maxWidth","minWidth","views","path","i18n"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { CollectionSlug, Config, Field } from 'payload'\n\nimport { deepMergeSimple } from 'payload/shared'\n\nimport type { ReservationPluginConfig } from './types.js'\n\nimport { createCustomersCollection } from './collections/Customers.js'\nimport { createReservationsCollection } from './collections/Reservations.js'\nimport { createResourcesCollection } from './collections/Resources.js'\nimport { createSchedulesCollection } from './collections/Schedules.js'\nimport { createServicesCollection } from './collections/Services.js'\nimport { resolveConfig } from './defaults.js'\nimport { createCancelBookingEndpoint } from './endpoints/cancelBooking.js'\nimport { createCheckAvailabilityEndpoint } from './endpoints/checkAvailability.js'\nimport { createBookingEndpoint } from './endpoints/createBooking.js'\nimport { createCustomerSearchEndpoint } from './endpoints/customerSearch.js'\nimport { createGetSlotsEndpoint } from './endpoints/getSlots.js'\nimport { createResourceAvailabilityEndpoint } from './endpoints/resourceAvailability.js'\nimport { provisionStaffResource } from './hooks/users/provisionStaffResource.js'\nimport { type PluginT, translations } from './translations/index.js'\n\nexport const payloadReserve =\n (pluginOptions: ReservationPluginConfig = {}) =>\n (config: Config): Config => {\n const resolved = resolveConfig(pluginOptions)\n\n // Detect localization from the Payload config\n if (config.localization) {\n resolved.localized = true\n }\n\n if (!config.collections) {\n config.collections = []\n }\n\n if (resolved.disabled) {\n return config\n }\n\n if (resolved.userCollection) {\n // Extend the existing auth collection with customer fields\n const targetCollection = config.collections.find(\n (col) => col.slug === resolved.userCollection,\n )\n\n if (targetCollection) {\n // Collect existing field names for deduplication check\n const existingFieldNames = new Set(\n targetCollection.fields\n .map((field) => ('name' in field ? field.name : undefined))\n .filter(Boolean),\n )\n\n // Fields to inject if not already present. `name` is added so that\n // admin.useAsTitle: 'name' works out of the box on the extended user\n // collection (matches the v1.0.0 behaviour documented in README/SKILL).\n const fieldsToAdd: Field[] = [\n {\n name: 'name',\n type: 'text',\n maxLength: 200,\n required: true,\n },\n {\n name: 'phone',\n type: 'text',\n maxLength: 50,\n },\n {\n name: 'notes',\n type: 'textarea',\n },\n {\n name: 'bookings',\n type: 'join',\n collection: resolved.slugs.reservations as unknown as CollectionSlug,\n on: 'customer',\n },\n ]\n\n for (const field of fieldsToAdd) {\n const fieldName = 'name' in field ? field.name : undefined\n if (fieldName && !existingFieldNames.has(fieldName)) {\n targetCollection.fields.push(field)\n }\n }\n }\n\n // Point the customers slug at the user collection so other parts of the\n // plugin (endpoints, hooks) reference the correct collection\n resolved.slugs.customers = resolved.userCollection\n\n // Push only the 4 domain collections (no standalone Customers)\n config.collections.push(\n createServicesCollection(resolved),\n createResourcesCollection(resolved),\n createSchedulesCollection(resolved),\n createReservationsCollection(resolved),\n )\n } else {\n // Default behaviour: push all 5 collections including standalone Customers\n config.collections.push(\n createServicesCollection(resolved),\n createResourcesCollection(resolved),\n createSchedulesCollection(resolved),\n createReservationsCollection(resolved),\n createCustomersCollection(resolved),\n )\n }\n\n // Register custom endpoints\n if (!config.endpoints) {config.endpoints = []}\n config.endpoints.push(\n createCancelBookingEndpoint(resolved),\n createCheckAvailabilityEndpoint(resolved),\n createBookingEndpoint(resolved),\n createCustomerSearchEndpoint(resolved),\n createGetSlotsEndpoint(resolved),\n createResourceAvailabilityEndpoint(resolved),\n )\n\n // Wire staff auto-provisioning onto the staff user collection\n if (resolved.staffProvisioning) {\n const staffUserSlug = resolved.staffProvisioning.userCollection\n const staffCollection = config.collections.find((col) => col.slug === staffUserSlug)\n if (!staffCollection) {\n throw new Error(\n `staffProvisioning.userCollection \"${staffUserSlug}\" was not found in config.collections`,\n )\n }\n staffCollection.hooks = {\n ...staffCollection.hooks,\n afterChange: [\n ...(staffCollection.hooks?.afterChange ?? []),\n provisionStaffResource(resolved),\n ],\n }\n }\n\n // Set up admin configuration\n if (!config.admin) {config.admin = {}}\n if (!config.admin.components) {config.admin.components = {}}\n\n // Store slugs and status machine in admin custom for component access\n if (!config.admin.custom) {config.admin.custom = {}}\n config.admin.custom.reservationSlugs = {\n ...resolved.slugs,\n }\n config.admin.custom.reservationStatusMachine = resolved.statusMachine\n config.admin.custom.reservationTenant = resolved.multiTenant\n\n // Add dashboard widget\n if (!config.admin.dashboard) {\n config.admin.dashboard = { widgets: [] }\n }\n if (!config.admin.dashboard.widgets) {\n config.admin.dashboard.widgets = []\n }\n config.admin.dashboard.widgets.push({\n slug: 'reservation-todays-reservations',\n Component: 'payload-reserve/rsc#DashboardWidgetServer',\n label: ({ t }) => (t as PluginT)('reservation:dashboardTitle'),\n maxWidth: 'large',\n minWidth: 'medium',\n })\n\n // Add availability overview as custom admin view\n if (!config.admin.components.views) {\n config.admin.components.views = {}\n }\n ;(config.admin.components.views as Record<string, unknown>)['reservation-availability'] = {\n Component: 'payload-reserve/client#AvailabilityOverview',\n path: '/reservation-availability',\n }\n\n // Merge plugin translations (user translations take precedence)\n config.i18n = {\n ...(config.i18n ?? {}),\n translations: deepMergeSimple(\n translations,\n (config.i18n?.translations as Record<string, Record<string, unknown>>) ?? {},\n ),\n }\n\n return config\n }\n"],"names":["deepMergeSimple","createCustomersCollection","createReservationsCollection","createResourcesCollection","createSchedulesCollection","createServicesCollection","resolveConfig","createCancelBookingEndpoint","createCheckAvailabilityEndpoint","createBookingEndpoint","createCustomerSearchEndpoint","createGetSlotsEndpoint","createResourceAvailabilityEndpoint","provisionStaffResource","translations","payloadReserve","pluginOptions","config","resolved","localization","localized","collections","disabled","userCollection","targetCollection","find","col","slug","existingFieldNames","Set","fields","map","field","name","undefined","filter","Boolean","fieldsToAdd","type","maxLength","required","collection","slugs","reservations","on","fieldName","has","push","customers","endpoints","staffProvisioning","staffUserSlug","staffCollection","Error","hooks","afterChange","admin","components","custom","reservationSlugs","reservationStatusMachine","statusMachine","reservationTenant","multiTenant","dashboard","widgets","Component","label","t","maxWidth","minWidth","views","path","i18n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -122,6 +122,13 @@ export type ReservationPluginConfig = {
122
122
  hooks?: ReservationPluginHooks;
123
123
  /** Configurable leave/exception type vocabulary (default: vacation/sick/personal/closure/other) */
124
124
  leaveTypes?: string[];
125
+ /** Tenant scoping for the custom admin views (calendar, availability, dashboard). Applied only when the scoped collection has the tenant field AND the tenant cookie is set. */
126
+ multiTenant?: {
127
+ /** Cookie written by the tenant-selector (default 'payload-tenant'). */
128
+ cookieName?: string;
129
+ /** Tenant field name on scoped collections (default 'tenant'). */
130
+ tenantField?: string;
131
+ };
125
132
  /** Enable resource-owner multi-tenancy (opt-in) */
126
133
  resourceOwnerMode?: ResourceOwnerModeConfig;
127
134
  /** Configurable resourceType vocabulary (default: staff/equipment/room) */
@@ -159,6 +166,10 @@ export type ResolvedReservationPluginConfig = {
159
166
  hooks: ReservationPluginHooks;
160
167
  leaveTypes: string[];
161
168
  localized: boolean;
169
+ multiTenant: {
170
+ cookieName: string;
171
+ tenantField: string;
172
+ };
162
173
  resourceOwnerMode: ResolvedResourceOwnerModeConfig | undefined;
163
174
  resourceTypes: string[];
164
175
  slugs: {
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionConfig, Field, PayloadRequest } from 'payload'\n\n// --- Duration & Capacity models ---\n\nexport type DurationType = 'fixed' | 'flexible' | 'full-day'\n\nexport type CapacityMode = 'per-guest' | 'per-reservation'\n\n// --- Configurable status machine ---\n\nexport type StatusMachineConfig = {\n blockingStatuses: string[]\n defaultStatus: string\n statuses: string[]\n terminalStatuses: string[]\n transitions: Record<string, string[]>\n}\n\nexport const DEFAULT_STATUS_MACHINE: StatusMachineConfig = {\n blockingStatuses: ['pending', 'confirmed'],\n defaultStatus: 'pending',\n statuses: ['pending', 'confirmed', 'completed', 'cancelled', 'no-show'],\n terminalStatuses: ['completed', 'cancelled', 'no-show'],\n transitions: {\n cancelled: [],\n completed: [],\n confirmed: ['completed', 'cancelled', 'no-show'],\n 'no-show': [],\n pending: ['confirmed', 'cancelled'],\n },\n}\n\n// --- Reservation item (for multi-resource bookings, Phase 3) ---\n\nexport type ReservationItemConfig = {\n endTime?: string\n guestCount?: number\n resource: string\n service?: string\n startTime?: string\n}\n\n// --- Plugin hooks for external integrations ---\n\nexport type ReservationPluginHooks = {\n afterBookingCancel?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingConfirm?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingCreate?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterStatusChange?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n previousStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCancel?: Array<\n (args: {\n doc: Record<string, unknown>\n reason?: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingConfirm?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCreate?: Array<\n (args: {\n data: Record<string, unknown>\n req: PayloadRequest\n }) => Promise<Record<string, unknown>> | Record<string, unknown>\n >\n}\n\n// --- Resource owner mode ---\n\nexport type ResourceOwnerModeConfig = {\n /** Roles that can see all records (default: check req.user.collection === adminCollection) */\n adminRoles?: string[]\n /** Whether Services also get an owner field (default: false — Services are platform-managed) */\n ownedServices?: boolean\n /**\n * Collection the owner field relates to (where owners/staff live). Defaults to\n * `staffProvisioning.userCollection` when set, otherwise `slugs.customers`. Set\n * this when owners live in a different collection than your customers (e.g.\n * separate `users` and `customers` collections).\n */\n ownerCollection?: string\n /** Field name for the owner relationship on Resources (default: 'owner') */\n ownerField?: string\n}\n\nexport type ResolvedResourceOwnerModeConfig = {\n adminRoles: string[]\n ownedServices: boolean\n ownerCollection?: string\n ownerField: string\n}\n\n// --- Staff provisioning ---\n\nexport type StaffProvisioningConfig = {\n /** Stamp tenant / custom fields onto the provisioned Resource before create. */\n beforeCreate?: (args: {\n data: Record<string, unknown>\n req: PayloadRequest\n user: Record<string, unknown>\n }) => Promise<Record<string, unknown>> | Record<string, unknown>\n /** User field copied into Resource `name` (default 'name', falls back to email). */\n nameFrom?: string\n /** resourceType to stamp (default 'staff'). Must be a valid resourceType. */\n resourceType?: string\n /** Field on the user holding the role (default 'role'). */\n roleField?: string\n /** Role value(s) marking a user as staff. Required, non-empty. */\n staffRoles: string[]\n /** Auth collection holding staff users. Defaults to top-level `userCollection`. */\n userCollection?: string\n}\n\nexport type ResolvedStaffProvisioningConfig = {\n beforeCreate?: StaffProvisioningConfig['beforeCreate']\n nameFrom: string\n resourceType: string\n roleField: string\n staffRoles: string[]\n userCollection: string\n}\n\n// --- Plugin configuration ---\n\nexport type ReservationPluginConfig = {\n /** Override access control per collection */\n access?: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n /** Admin group name for all reservation collections */\n adminGroup?: string\n /** Allow bookings without a customer account by default (per-service override available) */\n allowGuestBooking?: boolean\n /** Hours of notice required before cancellation */\n cancellationNoticePeriod?: number\n /** Default buffer time in minutes between reservations */\n defaultBufferTime?: number\n /** Disable the plugin entirely */\n disabled?: boolean\n /** Extra fields to append to the Reservations collection */\n extraReservationFields?: Field[]\n /** Plugin hooks for external integrations */\n hooks?: ReservationPluginHooks\n /** Configurable leave/exception type vocabulary (default: vacation/sick/personal/closure/other) */\n leaveTypes?: string[]\n /** Enable resource-owner multi-tenancy (opt-in) */\n resourceOwnerMode?: ResourceOwnerModeConfig\n /** Configurable resourceType vocabulary (default: staff/equipment/room) */\n resourceTypes?: string[]\n /** Override collection slugs */\n slugs?: {\n customers?: string\n media?: string\n reservations?: string\n resources?: string\n schedules?: string\n services?: string\n }\n /** Auto-provision a Resource from staff-role users (opt-in; requires resourceOwnerMode) */\n staffProvisioning?: StaffProvisioningConfig\n /** Configurable status machine (defaults to current behavior) */\n statusMachine?: Partial<StatusMachineConfig>\n /** Which existing auth collection to extend with customer fields */\n userCollection?: string\n}\n\nexport type ResolvedReservationPluginConfig = {\n access: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n adminGroup: string\n allowGuestBooking: boolean\n cancellationNoticePeriod: number\n defaultBufferTime: number\n disabled: boolean\n extraReservationFields: Field[]\n hooks: ReservationPluginHooks\n leaveTypes: string[]\n localized: boolean\n resourceOwnerMode: ResolvedResourceOwnerModeConfig | undefined\n resourceTypes: string[]\n slugs: {\n customers: string\n media: string\n reservations: string\n resources: string\n schedules: string\n services: string\n }\n staffProvisioning: ResolvedStaffProvisioningConfig | undefined\n statusMachine: StatusMachineConfig\n userCollection: string | undefined\n}\n\nexport type ReservationStatus = 'cancelled' | 'completed' | 'confirmed' | 'no-show' | 'pending'\n\nexport type DayOfWeek = 'fri' | 'mon' | 'sat' | 'sun' | 'thu' | 'tue' | 'wed'\n\nexport type ScheduleType = 'manual' | 'recurring'\n\n/** @deprecated Use DEFAULT_STATUS_MACHINE.transitions instead */\nexport const VALID_STATUS_TRANSITIONS: Record<ReservationStatus, ReservationStatus[]> =\n DEFAULT_STATUS_MACHINE.transitions as Record<ReservationStatus, ReservationStatus[]>\n"],"names":["DEFAULT_STATUS_MACHINE","blockingStatuses","defaultStatus","statuses","terminalStatuses","transitions","cancelled","completed","confirmed","pending","VALID_STATUS_TRANSITIONS"],"mappings":"AAkBA,OAAO,MAAMA,yBAA8C;IACzDC,kBAAkB;QAAC;QAAW;KAAY;IAC1CC,eAAe;IACfC,UAAU;QAAC;QAAW;QAAa;QAAa;QAAa;KAAU;IACvEC,kBAAkB;QAAC;QAAa;QAAa;KAAU;IACvDC,aAAa;QACXC,WAAW,EAAE;QACbC,WAAW,EAAE;QACbC,WAAW;YAAC;YAAa;YAAa;SAAU;QAChD,WAAW,EAAE;QACbC,SAAS;YAAC;YAAa;SAAY;IACrC;AACF,EAAC;AAmMD,+DAA+D,GAC/D,OAAO,MAAMC,2BACXV,uBAAuBK,WAAW,CAAkD"}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionConfig, Field, PayloadRequest } from 'payload'\n\n// --- Duration & Capacity models ---\n\nexport type DurationType = 'fixed' | 'flexible' | 'full-day'\n\nexport type CapacityMode = 'per-guest' | 'per-reservation'\n\n// --- Configurable status machine ---\n\nexport type StatusMachineConfig = {\n blockingStatuses: string[]\n defaultStatus: string\n statuses: string[]\n terminalStatuses: string[]\n transitions: Record<string, string[]>\n}\n\nexport const DEFAULT_STATUS_MACHINE: StatusMachineConfig = {\n blockingStatuses: ['pending', 'confirmed'],\n defaultStatus: 'pending',\n statuses: ['pending', 'confirmed', 'completed', 'cancelled', 'no-show'],\n terminalStatuses: ['completed', 'cancelled', 'no-show'],\n transitions: {\n cancelled: [],\n completed: [],\n confirmed: ['completed', 'cancelled', 'no-show'],\n 'no-show': [],\n pending: ['confirmed', 'cancelled'],\n },\n}\n\n// --- Reservation item (for multi-resource bookings, Phase 3) ---\n\nexport type ReservationItemConfig = {\n endTime?: string\n guestCount?: number\n resource: string\n service?: string\n startTime?: string\n}\n\n// --- Plugin hooks for external integrations ---\n\nexport type ReservationPluginHooks = {\n afterBookingCancel?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingConfirm?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingCreate?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterStatusChange?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n previousStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCancel?: Array<\n (args: {\n doc: Record<string, unknown>\n reason?: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingConfirm?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCreate?: Array<\n (args: {\n data: Record<string, unknown>\n req: PayloadRequest\n }) => Promise<Record<string, unknown>> | Record<string, unknown>\n >\n}\n\n// --- Resource owner mode ---\n\nexport type ResourceOwnerModeConfig = {\n /** Roles that can see all records (default: check req.user.collection === adminCollection) */\n adminRoles?: string[]\n /** Whether Services also get an owner field (default: false — Services are platform-managed) */\n ownedServices?: boolean\n /**\n * Collection the owner field relates to (where owners/staff live). Defaults to\n * `staffProvisioning.userCollection` when set, otherwise `slugs.customers`. Set\n * this when owners live in a different collection than your customers (e.g.\n * separate `users` and `customers` collections).\n */\n ownerCollection?: string\n /** Field name for the owner relationship on Resources (default: 'owner') */\n ownerField?: string\n}\n\nexport type ResolvedResourceOwnerModeConfig = {\n adminRoles: string[]\n ownedServices: boolean\n ownerCollection?: string\n ownerField: string\n}\n\n// --- Staff provisioning ---\n\nexport type StaffProvisioningConfig = {\n /** Stamp tenant / custom fields onto the provisioned Resource before create. */\n beforeCreate?: (args: {\n data: Record<string, unknown>\n req: PayloadRequest\n user: Record<string, unknown>\n }) => Promise<Record<string, unknown>> | Record<string, unknown>\n /** User field copied into Resource `name` (default 'name', falls back to email). */\n nameFrom?: string\n /** resourceType to stamp (default 'staff'). Must be a valid resourceType. */\n resourceType?: string\n /** Field on the user holding the role (default 'role'). */\n roleField?: string\n /** Role value(s) marking a user as staff. Required, non-empty. */\n staffRoles: string[]\n /** Auth collection holding staff users. Defaults to top-level `userCollection`. */\n userCollection?: string\n}\n\nexport type ResolvedStaffProvisioningConfig = {\n beforeCreate?: StaffProvisioningConfig['beforeCreate']\n nameFrom: string\n resourceType: string\n roleField: string\n staffRoles: string[]\n userCollection: string\n}\n\n// --- Plugin configuration ---\n\nexport type ReservationPluginConfig = {\n /** Override access control per collection */\n access?: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n /** Admin group name for all reservation collections */\n adminGroup?: string\n /** Allow bookings without a customer account by default (per-service override available) */\n allowGuestBooking?: boolean\n /** Hours of notice required before cancellation */\n cancellationNoticePeriod?: number\n /** Default buffer time in minutes between reservations */\n defaultBufferTime?: number\n /** Disable the plugin entirely */\n disabled?: boolean\n /** Extra fields to append to the Reservations collection */\n extraReservationFields?: Field[]\n /** Plugin hooks for external integrations */\n hooks?: ReservationPluginHooks\n /** Configurable leave/exception type vocabulary (default: vacation/sick/personal/closure/other) */\n leaveTypes?: string[]\n /** Tenant scoping for the custom admin views (calendar, availability, dashboard). Applied only when the scoped collection has the tenant field AND the tenant cookie is set. */\n multiTenant?: {\n /** Cookie written by the tenant-selector (default 'payload-tenant'). */\n cookieName?: string\n /** Tenant field name on scoped collections (default 'tenant'). */\n tenantField?: string\n }\n /** Enable resource-owner multi-tenancy (opt-in) */\n resourceOwnerMode?: ResourceOwnerModeConfig\n /** Configurable resourceType vocabulary (default: staff/equipment/room) */\n resourceTypes?: string[]\n /** Override collection slugs */\n slugs?: {\n customers?: string\n media?: string\n reservations?: string\n resources?: string\n schedules?: string\n services?: string\n }\n /** Auto-provision a Resource from staff-role users (opt-in; requires resourceOwnerMode) */\n staffProvisioning?: StaffProvisioningConfig\n /** Configurable status machine (defaults to current behavior) */\n statusMachine?: Partial<StatusMachineConfig>\n /** Which existing auth collection to extend with customer fields */\n userCollection?: string\n}\n\nexport type ResolvedReservationPluginConfig = {\n access: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n adminGroup: string\n allowGuestBooking: boolean\n cancellationNoticePeriod: number\n defaultBufferTime: number\n disabled: boolean\n extraReservationFields: Field[]\n hooks: ReservationPluginHooks\n leaveTypes: string[]\n localized: boolean\n multiTenant: {\n cookieName: string\n tenantField: string\n }\n resourceOwnerMode: ResolvedResourceOwnerModeConfig | undefined\n resourceTypes: string[]\n slugs: {\n customers: string\n media: string\n reservations: string\n resources: string\n schedules: string\n services: string\n }\n staffProvisioning: ResolvedStaffProvisioningConfig | undefined\n statusMachine: StatusMachineConfig\n userCollection: string | undefined\n}\n\nexport type ReservationStatus = 'cancelled' | 'completed' | 'confirmed' | 'no-show' | 'pending'\n\nexport type DayOfWeek = 'fri' | 'mon' | 'sat' | 'sun' | 'thu' | 'tue' | 'wed'\n\nexport type ScheduleType = 'manual' | 'recurring'\n\n/** @deprecated Use DEFAULT_STATUS_MACHINE.transitions instead */\nexport const VALID_STATUS_TRANSITIONS: Record<ReservationStatus, ReservationStatus[]> =\n DEFAULT_STATUS_MACHINE.transitions as Record<ReservationStatus, ReservationStatus[]>\n"],"names":["DEFAULT_STATUS_MACHINE","blockingStatuses","defaultStatus","statuses","terminalStatuses","transitions","cancelled","completed","confirmed","pending","VALID_STATUS_TRANSITIONS"],"mappings":"AAkBA,OAAO,MAAMA,yBAA8C;IACzDC,kBAAkB;QAAC;QAAW;KAAY;IAC1CC,eAAe;IACfC,UAAU;QAAC;QAAW;QAAa;QAAa;QAAa;KAAU;IACvEC,kBAAkB;QAAC;QAAa;QAAa;KAAU;IACvDC,aAAa;QACXC,WAAW,EAAE;QACbC,WAAW,EAAE;QACbC,WAAW;YAAC;YAAa;YAAa;SAAU;QAChD,WAAW,EAAE;QACbC,SAAS;YAAC;YAAa;SAAY;IACrC;AACF,EAAC;AA8MD,+DAA+D,GAC/D,OAAO,MAAMC,2BACXV,uBAAuBK,WAAW,CAAkD"}
@@ -0,0 +1,25 @@
1
+ import type { Where } from 'payload';
2
+ type CollectionLike = {
3
+ fields?: unknown[];
4
+ } | null | undefined;
5
+ type TenantGate = {
6
+ hasField: boolean;
7
+ tenantField: string;
8
+ tenantId: null | string;
9
+ };
10
+ /**
11
+ * True if the collection has a top-level field named `fieldName`.
12
+ *
13
+ * Intentionally scans only top-level fields — does NOT descend into groups,
14
+ * rows, or tabs. This is by design: the multi-tenant plugin injects its tenant
15
+ * field at the top level of the collection, so a shallow scan is both correct
16
+ * and sufficient.
17
+ */
18
+ export declare function collectionHasTenantField(collection: CollectionLike, fieldName: string): boolean;
19
+ /** Parse a single cookie value from a Cookie header / document.cookie string. */
20
+ export declare function readCookie(cookieHeader: null | string | undefined, name: string): null | string;
21
+ /** REST query params: `{ 'where[<field>][equals]': id }`, or `{}` when not scoping. */
22
+ export declare function tenantQueryParams({ hasField, tenantField, tenantId }: TenantGate): Record<string, string>;
23
+ /** Local API `Where` clause, or `null` when not scoping. */
24
+ export declare function tenantWhereClause({ hasField, tenantField, tenantId }: TenantGate): null | Where;
25
+ export {};
@@ -0,0 +1,56 @@
1
+ /**
2
+ * True if the collection has a top-level field named `fieldName`.
3
+ *
4
+ * Intentionally scans only top-level fields — does NOT descend into groups,
5
+ * rows, or tabs. This is by design: the multi-tenant plugin injects its tenant
6
+ * field at the top level of the collection, so a shallow scan is both correct
7
+ * and sufficient.
8
+ */ export function collectionHasTenantField(collection, fieldName) {
9
+ if (!collection || !Array.isArray(collection.fields)) {
10
+ return false;
11
+ }
12
+ return collection.fields.some((f)=>typeof f === 'object' && f !== null && 'name' in f && f.name === fieldName);
13
+ }
14
+ /** Parse a single cookie value from a Cookie header / document.cookie string. */ export function readCookie(cookieHeader, name) {
15
+ if (!cookieHeader) {
16
+ return null;
17
+ }
18
+ for (const part of cookieHeader.split(';')){
19
+ const idx = part.indexOf('=');
20
+ if (idx === -1) {
21
+ continue;
22
+ }
23
+ if (part.slice(0, idx).trim() === name) {
24
+ const raw = part.slice(idx + 1).trim();
25
+ if (!raw) {
26
+ return null;
27
+ }
28
+ try {
29
+ return decodeURIComponent(raw);
30
+ } catch {
31
+ return raw;
32
+ }
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ /** REST query params: `{ 'where[<field>][equals]': id }`, or `{}` when not scoping. */ export function tenantQueryParams({ hasField, tenantField, tenantId }) {
38
+ if (!hasField || !tenantId) {
39
+ return {};
40
+ }
41
+ return {
42
+ [`where[${tenantField}][equals]`]: tenantId
43
+ };
44
+ }
45
+ /** Local API `Where` clause, or `null` when not scoping. */ export function tenantWhereClause({ hasField, tenantField, tenantId }) {
46
+ if (!hasField || !tenantId) {
47
+ return null;
48
+ }
49
+ return {
50
+ [tenantField]: {
51
+ equals: tenantId
52
+ }
53
+ };
54
+ }
55
+
56
+ //# sourceMappingURL=tenantFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/tenantFilter.ts"],"sourcesContent":["import type { Where } from 'payload'\n\ntype CollectionLike = { fields?: unknown[] } | null | undefined\n\ntype TenantGate = {\n hasField: boolean\n tenantField: string\n tenantId: null | string\n}\n\n/**\n * True if the collection has a top-level field named `fieldName`.\n *\n * Intentionally scans only top-level fields — does NOT descend into groups,\n * rows, or tabs. This is by design: the multi-tenant plugin injects its tenant\n * field at the top level of the collection, so a shallow scan is both correct\n * and sufficient.\n */\nexport function collectionHasTenantField(collection: CollectionLike, fieldName: string): boolean {\n if (!collection || !Array.isArray(collection.fields)) {\n return false\n }\n return collection.fields.some(\n (f) => typeof f === 'object' && f !== null && 'name' in f && (f as { name?: string }).name === fieldName,\n )\n}\n\n/** Parse a single cookie value from a Cookie header / document.cookie string. */\nexport function readCookie(cookieHeader: null | string | undefined, name: string): null | string {\n if (!cookieHeader) {\n return null\n }\n for (const part of cookieHeader.split(';')) {\n const idx = part.indexOf('=')\n if (idx === -1) {\n continue\n }\n if (part.slice(0, idx).trim() === name) {\n const raw = part.slice(idx + 1).trim()\n if (!raw) {\n return null\n }\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n }\n }\n return null\n}\n\n/** REST query params: `{ 'where[<field>][equals]': id }`, or `{}` when not scoping. */\nexport function tenantQueryParams({ hasField, tenantField, tenantId }: TenantGate): Record<string, string> {\n if (!hasField || !tenantId) {\n return {}\n }\n return { [`where[${tenantField}][equals]`]: tenantId }\n}\n\n/** Local API `Where` clause, or `null` when not scoping. */\nexport function tenantWhereClause({ hasField, tenantField, tenantId }: TenantGate): null | Where {\n if (!hasField || !tenantId) {\n return null\n }\n return { [tenantField]: { equals: tenantId } }\n}\n"],"names":["collectionHasTenantField","collection","fieldName","Array","isArray","fields","some","f","name","readCookie","cookieHeader","part","split","idx","indexOf","slice","trim","raw","decodeURIComponent","tenantQueryParams","hasField","tenantField","tenantId","tenantWhereClause","equals"],"mappings":"AAUA;;;;;;;CAOC,GACD,OAAO,SAASA,yBAAyBC,UAA0B,EAAEC,SAAiB;IACpF,IAAI,CAACD,cAAc,CAACE,MAAMC,OAAO,CAACH,WAAWI,MAAM,GAAG;QACpD,OAAO;IACT;IACA,OAAOJ,WAAWI,MAAM,CAACC,IAAI,CAC3B,CAACC,IAAM,OAAOA,MAAM,YAAYA,MAAM,QAAQ,UAAUA,KAAK,AAACA,EAAwBC,IAAI,KAAKN;AAEnG;AAEA,+EAA+E,GAC/E,OAAO,SAASO,WAAWC,YAAuC,EAAEF,IAAY;IAC9E,IAAI,CAACE,cAAc;QACjB,OAAO;IACT;IACA,KAAK,MAAMC,QAAQD,aAAaE,KAAK,CAAC,KAAM;QAC1C,MAAMC,MAAMF,KAAKG,OAAO,CAAC;QACzB,IAAID,QAAQ,CAAC,GAAG;YACd;QACF;QACA,IAAIF,KAAKI,KAAK,CAAC,GAAGF,KAAKG,IAAI,OAAOR,MAAM;YACtC,MAAMS,MAAMN,KAAKI,KAAK,CAACF,MAAM,GAAGG,IAAI;YACpC,IAAI,CAACC,KAAK;gBACR,OAAO;YACT;YACA,IAAI;gBACF,OAAOC,mBAAmBD;YAC5B,EAAE,OAAM;gBACN,OAAOA;YACT;QACF;IACF;IACA,OAAO;AACT;AAEA,qFAAqF,GACrF,OAAO,SAASE,kBAAkB,EAAEC,QAAQ,EAAEC,WAAW,EAAEC,QAAQ,EAAc;IAC/E,IAAI,CAACF,YAAY,CAACE,UAAU;QAC1B,OAAO,CAAC;IACV;IACA,OAAO;QAAE,CAAC,CAAC,MAAM,EAAED,YAAY,SAAS,CAAC,CAAC,EAAEC;IAAS;AACvD;AAEA,0DAA0D,GAC1D,OAAO,SAASC,kBAAkB,EAAEH,QAAQ,EAAEC,WAAW,EAAEC,QAAQ,EAAc;IAC/E,IAAI,CAACF,YAAY,CAACE,UAAU;QAC1B,OAAO;IACT;IACA,OAAO;QAAE,CAACD,YAAY,EAAE;YAAEG,QAAQF;QAAS;IAAE;AAC/C"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * REST query params scoping a collection's fetch to the selected tenant.
3
+ * Returns `{}` when the collection has no tenant field or no tenant cookie is set,
4
+ * so single-tenant installs build identical URLs to before.
5
+ */
6
+ export declare function useTenantFilter(collectionSlug: string): Record<string, string>;
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+ import { useConfig } from '@payloadcms/ui';
3
+ import { useMemo } from 'react';
4
+ import { collectionHasTenantField, readCookie, tenantQueryParams } from './tenantFilter.js';
5
+ /**
6
+ * REST query params scoping a collection's fetch to the selected tenant.
7
+ * Returns `{}` when the collection has no tenant field or no tenant cookie is set,
8
+ * so single-tenant installs build identical URLs to before.
9
+ */ export function useTenantFilter(collectionSlug) {
10
+ const { config } = useConfig();
11
+ const tenantConfig = config.admin?.custom?.reservationTenant ?? {};
12
+ const cookieName = tenantConfig.cookieName ?? 'payload-tenant';
13
+ const tenantField = tenantConfig.tenantField ?? 'tenant';
14
+ const collection = config.collections?.find((c)=>c.slug === collectionSlug);
15
+ const hasField = collectionHasTenantField(collection, tenantField);
16
+ const tenantId = typeof document !== 'undefined' ? readCookie(document.cookie, cookieName) : null;
17
+ return useMemo(()=>tenantQueryParams({
18
+ hasField,
19
+ tenantField,
20
+ tenantId
21
+ }), [
22
+ hasField,
23
+ tenantField,
24
+ tenantId
25
+ ]);
26
+ }
27
+
28
+ //# sourceMappingURL=useTenantFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/useTenantFilter.ts"],"sourcesContent":["'use client'\nimport { useConfig } from '@payloadcms/ui'\nimport { useMemo } from 'react'\n\nimport { collectionHasTenantField, readCookie, tenantQueryParams } from './tenantFilter.js'\n\n/**\n * REST query params scoping a collection's fetch to the selected tenant.\n * Returns `{}` when the collection has no tenant field or no tenant cookie is set,\n * so single-tenant installs build identical URLs to before.\n */\nexport function useTenantFilter(collectionSlug: string): Record<string, string> {\n const { config } = useConfig()\n const tenantConfig =\n (config.admin?.custom?.reservationTenant as\n | { cookieName?: string; tenantField?: string }\n | undefined) ?? {}\n const cookieName = tenantConfig.cookieName ?? 'payload-tenant'\n const tenantField = tenantConfig.tenantField ?? 'tenant'\n const collection = config.collections?.find((c) => c.slug === collectionSlug)\n const hasField = collectionHasTenantField(collection as { fields?: unknown[] } | undefined, tenantField)\n const tenantId = typeof document !== 'undefined' ? readCookie(document.cookie, cookieName) : null\n\n return useMemo(\n () => tenantQueryParams({ hasField, tenantField, tenantId }),\n [hasField, tenantField, tenantId],\n )\n}\n"],"names":["useConfig","useMemo","collectionHasTenantField","readCookie","tenantQueryParams","useTenantFilter","collectionSlug","config","tenantConfig","admin","custom","reservationTenant","cookieName","tenantField","collection","collections","find","c","slug","hasField","tenantId","document","cookie"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,iBAAgB;AAC1C,SAASC,OAAO,QAAQ,QAAO;AAE/B,SAASC,wBAAwB,EAAEC,UAAU,EAAEC,iBAAiB,QAAQ,oBAAmB;AAE3F;;;;CAIC,GACD,OAAO,SAASC,gBAAgBC,cAAsB;IACpD,MAAM,EAAEC,MAAM,EAAE,GAAGP;IACnB,MAAMQ,eACJ,AAACD,OAAOE,KAAK,EAAEC,QAAQC,qBAEL,CAAC;IACrB,MAAMC,aAAaJ,aAAaI,UAAU,IAAI;IAC9C,MAAMC,cAAcL,aAAaK,WAAW,IAAI;IAChD,MAAMC,aAAaP,OAAOQ,WAAW,EAAEC,KAAK,CAACC,IAAMA,EAAEC,IAAI,KAAKZ;IAC9D,MAAMa,WAAWjB,yBAAyBY,YAAkDD;IAC5F,MAAMO,WAAW,OAAOC,aAAa,cAAclB,WAAWkB,SAASC,MAAM,EAAEV,cAAc;IAE7F,OAAOX,QACL,IAAMG,kBAAkB;YAAEe;YAAUN;YAAaO;QAAS,IAC1D;QAACD;QAAUN;QAAaO;KAAS;AAErC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-reserve",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "A Payload CMS 3.x plugin for reservation and booking management with conflict detection, status workflows, and calendar UI",
5
5
  "keywords": [
6
6
  "payload",
@@ -54,6 +54,7 @@
54
54
  "@payloadcms/db-sqlite": "3.79.0",
55
55
  "@payloadcms/eslint-config": "3.9.0",
56
56
  "@payloadcms/next": "3.79.0",
57
+ "@payloadcms/plugin-multi-tenant": "3.79.0",
57
58
  "@payloadcms/richtext-lexical": "3.79.0",
58
59
  "@payloadcms/translations": "3.79.0",
59
60
  "@payloadcms/ui": "3.79.0",