payload-reserve 1.0.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.
- package/LICENSE +21 -0
- package/README.md +1145 -0
- package/dist/collections/Reservations.d.ts +3 -0
- package/dist/collections/Reservations.js +124 -0
- package/dist/collections/Reservations.js.map +1 -0
- package/dist/collections/Resources.d.ts +3 -0
- package/dist/collections/Resources.js +53 -0
- package/dist/collections/Resources.js.map +1 -0
- package/dist/collections/Schedules.d.ts +3 -0
- package/dist/collections/Schedules.js +182 -0
- package/dist/collections/Schedules.js.map +1 -0
- package/dist/collections/Services.d.ts +3 -0
- package/dist/collections/Services.js +75 -0
- package/dist/collections/Services.js.map +1 -0
- package/dist/components/AvailabilityOverview/AvailabilityOverview.module.css +103 -0
- package/dist/components/AvailabilityOverview/index.d.ts +2 -0
- package/dist/components/AvailabilityOverview/index.js +277 -0
- package/dist/components/AvailabilityOverview/index.js.map +1 -0
- package/dist/components/CalendarView/CalendarView.module.css +283 -0
- package/dist/components/CalendarView/index.d.ts +3 -0
- package/dist/components/CalendarView/index.js +508 -0
- package/dist/components/CalendarView/index.js.map +1 -0
- package/dist/components/DashboardWidget/DashboardWidget.module.css +53 -0
- package/dist/components/DashboardWidget/DashboardWidgetServer.d.ts +2 -0
- package/dist/components/DashboardWidget/DashboardWidgetServer.js +126 -0
- package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -0
- package/dist/defaults.d.ts +12 -0
- package/dist/defaults.js +29 -0
- package/dist/defaults.js.map +1 -0
- package/dist/exports/client.d.ts +2 -0
- package/dist/exports/client.js +4 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/exports/rsc.js +3 -0
- package/dist/exports/rsc.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/reservations/calculateEndTime.d.ts +3 -0
- package/dist/hooks/reservations/calculateEndTime.js +22 -0
- package/dist/hooks/reservations/calculateEndTime.js.map +1 -0
- package/dist/hooks/reservations/validateCancellation.d.ts +3 -0
- package/dist/hooks/reservations/validateCancellation.js +38 -0
- package/dist/hooks/reservations/validateCancellation.js.map +1 -0
- package/dist/hooks/reservations/validateConflicts.d.ts +3 -0
- package/dist/hooks/reservations/validateConflicts.js +86 -0
- package/dist/hooks/reservations/validateConflicts.js.map +1 -0
- package/dist/hooks/reservations/validateStatusTransition.d.ts +2 -0
- package/dist/hooks/reservations/validateStatusTransition.js +54 -0
- package/dist/hooks/reservations/validateStatusTransition.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +106 -0
- package/dist/plugin.js.map +1 -0
- package/dist/translations/en.json +86 -0
- package/dist/translations/index.d.ts +3 -0
- package/dist/translations/index.js +8 -0
- package/dist/translations/index.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/utilities/scheduleUtils.d.ts +54 -0
- package/dist/utilities/scheduleUtils.js +87 -0
- package/dist/utilities/scheduleUtils.js.map +1 -0
- package/dist/utilities/slotUtils.d.ts +21 -0
- package/dist/utilities/slotUtils.js +28 -0
- package/dist/utilities/slotUtils.js.map +1 -0
- package/package.json +108 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import styles from './DashboardWidget.module.css';
|
|
3
|
+
export const DashboardWidgetServer = async (props)=>{
|
|
4
|
+
const { req } = props;
|
|
5
|
+
const { i18n, payload } = req;
|
|
6
|
+
const t = i18n.t;
|
|
7
|
+
const slugs = payload.config.admin?.custom?.reservationSlugs;
|
|
8
|
+
if (!slugs) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const now = new Date();
|
|
12
|
+
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
13
|
+
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
|
|
14
|
+
const { docs: todayReservations } = await payload.find({
|
|
15
|
+
collection: slugs.reservations,
|
|
16
|
+
limit: 100,
|
|
17
|
+
sort: 'startTime',
|
|
18
|
+
where: {
|
|
19
|
+
startTime: {
|
|
20
|
+
greater_than_equal: startOfDay.toISOString(),
|
|
21
|
+
less_than: endOfDay.toISOString()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const total = todayReservations.length;
|
|
26
|
+
const upcoming = todayReservations.filter((r)=>r.status !== 'completed' && r.status !== 'cancelled' && r.status !== 'no-show' && new Date(r.startTime) > now).length;
|
|
27
|
+
const completed = todayReservations.filter((r)=>r.status === 'completed').length;
|
|
28
|
+
const cancelled = todayReservations.filter((r)=>r.status === 'cancelled').length;
|
|
29
|
+
const nextAppointment = todayReservations.find((r)=>r.status !== 'completed' && r.status !== 'cancelled' && r.status !== 'no-show' && new Date(r.startTime) > now);
|
|
30
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
31
|
+
className: styles.wrapper,
|
|
32
|
+
children: [
|
|
33
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
34
|
+
className: styles.title,
|
|
35
|
+
children: t('reservation:dashboardTitle')
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
38
|
+
className: styles.statsGrid,
|
|
39
|
+
children: [
|
|
40
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
41
|
+
className: styles.statCard,
|
|
42
|
+
children: [
|
|
43
|
+
/*#__PURE__*/ _jsx("span", {
|
|
44
|
+
className: styles.statValue,
|
|
45
|
+
children: total
|
|
46
|
+
}),
|
|
47
|
+
/*#__PURE__*/ _jsx("span", {
|
|
48
|
+
className: styles.statLabel,
|
|
49
|
+
children: t('reservation:dashboardTotal')
|
|
50
|
+
})
|
|
51
|
+
]
|
|
52
|
+
}),
|
|
53
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
54
|
+
className: styles.statCard,
|
|
55
|
+
children: [
|
|
56
|
+
/*#__PURE__*/ _jsx("span", {
|
|
57
|
+
className: styles.statValue,
|
|
58
|
+
children: upcoming
|
|
59
|
+
}),
|
|
60
|
+
/*#__PURE__*/ _jsx("span", {
|
|
61
|
+
className: styles.statLabel,
|
|
62
|
+
children: t('reservation:dashboardUpcoming')
|
|
63
|
+
})
|
|
64
|
+
]
|
|
65
|
+
}),
|
|
66
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
67
|
+
className: styles.statCard,
|
|
68
|
+
children: [
|
|
69
|
+
/*#__PURE__*/ _jsx("span", {
|
|
70
|
+
className: styles.statValue,
|
|
71
|
+
children: completed
|
|
72
|
+
}),
|
|
73
|
+
/*#__PURE__*/ _jsx("span", {
|
|
74
|
+
className: styles.statLabel,
|
|
75
|
+
children: t('reservation:dashboardCompleted')
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
}),
|
|
79
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
80
|
+
className: styles.statCard,
|
|
81
|
+
children: [
|
|
82
|
+
/*#__PURE__*/ _jsx("span", {
|
|
83
|
+
className: styles.statValue,
|
|
84
|
+
children: cancelled
|
|
85
|
+
}),
|
|
86
|
+
/*#__PURE__*/ _jsx("span", {
|
|
87
|
+
className: styles.statLabel,
|
|
88
|
+
children: t('reservation:dashboardCancelled')
|
|
89
|
+
})
|
|
90
|
+
]
|
|
91
|
+
})
|
|
92
|
+
]
|
|
93
|
+
}),
|
|
94
|
+
nextAppointment ? /*#__PURE__*/ _jsxs("div", {
|
|
95
|
+
className: styles.nextAppointment,
|
|
96
|
+
children: [
|
|
97
|
+
/*#__PURE__*/ _jsx("strong", {
|
|
98
|
+
children: t('reservation:dashboardNextAppointment')
|
|
99
|
+
}),
|
|
100
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
101
|
+
children: [
|
|
102
|
+
t('reservation:dashboardTime'),
|
|
103
|
+
" ",
|
|
104
|
+
new Date(nextAppointment.startTime).toLocaleTimeString([], {
|
|
105
|
+
hour: '2-digit',
|
|
106
|
+
minute: '2-digit'
|
|
107
|
+
})
|
|
108
|
+
]
|
|
109
|
+
}),
|
|
110
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
111
|
+
children: [
|
|
112
|
+
t('reservation:dashboardStatus'),
|
|
113
|
+
" ",
|
|
114
|
+
nextAppointment.status
|
|
115
|
+
]
|
|
116
|
+
})
|
|
117
|
+
]
|
|
118
|
+
}) : /*#__PURE__*/ _jsx("p", {
|
|
119
|
+
className: styles.noData,
|
|
120
|
+
children: t('reservation:dashboardNoUpcoming')
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=DashboardWidgetServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/DashboardWidget/DashboardWidgetServer.tsx"],"sourcesContent":["import type { WidgetServerProps } from 'payload'\n\nimport type { PluginT } from '../../translations/index.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 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 const upcoming = todayReservations.filter(\n (r: Record<string, unknown>) =>\n r.status !== 'completed' && r.status !== 'cancelled' && r.status !== 'no-show' &&\n new Date(r.startTime as string) > now,\n ).length\n const completed = todayReservations.filter(\n (r: Record<string, unknown>) => r.status === 'completed',\n ).length\n const cancelled = todayReservations.filter(\n (r: Record<string, unknown>) => r.status === 'cancelled',\n ).length\n\n const nextAppointment = todayReservations.find(\n (r: Record<string, unknown>) =>\n r.status !== 'completed' && r.status !== 'cancelled' && r.status !== 'no-show' &&\n 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}>{upcoming}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardUpcoming')}</span>\n </div>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{completed}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardCompleted')}</span>\n </div>\n <div className={styles.statCard}>\n <span className={styles.statValue}>{cancelled}</span>\n <span className={styles.statLabel}>{t('reservation:dashboardCancelled')}</span>\n </div>\n </div>\n {nextAppointment ? (\n <div className={styles.nextAppointment}>\n <strong>{t('reservation:dashboardNextAppointment')}</strong>\n <p>\n {t('reservation:dashboardTime')} {new Date(nextAppointment.startTime as string).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}\n </p>\n <p>{t('reservation:dashboardStatus')} {nextAppointment.status as string}</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","now","Date","startOfDay","getFullYear","getMonth","getDate","endOfDay","docs","todayReservations","find","collection","reservations","limit","sort","where","startTime","greater_than_equal","toISOString","less_than","total","length","upcoming","filter","r","status","completed","cancelled","nextAppointment","div","className","wrapper","h3","title","statsGrid","statCard","span","statValue","statLabel","strong","p","toLocaleTimeString","hour","minute","noData"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReservationPluginConfig, ResolvedReservationPluginConfig } from './types.js';
|
|
2
|
+
export declare const DEFAULT_SLUGS: {
|
|
3
|
+
readonly reservations: "reservations";
|
|
4
|
+
readonly resources: "reservation-resources";
|
|
5
|
+
readonly schedules: "reservation-schedules";
|
|
6
|
+
readonly services: "reservation-services";
|
|
7
|
+
};
|
|
8
|
+
export declare const DEFAULT_ADMIN_GROUP = "Reservations";
|
|
9
|
+
export declare const DEFAULT_BUFFER_TIME = 0;
|
|
10
|
+
export declare const DEFAULT_CANCELLATION_NOTICE_PERIOD = 24;
|
|
11
|
+
export declare const DEFAULT_USER_COLLECTION = "users";
|
|
12
|
+
export declare function resolveConfig(pluginOptions: ReservationPluginConfig): ResolvedReservationPluginConfig;
|
package/dist/defaults.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const DEFAULT_SLUGS = {
|
|
2
|
+
reservations: 'reservations',
|
|
3
|
+
resources: 'reservation-resources',
|
|
4
|
+
schedules: 'reservation-schedules',
|
|
5
|
+
services: 'reservation-services'
|
|
6
|
+
};
|
|
7
|
+
export const DEFAULT_ADMIN_GROUP = 'Reservations';
|
|
8
|
+
export const DEFAULT_BUFFER_TIME = 0;
|
|
9
|
+
export const DEFAULT_CANCELLATION_NOTICE_PERIOD = 24;
|
|
10
|
+
export const DEFAULT_USER_COLLECTION = 'users';
|
|
11
|
+
export function resolveConfig(pluginOptions) {
|
|
12
|
+
return {
|
|
13
|
+
access: pluginOptions.access ?? {},
|
|
14
|
+
adminGroup: pluginOptions.adminGroup ?? DEFAULT_ADMIN_GROUP,
|
|
15
|
+
cancellationNoticePeriod: pluginOptions.cancellationNoticePeriod ?? DEFAULT_CANCELLATION_NOTICE_PERIOD,
|
|
16
|
+
defaultBufferTime: pluginOptions.defaultBufferTime ?? DEFAULT_BUFFER_TIME,
|
|
17
|
+
disabled: pluginOptions.disabled ?? false,
|
|
18
|
+
localized: false,
|
|
19
|
+
slugs: {
|
|
20
|
+
reservations: pluginOptions.slugs?.reservations ?? DEFAULT_SLUGS.reservations,
|
|
21
|
+
resources: pluginOptions.slugs?.resources ?? DEFAULT_SLUGS.resources,
|
|
22
|
+
schedules: pluginOptions.slugs?.schedules ?? DEFAULT_SLUGS.schedules,
|
|
23
|
+
services: pluginOptions.slugs?.services ?? DEFAULT_SLUGS.services
|
|
24
|
+
},
|
|
25
|
+
userCollection: pluginOptions.userCollection ?? DEFAULT_USER_COLLECTION
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["import type { ReservationPluginConfig, ResolvedReservationPluginConfig } from './types.js'\n\nexport const DEFAULT_SLUGS = {\n reservations: 'reservations',\n resources: 'reservation-resources',\n schedules: 'reservation-schedules',\n services: 'reservation-services',\n} as const\n\nexport const DEFAULT_ADMIN_GROUP = 'Reservations'\nexport const DEFAULT_BUFFER_TIME = 0\nexport const DEFAULT_CANCELLATION_NOTICE_PERIOD = 24\nexport const DEFAULT_USER_COLLECTION = 'users'\n\nexport function resolveConfig(\n pluginOptions: ReservationPluginConfig,\n): ResolvedReservationPluginConfig {\n return {\n access: pluginOptions.access ?? {},\n adminGroup: pluginOptions.adminGroup ?? DEFAULT_ADMIN_GROUP,\n cancellationNoticePeriod:\n pluginOptions.cancellationNoticePeriod ?? DEFAULT_CANCELLATION_NOTICE_PERIOD,\n defaultBufferTime: pluginOptions.defaultBufferTime ?? DEFAULT_BUFFER_TIME,\n disabled: pluginOptions.disabled ?? false,\n localized: false,\n slugs: {\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 userCollection: pluginOptions.userCollection ?? DEFAULT_USER_COLLECTION,\n }\n}\n"],"names":["DEFAULT_SLUGS","reservations","resources","schedules","services","DEFAULT_ADMIN_GROUP","DEFAULT_BUFFER_TIME","DEFAULT_CANCELLATION_NOTICE_PERIOD","DEFAULT_USER_COLLECTION","resolveConfig","pluginOptions","access","adminGroup","cancellationNoticePeriod","defaultBufferTime","disabled","localized","slugs","userCollection"],"mappings":"AAEA,OAAO,MAAMA,gBAAgB;IAC3BC,cAAc;IACdC,WAAW;IACXC,WAAW;IACXC,UAAU;AACZ,EAAU;AAEV,OAAO,MAAMC,sBAAsB,eAAc;AACjD,OAAO,MAAMC,sBAAsB,EAAC;AACpC,OAAO,MAAMC,qCAAqC,GAAE;AACpD,OAAO,MAAMC,0BAA0B,QAAO;AAE9C,OAAO,SAASC,cACdC,aAAsC;IAEtC,OAAO;QACLC,QAAQD,cAAcC,MAAM,IAAI,CAAC;QACjCC,YAAYF,cAAcE,UAAU,IAAIP;QACxCQ,0BACEH,cAAcG,wBAAwB,IAAIN;QAC5CO,mBAAmBJ,cAAcI,iBAAiB,IAAIR;QACtDS,UAAUL,cAAcK,QAAQ,IAAI;QACpCC,WAAW;QACXC,OAAO;YACLhB,cAAcS,cAAcO,KAAK,EAAEhB,gBAAgBD,cAAcC,YAAY;YAC7EC,WAAWQ,cAAcO,KAAK,EAAEf,aAAaF,cAAcE,SAAS;YACpEC,WAAWO,cAAcO,KAAK,EAAEd,aAAaH,cAAcG,SAAS;YACpEC,UAAUM,cAAcO,KAAK,EAAEb,YAAYJ,cAAcI,QAAQ;QACnE;QACAc,gBAAgBR,cAAcQ,cAAc,IAAIV;IAClD;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { AvailabilityOverview } from '../components/AvailabilityOverview/index.js'\nexport { CalendarView } from '../components/CalendarView/index.js'\n"],"names":["AvailabilityOverview","CalendarView"],"mappings":"AAAA,SAASA,oBAAoB,QAAQ,8CAA6C;AAClF,SAASC,YAAY,QAAQ,sCAAqC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DashboardWidgetServer } from '../components/DashboardWidget/DashboardWidgetServer.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exports/rsc.ts"],"sourcesContent":["export { DashboardWidgetServer } from '../components/DashboardWidget/DashboardWidgetServer.js'\n"],"names":["DashboardWidgetServer"],"mappings":"AAAA,SAASA,qBAAqB,QAAQ,yDAAwD"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { calculateEndTime } from './reservations/calculateEndTime.js';
|
|
2
|
+
export { validateCancellation } from './reservations/validateCancellation.js';
|
|
3
|
+
export { validateConflicts } from './reservations/validateConflicts.js';
|
|
4
|
+
export { validateStatusTransition } from './reservations/validateStatusTransition.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { calculateEndTime } from './reservations/calculateEndTime.js';
|
|
2
|
+
export { validateCancellation } from './reservations/validateCancellation.js';
|
|
3
|
+
export { validateConflicts } from './reservations/validateConflicts.js';
|
|
4
|
+
export { validateStatusTransition } from './reservations/validateStatusTransition.js';
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/index.ts"],"sourcesContent":["export { calculateEndTime } from './reservations/calculateEndTime.js'\nexport { validateCancellation } from './reservations/validateCancellation.js'\nexport { validateConflicts } from './reservations/validateConflicts.js'\nexport { validateStatusTransition } from './reservations/validateStatusTransition.js'\n"],"names":["calculateEndTime","validateCancellation","validateConflicts","validateStatusTransition"],"mappings":"AAAA,SAASA,gBAAgB,QAAQ,qCAAoC;AACrE,SAASC,oBAAoB,QAAQ,yCAAwC;AAC7E,SAASC,iBAAiB,QAAQ,sCAAqC;AACvE,SAASC,wBAAwB,QAAQ,6CAA4C"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { addMinutes } from '../../utilities/slotUtils.js';
|
|
2
|
+
export const calculateEndTime = (config)=>async ({ context, data, req })=>{
|
|
3
|
+
if (context?.skipReservationHooks) {
|
|
4
|
+
return data;
|
|
5
|
+
}
|
|
6
|
+
if (!data?.startTime || !data?.service) {
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
9
|
+
const serviceId = typeof data.service === 'object' ? data.service.id : data.service;
|
|
10
|
+
const service = await req.payload.findByID({
|
|
11
|
+
id: serviceId,
|
|
12
|
+
collection: config.slugs.services,
|
|
13
|
+
req
|
|
14
|
+
});
|
|
15
|
+
if (service?.duration) {
|
|
16
|
+
const startDate = new Date(data.startTime);
|
|
17
|
+
data.endTime = addMinutes(startDate, service.duration).toISOString();
|
|
18
|
+
}
|
|
19
|
+
return data;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=calculateEndTime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/reservations/calculateEndTime.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nimport { addMinutes } from '../../utilities/slotUtils.js'\n\nexport const calculateEndTime =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n async ({ context, data, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (!data?.startTime || !data?.service) {return data}\n\n const serviceId = typeof data.service === 'object' ? data.service.id : data.service\n\n const service = await req.payload.findByID({\n id: serviceId,\n collection: config.slugs.services as 'reservation-services',\n req,\n })\n\n if (service?.duration) {\n const startDate = new Date(data.startTime)\n data.endTime = addMinutes(startDate, service.duration).toISOString()\n }\n\n return data\n }\n"],"names":["addMinutes","calculateEndTime","config","context","data","req","skipReservationHooks","startTime","service","serviceId","id","payload","findByID","collection","slugs","services","duration","startDate","Date","endTime","toISOString"],"mappings":"AAIA,SAASA,UAAU,QAAQ,+BAA8B;AAEzD,OAAO,MAAMC,mBACX,CAACC,SACD,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,GAAG,EAAE;QAC3B,IAAIF,SAASG,sBAAsB;YAAC,OAAOF;QAAI;QAE/C,IAAI,CAACA,MAAMG,aAAa,CAACH,MAAMI,SAAS;YAAC,OAAOJ;QAAI;QAEpD,MAAMK,YAAY,OAAOL,KAAKI,OAAO,KAAK,WAAWJ,KAAKI,OAAO,CAACE,EAAE,GAAGN,KAAKI,OAAO;QAEnF,MAAMA,UAAU,MAAMH,IAAIM,OAAO,CAACC,QAAQ,CAAC;YACzCF,IAAID;YACJI,YAAYX,OAAOY,KAAK,CAACC,QAAQ;YACjCV;QACF;QAEA,IAAIG,SAASQ,UAAU;YACrB,MAAMC,YAAY,IAAIC,KAAKd,KAAKG,SAAS;YACzCH,KAAKe,OAAO,GAAGnB,WAAWiB,WAAWT,QAAQQ,QAAQ,EAAEI,WAAW;QACpE;QAEA,OAAOhB;IACT,EAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ValidationError } from 'payload';
|
|
2
|
+
import { hoursUntil } from '../../utilities/slotUtils.js';
|
|
3
|
+
export const validateCancellation = (config)=>({ context, data, operation, originalDoc, req })=>{
|
|
4
|
+
if (context?.skipReservationHooks) {
|
|
5
|
+
return data;
|
|
6
|
+
}
|
|
7
|
+
if (operation !== 'update') {
|
|
8
|
+
return data;
|
|
9
|
+
}
|
|
10
|
+
const newStatus = data?.status;
|
|
11
|
+
const previousStatus = originalDoc?.status;
|
|
12
|
+
// Only check when transitioning to cancelled
|
|
13
|
+
if (newStatus !== 'cancelled' || previousStatus === 'cancelled') {
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
const startTime = data?.startTime ?? originalDoc?.startTime;
|
|
17
|
+
if (!startTime) {
|
|
18
|
+
return data;
|
|
19
|
+
}
|
|
20
|
+
const startDate = new Date(startTime);
|
|
21
|
+
const hours = hoursUntil(startDate);
|
|
22
|
+
if (hours < config.cancellationNoticePeriod) {
|
|
23
|
+
throw new ValidationError({
|
|
24
|
+
errors: [
|
|
25
|
+
{
|
|
26
|
+
message: req.t('reservation:errorCancellationNotice', {
|
|
27
|
+
hours: String(Math.round(hours)),
|
|
28
|
+
period: String(config.cancellationNoticePeriod)
|
|
29
|
+
}),
|
|
30
|
+
path: 'status'
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return data;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=validateCancellation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/reservations/validateCancellation.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nimport { hoursUntil } from '../../utilities/slotUtils.js'\n\nexport const validateCancellation =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n ({ context, data, operation, originalDoc, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (operation !== 'update') {return data}\n\n const newStatus = data?.status\n const previousStatus = originalDoc?.status\n\n // Only check when transitioning to cancelled\n if (newStatus !== 'cancelled' || previousStatus === 'cancelled') {return data}\n\n const startTime = data?.startTime ?? originalDoc?.startTime\n if (!startTime) {return data}\n\n const startDate = new Date(startTime)\n const hours = hoursUntil(startDate)\n\n if (hours < config.cancellationNoticePeriod) {\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorCancellationNotice', {\n hours: String(Math.round(hours)),\n period: String(config.cancellationNoticePeriod),\n }),\n path: 'status',\n },\n ],\n })\n }\n\n return data\n }\n"],"names":["ValidationError","hoursUntil","validateCancellation","config","context","data","operation","originalDoc","req","skipReservationHooks","newStatus","status","previousStatus","startTime","startDate","Date","hours","cancellationNoticePeriod","errors","message","t","String","Math","round","period","path"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,UAAU,QAAQ,+BAA8B;AAEzD,OAAO,MAAMC,uBACX,CAACC,SACD,CAAC,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QAC7C,IAAIJ,SAASK,sBAAsB;YAAC,OAAOJ;QAAI;QAE/C,IAAIC,cAAc,UAAU;YAAC,OAAOD;QAAI;QAExC,MAAMK,YAAYL,MAAMM;QACxB,MAAMC,iBAAiBL,aAAaI;QAEpC,6CAA6C;QAC7C,IAAID,cAAc,eAAeE,mBAAmB,aAAa;YAAC,OAAOP;QAAI;QAE7E,MAAMQ,YAAYR,MAAMQ,aAAaN,aAAaM;QAClD,IAAI,CAACA,WAAW;YAAC,OAAOR;QAAI;QAE5B,MAAMS,YAAY,IAAIC,KAAKF;QAC3B,MAAMG,QAAQf,WAAWa;QAEzB,IAAIE,QAAQb,OAAOc,wBAAwB,EAAE;YAC3C,MAAM,IAAIjB,gBAAgB;gBACxBkB,QAAQ;oBACN;wBACEC,SAAS,AAACX,IAAIY,CAAC,CAAa,uCAAuC;4BACjEJ,OAAOK,OAAOC,KAAKC,KAAK,CAACP;4BACzBQ,QAAQH,OAAOlB,OAAOc,wBAAwB;wBAChD;wBACAQ,MAAM;oBACR;iBACD;YACH;QACF;QAEA,OAAOpB;IACT,EAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ValidationError } from 'payload';
|
|
2
|
+
import { computeBlockedWindow } from '../../utilities/slotUtils.js';
|
|
3
|
+
export const validateConflicts = (config)=>async ({ context, data, operation, originalDoc, req })=>{
|
|
4
|
+
if (context?.skipReservationHooks) {
|
|
5
|
+
return data;
|
|
6
|
+
}
|
|
7
|
+
if (!data?.startTime || !data?.endTime || !data?.resource) {
|
|
8
|
+
return data;
|
|
9
|
+
}
|
|
10
|
+
const serviceId = typeof data.service === 'object' ? data.service.id : data.service;
|
|
11
|
+
let bufferBefore = config.defaultBufferTime;
|
|
12
|
+
let bufferAfter = config.defaultBufferTime;
|
|
13
|
+
if (serviceId) {
|
|
14
|
+
try {
|
|
15
|
+
const service = await req.payload.findByID({
|
|
16
|
+
id: serviceId,
|
|
17
|
+
collection: config.slugs.services,
|
|
18
|
+
req
|
|
19
|
+
});
|
|
20
|
+
if (service) {
|
|
21
|
+
bufferBefore = service.bufferTimeBefore ?? config.defaultBufferTime;
|
|
22
|
+
bufferAfter = service.bufferTimeAfter ?? config.defaultBufferTime;
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
// Use defaults if service lookup fails
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const startTime = new Date(data.startTime);
|
|
29
|
+
const endTime = new Date(data.endTime);
|
|
30
|
+
const { effectiveEnd, effectiveStart } = computeBlockedWindow(startTime, endTime, bufferBefore, bufferAfter);
|
|
31
|
+
const resourceId = typeof data.resource === 'object' ? data.resource.id : data.resource;
|
|
32
|
+
const where = {
|
|
33
|
+
and: [
|
|
34
|
+
{
|
|
35
|
+
resource: {
|
|
36
|
+
equals: resourceId
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
status: {
|
|
41
|
+
not_in: [
|
|
42
|
+
'cancelled',
|
|
43
|
+
'no-show'
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
startTime: {
|
|
49
|
+
less_than: effectiveEnd.toISOString()
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
endTime: {
|
|
54
|
+
greater_than: effectiveStart.toISOString()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
};
|
|
59
|
+
// Exclude self on update
|
|
60
|
+
if (operation === 'update' && originalDoc?.id) {
|
|
61
|
+
;
|
|
62
|
+
where.and.push({
|
|
63
|
+
id: {
|
|
64
|
+
not_equals: originalDoc.id
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const { totalDocs } = await req.payload.count({
|
|
69
|
+
collection: config.slugs.reservations,
|
|
70
|
+
req,
|
|
71
|
+
where
|
|
72
|
+
});
|
|
73
|
+
if (totalDocs > 0) {
|
|
74
|
+
throw new ValidationError({
|
|
75
|
+
errors: [
|
|
76
|
+
{
|
|
77
|
+
message: req.t('reservation:errorConflict'),
|
|
78
|
+
path: 'startTime'
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return data;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=validateConflicts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/reservations/validateConflicts.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook, Where } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nimport { computeBlockedWindow } from '../../utilities/slotUtils.js'\n\nexport const validateConflicts =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n async ({ context, data, operation, originalDoc, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (!data?.startTime || !data?.endTime || !data?.resource) {return data}\n\n const serviceId = typeof data.service === 'object' ? data.service.id : data.service\n\n let bufferBefore = config.defaultBufferTime\n let bufferAfter = config.defaultBufferTime\n\n if (serviceId) {\n try {\n const service = await req.payload.findByID({\n id: serviceId,\n collection: config.slugs.services as 'reservation-services',\n req,\n })\n if (service) {\n bufferBefore = (service.bufferTimeBefore as number) ?? config.defaultBufferTime\n bufferAfter = (service.bufferTimeAfter as number) ?? config.defaultBufferTime\n }\n } catch {\n // Use defaults if service lookup fails\n }\n }\n\n const startTime = new Date(data.startTime)\n const endTime = new Date(data.endTime)\n const { effectiveEnd, effectiveStart } = computeBlockedWindow(\n startTime,\n endTime,\n bufferBefore,\n bufferAfter,\n )\n\n const resourceId = typeof data.resource === 'object' ? data.resource.id : data.resource\n\n const where: Where = {\n and: [\n { resource: { equals: resourceId } },\n {\n status: {\n not_in: ['cancelled', 'no-show'],\n },\n },\n { startTime: { less_than: effectiveEnd.toISOString() } },\n { endTime: { greater_than: effectiveStart.toISOString() } },\n ],\n }\n\n // Exclude self on update\n if (operation === 'update' && originalDoc?.id) {\n ;(where.and as Where[]).push({ id: { not_equals: originalDoc.id } })\n }\n\n const { totalDocs } = await req.payload.count({\n collection: config.slugs.reservations as 'reservations',\n req,\n where,\n })\n\n if (totalDocs > 0) {\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorConflict'),\n path: 'startTime',\n },\n ],\n })\n }\n\n return data\n }\n"],"names":["ValidationError","computeBlockedWindow","validateConflicts","config","context","data","operation","originalDoc","req","skipReservationHooks","startTime","endTime","resource","serviceId","service","id","bufferBefore","defaultBufferTime","bufferAfter","payload","findByID","collection","slugs","services","bufferTimeBefore","bufferTimeAfter","Date","effectiveEnd","effectiveStart","resourceId","where","and","equals","status","not_in","less_than","toISOString","greater_than","push","not_equals","totalDocs","count","reservations","errors","message","t","path"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,oBAAoB,QAAQ,+BAA8B;AAEnE,OAAO,MAAMC,oBACX,CAACC,SACD,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QACnD,IAAIJ,SAASK,sBAAsB;YAAC,OAAOJ;QAAI;QAE/C,IAAI,CAACA,MAAMK,aAAa,CAACL,MAAMM,WAAW,CAACN,MAAMO,UAAU;YAAC,OAAOP;QAAI;QAEvE,MAAMQ,YAAY,OAAOR,KAAKS,OAAO,KAAK,WAAWT,KAAKS,OAAO,CAACC,EAAE,GAAGV,KAAKS,OAAO;QAEnF,IAAIE,eAAeb,OAAOc,iBAAiB;QAC3C,IAAIC,cAAcf,OAAOc,iBAAiB;QAE1C,IAAIJ,WAAW;YACb,IAAI;gBACF,MAAMC,UAAU,MAAMN,IAAIW,OAAO,CAACC,QAAQ,CAAC;oBACzCL,IAAIF;oBACJQ,YAAYlB,OAAOmB,KAAK,CAACC,QAAQ;oBACjCf;gBACF;gBACA,IAAIM,SAAS;oBACXE,eAAe,AAACF,QAAQU,gBAAgB,IAAerB,OAAOc,iBAAiB;oBAC/EC,cAAc,AAACJ,QAAQW,eAAe,IAAetB,OAAOc,iBAAiB;gBAC/E;YACF,EAAE,OAAM;YACN,uCAAuC;YACzC;QACF;QAEA,MAAMP,YAAY,IAAIgB,KAAKrB,KAAKK,SAAS;QACzC,MAAMC,UAAU,IAAIe,KAAKrB,KAAKM,OAAO;QACrC,MAAM,EAAEgB,YAAY,EAAEC,cAAc,EAAE,GAAG3B,qBACvCS,WACAC,SACAK,cACAE;QAGF,MAAMW,aAAa,OAAOxB,KAAKO,QAAQ,KAAK,WAAWP,KAAKO,QAAQ,CAACG,EAAE,GAAGV,KAAKO,QAAQ;QAEvF,MAAMkB,QAAe;YACnBC,KAAK;gBACH;oBAAEnB,UAAU;wBAAEoB,QAAQH;oBAAW;gBAAE;gBACnC;oBACEI,QAAQ;wBACNC,QAAQ;4BAAC;4BAAa;yBAAU;oBAClC;gBACF;gBACA;oBAAExB,WAAW;wBAAEyB,WAAWR,aAAaS,WAAW;oBAAG;gBAAE;gBACvD;oBAAEzB,SAAS;wBAAE0B,cAAcT,eAAeQ,WAAW;oBAAG;gBAAE;aAC3D;QACH;QAEA,yBAAyB;QACzB,IAAI9B,cAAc,YAAYC,aAAaQ,IAAI;;YAC3Ce,MAAMC,GAAG,CAAaO,IAAI,CAAC;gBAAEvB,IAAI;oBAAEwB,YAAYhC,YAAYQ,EAAE;gBAAC;YAAE;QACpE;QAEA,MAAM,EAAEyB,SAAS,EAAE,GAAG,MAAMhC,IAAIW,OAAO,CAACsB,KAAK,CAAC;YAC5CpB,YAAYlB,OAAOmB,KAAK,CAACoB,YAAY;YACrClC;YACAsB;QACF;QAEA,IAAIU,YAAY,GAAG;YACjB,MAAM,IAAIxC,gBAAgB;gBACxB2C,QAAQ;oBACN;wBACEC,SAAS,AAACpC,IAAIqC,CAAC,CAAa;wBAC5BC,MAAM;oBACR;iBACD;YACH;QACF;QAEA,OAAOzC;IACT,EAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ValidationError } from 'payload';
|
|
2
|
+
import { VALID_STATUS_TRANSITIONS } from '../../types.js';
|
|
3
|
+
export const validateStatusTransition = ()=>({ context, data, operation, originalDoc, req })=>{
|
|
4
|
+
if (context?.skipReservationHooks) {
|
|
5
|
+
return data;
|
|
6
|
+
}
|
|
7
|
+
const newStatus = data?.status;
|
|
8
|
+
if (operation === 'create') {
|
|
9
|
+
const isAdmin = Boolean(req.user);
|
|
10
|
+
const allowedOnCreate = isAdmin ? [
|
|
11
|
+
'pending',
|
|
12
|
+
'confirmed'
|
|
13
|
+
] : [
|
|
14
|
+
'pending'
|
|
15
|
+
];
|
|
16
|
+
if (newStatus && !allowedOnCreate.includes(newStatus)) {
|
|
17
|
+
const allowed = allowedOnCreate.map((s)=>`"${s}"`).join(' or ');
|
|
18
|
+
throw new ValidationError({
|
|
19
|
+
errors: [
|
|
20
|
+
{
|
|
21
|
+
message: req.t('reservation:errorInvalidCreateStatus', {
|
|
22
|
+
allowed
|
|
23
|
+
}),
|
|
24
|
+
path: 'status'
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
// On update
|
|
32
|
+
if (operation === 'update' && newStatus) {
|
|
33
|
+
const previousStatus = originalDoc?.status;
|
|
34
|
+
if (previousStatus && previousStatus !== newStatus) {
|
|
35
|
+
const allowed = VALID_STATUS_TRANSITIONS[previousStatus];
|
|
36
|
+
if (!allowed || !allowed.includes(newStatus)) {
|
|
37
|
+
throw new ValidationError({
|
|
38
|
+
errors: [
|
|
39
|
+
{
|
|
40
|
+
message: req.t('reservation:errorInvalidTransition', {
|
|
41
|
+
from: previousStatus,
|
|
42
|
+
to: newStatus
|
|
43
|
+
}),
|
|
44
|
+
path: 'status'
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return data;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=validateStatusTransition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/reservations/validateStatusTransition.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { ReservationStatus } from '../../types.js'\n\nimport { VALID_STATUS_TRANSITIONS } from '../../types.js'\n\nexport const validateStatusTransition = (): CollectionBeforeChangeHook =>\n ({ context, data, operation, originalDoc, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n const newStatus = data?.status as ReservationStatus | undefined\n\n if (operation === 'create') {\n const isAdmin = Boolean(req.user)\n const allowedOnCreate: ReservationStatus[] = isAdmin\n ? ['pending', 'confirmed']\n : ['pending']\n\n if (newStatus && !allowedOnCreate.includes(newStatus)) {\n const allowed = allowedOnCreate.map((s) => `\"${s}\"`).join(' or ')\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorInvalidCreateStatus', { allowed }),\n path: 'status',\n },\n ],\n })\n }\n return data\n }\n\n // On update\n if (operation === 'update' && newStatus) {\n const previousStatus = originalDoc?.status as ReservationStatus | undefined\n\n if (previousStatus && previousStatus !== newStatus) {\n const allowed = VALID_STATUS_TRANSITIONS[previousStatus]\n if (!allowed || !allowed.includes(newStatus)) {\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorInvalidTransition', {\n from: previousStatus,\n to: newStatus,\n }),\n path: 'status',\n },\n ],\n })\n }\n }\n }\n\n return data\n }\n"],"names":["ValidationError","VALID_STATUS_TRANSITIONS","validateStatusTransition","context","data","operation","originalDoc","req","skipReservationHooks","newStatus","status","isAdmin","Boolean","user","allowedOnCreate","includes","allowed","map","s","join","errors","message","t","path","previousStatus","from","to"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,wBAAwB,QAAQ,iBAAgB;AAEzD,OAAO,MAAMC,2BAA2B,IACtC,CAAC,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QAC7C,IAAIJ,SAASK,sBAAsB;YAAC,OAAOJ;QAAI;QAE/C,MAAMK,YAAYL,MAAMM;QAExB,IAAIL,cAAc,UAAU;YAC1B,MAAMM,UAAUC,QAAQL,IAAIM,IAAI;YAChC,MAAMC,kBAAuCH,UACzC;gBAAC;gBAAW;aAAY,GACxB;gBAAC;aAAU;YAEf,IAAIF,aAAa,CAACK,gBAAgBC,QAAQ,CAACN,YAAY;gBACrD,MAAMO,UAAUF,gBAAgBG,GAAG,CAAC,CAACC,IAAM,CAAC,CAAC,EAAEA,EAAE,CAAC,CAAC,EAAEC,IAAI,CAAC;gBAC1D,MAAM,IAAInB,gBAAgB;oBACxBoB,QAAQ;wBACN;4BACEC,SAAS,AAACd,IAAIe,CAAC,CAAa,wCAAwC;gCAAEN;4BAAQ;4BAC9EO,MAAM;wBACR;qBACD;gBACH;YACF;YACA,OAAOnB;QACT;QAEA,YAAY;QACZ,IAAIC,cAAc,YAAYI,WAAW;YACvC,MAAMe,iBAAiBlB,aAAaI;YAEpC,IAAIc,kBAAkBA,mBAAmBf,WAAW;gBAClD,MAAMO,UAAUf,wBAAwB,CAACuB,eAAe;gBACxD,IAAI,CAACR,WAAW,CAACA,QAAQD,QAAQ,CAACN,YAAY;oBAC5C,MAAM,IAAIT,gBAAgB;wBACxBoB,QAAQ;4BACN;gCACEC,SAAS,AAACd,IAAIe,CAAC,CAAa,sCAAsC;oCAChEG,MAAMD;oCACNE,IAAIjB;gCACN;gCACAc,MAAM;4BACR;yBACD;oBACH;gBACF;YACF;QACF;QAEA,OAAOnB;IACT,EAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { reservationPlugin } from './plugin.js'\nexport type { ReservationPluginConfig, ResolvedReservationPluginConfig } from './types.js'\n"],"names":["reservationPlugin"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,cAAa"}
|
package/dist/plugin.d.ts
ADDED
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { deepMergeSimple } from 'payload/shared';
|
|
2
|
+
import { createReservationsCollection } from './collections/Reservations.js';
|
|
3
|
+
import { createResourcesCollection } from './collections/Resources.js';
|
|
4
|
+
import { createSchedulesCollection } from './collections/Schedules.js';
|
|
5
|
+
import { createServicesCollection } from './collections/Services.js';
|
|
6
|
+
import { resolveConfig } from './defaults.js';
|
|
7
|
+
import { translations } from './translations/index.js';
|
|
8
|
+
/** Check whether a top-level field with the given name already exists */ const hasField = (fields, name)=>fields.some((f)=>'name' in f && f.name === name);
|
|
9
|
+
export const reservationPlugin = (pluginOptions = {})=>(config)=>{
|
|
10
|
+
const resolved = resolveConfig(pluginOptions);
|
|
11
|
+
// Detect localization from the Payload config
|
|
12
|
+
if (config.localization) {
|
|
13
|
+
resolved.localized = true;
|
|
14
|
+
}
|
|
15
|
+
if (!config.collections) {
|
|
16
|
+
config.collections = [];
|
|
17
|
+
}
|
|
18
|
+
if (resolved.disabled) {
|
|
19
|
+
return config;
|
|
20
|
+
}
|
|
21
|
+
// Add the 4 plugin collections
|
|
22
|
+
config.collections.push(createServicesCollection(resolved), createResourcesCollection(resolved), createSchedulesCollection(resolved), createReservationsCollection(resolved));
|
|
23
|
+
// Extend the existing user collection with customer fields
|
|
24
|
+
const userCol = config.collections.find((c)=>c.slug === resolved.userCollection);
|
|
25
|
+
if (userCol) {
|
|
26
|
+
const fieldsToAdd = [
|
|
27
|
+
{
|
|
28
|
+
name: 'name',
|
|
29
|
+
type: 'text',
|
|
30
|
+
maxLength: 200
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'phone',
|
|
34
|
+
type: 'text',
|
|
35
|
+
maxLength: 50
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'notes',
|
|
39
|
+
type: 'textarea'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'bookings',
|
|
43
|
+
type: 'join',
|
|
44
|
+
collection: resolved.slugs.reservations,
|
|
45
|
+
on: 'customer'
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
for (const field of fieldsToAdd){
|
|
49
|
+
if (!hasField(userCol.fields, field.name)) {
|
|
50
|
+
userCol.fields.push(field);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
console.warn(`[payload-reserve] Could not find collection "${resolved.userCollection}" to extend with customer fields. ` + 'Make sure your Payload config defines this collection before the reservation plugin runs.');
|
|
56
|
+
}
|
|
57
|
+
// Set up admin configuration
|
|
58
|
+
if (!config.admin) {
|
|
59
|
+
config.admin = {};
|
|
60
|
+
}
|
|
61
|
+
if (!config.admin.components) {
|
|
62
|
+
config.admin.components = {};
|
|
63
|
+
}
|
|
64
|
+
// Store slugs in admin custom for component access
|
|
65
|
+
if (!config.admin.custom) {
|
|
66
|
+
config.admin.custom = {};
|
|
67
|
+
}
|
|
68
|
+
config.admin.custom.reservationSlugs = {
|
|
69
|
+
...resolved.slugs,
|
|
70
|
+
userCollection: resolved.userCollection
|
|
71
|
+
};
|
|
72
|
+
// Add dashboard widget
|
|
73
|
+
if (!config.admin.dashboard) {
|
|
74
|
+
config.admin.dashboard = {
|
|
75
|
+
widgets: []
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (!config.admin.dashboard.widgets) {
|
|
79
|
+
config.admin.dashboard.widgets = [];
|
|
80
|
+
}
|
|
81
|
+
config.admin.dashboard.widgets.push({
|
|
82
|
+
slug: 'reservation-todays-reservations',
|
|
83
|
+
ComponentPath: 'payload-reserve/rsc#DashboardWidgetServer',
|
|
84
|
+
label: 'Today\'s Reservations',
|
|
85
|
+
maxWidth: 'large',
|
|
86
|
+
minWidth: 'medium'
|
|
87
|
+
});
|
|
88
|
+
// Add availability overview as custom admin view
|
|
89
|
+
if (!config.admin.components.views) {
|
|
90
|
+
config.admin.components.views = {};
|
|
91
|
+
}
|
|
92
|
+
;
|
|
93
|
+
config.admin.components.views['reservation-availability'] = {
|
|
94
|
+
Component: 'payload-reserve/client#AvailabilityOverview',
|
|
95
|
+
path: '/reservation-availability'
|
|
96
|
+
};
|
|
97
|
+
// Merge plugin translations (user translations take precedence)
|
|
98
|
+
if (!config.i18n) {
|
|
99
|
+
config.i18n = {};
|
|
100
|
+
}
|
|
101
|
+
;
|
|
102
|
+
config.i18n.translations = deepMergeSimple(translations, config.i18n.translations ?? {});
|
|
103
|
+
return config;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=plugin.js.map
|