gorombo-payload-appointments 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 +308 -0
- package/dist/collections/Appointments.d.ts +2 -0
- package/dist/collections/Appointments.js +165 -0
- package/dist/collections/Appointments.js.map +1 -0
- package/dist/collections/GuestCustomers.d.ts +2 -0
- package/dist/collections/GuestCustomers.js +106 -0
- package/dist/collections/GuestCustomers.js.map +1 -0
- package/dist/collections/Services.d.ts +2 -0
- package/dist/collections/Services.js +147 -0
- package/dist/collections/Services.js.map +1 -0
- package/dist/collections/TeamMembers.d.ts +2 -0
- package/dist/collections/TeamMembers.js +184 -0
- package/dist/collections/TeamMembers.js.map +1 -0
- package/dist/components/BeforeDashboardClient.d.ts +2 -0
- package/dist/components/BeforeDashboardClient.js +162 -0
- package/dist/components/BeforeDashboardClient.js.map +1 -0
- package/dist/components/BeforeDashboardServer.d.ts +2 -0
- package/dist/components/BeforeDashboardServer.js +22 -0
- package/dist/components/BeforeDashboardServer.js.map +1 -0
- package/dist/components/BeforeDashboardServer.module.css +5 -0
- package/dist/components/calendar/Calendar.module.css +506 -0
- package/dist/components/calendar/CalendarContainer.d.ts +3 -0
- package/dist/components/calendar/CalendarContainer.js +246 -0
- package/dist/components/calendar/CalendarContainer.js.map +1 -0
- package/dist/components/calendar/DayView.d.ts +3 -0
- package/dist/components/calendar/DayView.js +192 -0
- package/dist/components/calendar/DayView.js.map +1 -0
- package/dist/components/calendar/EventPopover.d.ts +3 -0
- package/dist/components/calendar/EventPopover.js +257 -0
- package/dist/components/calendar/EventPopover.js.map +1 -0
- package/dist/components/calendar/EventRenderer.d.ts +3 -0
- package/dist/components/calendar/EventRenderer.js +76 -0
- package/dist/components/calendar/EventRenderer.js.map +1 -0
- package/dist/components/calendar/WeekView.d.ts +3 -0
- package/dist/components/calendar/WeekView.js +203 -0
- package/dist/components/calendar/WeekView.js.map +1 -0
- package/dist/components/calendar/index.d.ts +6 -0
- package/dist/components/calendar/index.js +7 -0
- package/dist/components/calendar/index.js.map +1 -0
- package/dist/components/calendar/types.d.ts +69 -0
- package/dist/components/calendar/types.js +3 -0
- package/dist/components/calendar/types.js.map +1 -0
- package/dist/endpoints/customEndpointHandler.d.ts +2 -0
- package/dist/endpoints/customEndpointHandler.js +7 -0
- package/dist/endpoints/customEndpointHandler.js.map +1 -0
- package/dist/endpoints/getAvailableSlots.d.ts +12 -0
- package/dist/endpoints/getAvailableSlots.js +291 -0
- package/dist/endpoints/getAvailableSlots.js.map +1 -0
- package/dist/exports/client.d.ts +3 -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/globals/OpeningTimes.d.ts +2 -0
- package/dist/globals/OpeningTimes.js +196 -0
- package/dist/globals/OpeningTimes.js.map +1 -0
- package/dist/hooks/addAdminTitle.d.ts +7 -0
- package/dist/hooks/addAdminTitle.js +86 -0
- package/dist/hooks/addAdminTitle.js.map +1 -0
- package/dist/hooks/sendCustomerEmail.d.ts +6 -0
- package/dist/hooks/sendCustomerEmail.js +351 -0
- package/dist/hooks/sendCustomerEmail.js.map +1 -0
- package/dist/hooks/setEndDateTime.d.ts +6 -0
- package/dist/hooks/setEndDateTime.js +44 -0
- package/dist/hooks/setEndDateTime.js.map +1 -0
- package/dist/hooks/validateCustomerOrGuest.d.ts +6 -0
- package/dist/hooks/validateCustomerOrGuest.js +21 -0
- package/dist/hooks/validateCustomerOrGuest.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +183 -0
- package/dist/index.js.map +1 -0
- package/package.json +135 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Daniel T Sasser II
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Gorombo Appointments Plugin
|
|
2
|
+
|
|
3
|
+
A full-featured appointments, scheduling, and booking plugin for [PayloadCMS 3.x](https://payloadcms.com).
|
|
4
|
+
|
|
5
|
+
[](https://github.com/dansasser/gorombo-appointment-plugin/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/gorombo-payload-appointments)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Admin Calendar Dashboard** - Week/Day view calendar integrated into Payload admin
|
|
12
|
+
- **Services Management** - Define bookable services with duration, pricing, and colors
|
|
13
|
+
- **Team Members** - Assign appointments to team members with availability settings
|
|
14
|
+
- **Guest Customers** - Support for non-registered customers booking appointments
|
|
15
|
+
- **Automatic Scheduling** - Auto-calculates end times based on service duration
|
|
16
|
+
- **Business Hours** - Configure opening times, breaks, and scheduling rules
|
|
17
|
+
- **Email Notifications** - Automatic confirmation and update emails
|
|
18
|
+
- **REST API** - Full headless API for custom frontend booking flows
|
|
19
|
+
- **Available Slots Endpoint** - Query available booking times
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install gorombo-payload-appointments
|
|
25
|
+
# or
|
|
26
|
+
pnpm add gorombo-payload-appointments
|
|
27
|
+
# or
|
|
28
|
+
yarn add gorombo-payload-appointments
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
Add the plugin to your Payload config:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// payload.config.ts
|
|
37
|
+
import { buildConfig } from 'payload'
|
|
38
|
+
import { goromboAppointmentsPlugin } from 'gorombo-payload-appointments'
|
|
39
|
+
|
|
40
|
+
export default buildConfig({
|
|
41
|
+
// ... your config
|
|
42
|
+
plugins: [
|
|
43
|
+
goromboAppointmentsPlugin({
|
|
44
|
+
// Optional configuration
|
|
45
|
+
}),
|
|
46
|
+
],
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Regenerate types and import map:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run generate:types
|
|
54
|
+
npm run generate:importmap
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration Options
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
goromboAppointmentsPlugin({
|
|
61
|
+
// Slug for your media collection (default: 'media')
|
|
62
|
+
mediaCollectionSlug: 'media',
|
|
63
|
+
|
|
64
|
+
// Slug for your users collection (default: 'users')
|
|
65
|
+
usersCollectionSlug: 'users',
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## What Gets Added
|
|
70
|
+
|
|
71
|
+
### Collections
|
|
72
|
+
|
|
73
|
+
| Collection | Slug | Description |
|
|
74
|
+
|------------|------|-------------|
|
|
75
|
+
| Services | `services` | Bookable services with duration, price, and color |
|
|
76
|
+
| Team Members | `team-members` | Staff who can be assigned to appointments |
|
|
77
|
+
| Guest Customers | `guest-customers` | Non-registered booking customers |
|
|
78
|
+
| Appointments | `appointments` | The bookings themselves |
|
|
79
|
+
|
|
80
|
+
### Globals
|
|
81
|
+
|
|
82
|
+
| Global | Slug | Description |
|
|
83
|
+
|--------|------|-------------|
|
|
84
|
+
| Opening Times | `opening-times` | Business hours and scheduling settings |
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
PayloadCMS automatically generates REST API endpoints for all collections. Here's the complete API reference:
|
|
89
|
+
|
|
90
|
+
### Services
|
|
91
|
+
|
|
92
|
+
| Method | Endpoint | Description |
|
|
93
|
+
|--------|----------|-------------|
|
|
94
|
+
| GET | `/api/services` | List all services |
|
|
95
|
+
| GET | `/api/services/:id` | Get a single service |
|
|
96
|
+
| POST | `/api/services` | Create a service |
|
|
97
|
+
| PATCH | `/api/services/:id` | Update a service |
|
|
98
|
+
| DELETE | `/api/services/:id` | Delete a service |
|
|
99
|
+
|
|
100
|
+
**Query examples:**
|
|
101
|
+
```bash
|
|
102
|
+
# Get all active services
|
|
103
|
+
GET /api/services?where[isActive][equals]=true
|
|
104
|
+
|
|
105
|
+
# Get services sorted by price
|
|
106
|
+
GET /api/services?sort=price
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Team Members
|
|
110
|
+
|
|
111
|
+
| Method | Endpoint | Description |
|
|
112
|
+
|--------|----------|-------------|
|
|
113
|
+
| GET | `/api/team-members` | List all team members |
|
|
114
|
+
| GET | `/api/team-members/:id` | Get a single team member |
|
|
115
|
+
| POST | `/api/team-members` | Create a team member |
|
|
116
|
+
| PATCH | `/api/team-members/:id` | Update a team member |
|
|
117
|
+
| DELETE | `/api/team-members/:id` | Delete a team member |
|
|
118
|
+
|
|
119
|
+
**Query examples:**
|
|
120
|
+
```bash
|
|
121
|
+
# Get team members taking appointments
|
|
122
|
+
GET /api/team-members?where[takingAppointments][equals]=true
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Guest Customers
|
|
126
|
+
|
|
127
|
+
| Method | Endpoint | Description |
|
|
128
|
+
|--------|----------|-------------|
|
|
129
|
+
| GET | `/api/guest-customers` | List all guest customers |
|
|
130
|
+
| GET | `/api/guest-customers/:id` | Get a single guest customer |
|
|
131
|
+
| POST | `/api/guest-customers` | Create a guest customer |
|
|
132
|
+
| PATCH | `/api/guest-customers/:id` | Update a guest customer |
|
|
133
|
+
| DELETE | `/api/guest-customers/:id` | Delete a guest customer |
|
|
134
|
+
|
|
135
|
+
**Query examples:**
|
|
136
|
+
```bash
|
|
137
|
+
# Find guest by email
|
|
138
|
+
GET /api/guest-customers?where[email][equals]=john@example.com
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Appointments
|
|
142
|
+
|
|
143
|
+
| Method | Endpoint | Description |
|
|
144
|
+
|--------|----------|-------------|
|
|
145
|
+
| GET | `/api/appointments` | List all appointments |
|
|
146
|
+
| GET | `/api/appointments/:id` | Get a single appointment |
|
|
147
|
+
| POST | `/api/appointments` | Create an appointment |
|
|
148
|
+
| PATCH | `/api/appointments/:id` | Update an appointment |
|
|
149
|
+
| DELETE | `/api/appointments/:id` | Delete an appointment |
|
|
150
|
+
|
|
151
|
+
**Query examples:**
|
|
152
|
+
```bash
|
|
153
|
+
# Get appointments for a specific date range
|
|
154
|
+
GET /api/appointments?where[startDateTime][greater_than_equal]=2024-01-01&where[startDateTime][less_than]=2024-01-31
|
|
155
|
+
|
|
156
|
+
# Get appointments by status
|
|
157
|
+
GET /api/appointments?where[status][equals]=scheduled
|
|
158
|
+
|
|
159
|
+
# Get appointments with related data
|
|
160
|
+
GET /api/appointments?depth=1
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Available Slots (Custom Endpoint)
|
|
164
|
+
|
|
165
|
+
| Method | Endpoint | Description |
|
|
166
|
+
|--------|----------|-------------|
|
|
167
|
+
| GET | `/api/appointments/available-slots` | Query available booking times |
|
|
168
|
+
|
|
169
|
+
**Query parameters:**
|
|
170
|
+
- `date` (required) - ISO date string (YYYY-MM-DD)
|
|
171
|
+
- `serviceId` (required) - ID of the service
|
|
172
|
+
- `teamMemberId` (optional) - Filter by specific team member
|
|
173
|
+
|
|
174
|
+
**Example:**
|
|
175
|
+
```bash
|
|
176
|
+
GET /api/appointments/available-slots?date=2024-01-15&serviceId=abc123
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Response:**
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"date": "2024-01-15",
|
|
183
|
+
"serviceId": "abc123",
|
|
184
|
+
"slots": [
|
|
185
|
+
{ "start": "2024-01-15T09:00:00Z", "end": "2024-01-15T09:30:00Z", "available": true },
|
|
186
|
+
{ "start": "2024-01-15T09:30:00Z", "end": "2024-01-15T10:00:00Z", "available": true }
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Opening Times (Global)
|
|
192
|
+
|
|
193
|
+
| Method | Endpoint | Description |
|
|
194
|
+
|--------|----------|-------------|
|
|
195
|
+
| GET | `/api/globals/opening-times` | Get business hours settings |
|
|
196
|
+
| POST | `/api/globals/opening-times` | Update business hours settings |
|
|
197
|
+
|
|
198
|
+
## Booking Flow Example
|
|
199
|
+
|
|
200
|
+
### 1. Get Available Services
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
GET /api/services?where[isActive][equals]=true
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 2. Get Available Time Slots
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
GET /api/appointments/available-slots?date=2024-01-15&serviceId=SERVICE_ID
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 3. Create Guest Customer (if not registered)
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
POST /api/guest-customers
|
|
216
|
+
Content-Type: application/json
|
|
217
|
+
|
|
218
|
+
{
|
|
219
|
+
"firstName": "John",
|
|
220
|
+
"lastName": "Doe",
|
|
221
|
+
"email": "john@example.com",
|
|
222
|
+
"phone": "+1234567890",
|
|
223
|
+
"source": "website"
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 4. Create Appointment
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
POST /api/appointments
|
|
231
|
+
Content-Type: application/json
|
|
232
|
+
|
|
233
|
+
{
|
|
234
|
+
"type": "appointment",
|
|
235
|
+
"service": "SERVICE_ID",
|
|
236
|
+
"guest": "GUEST_ID",
|
|
237
|
+
"startDateTime": "2024-01-15T09:00:00Z",
|
|
238
|
+
"status": "scheduled"
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Frontend Integration
|
|
243
|
+
|
|
244
|
+
See the [examples/BookingWidget.tsx](./examples/BookingWidget.tsx) for a complete React booking component with:
|
|
245
|
+
- 3-step booking flow (service -> date/time -> details)
|
|
246
|
+
- Available slots display
|
|
247
|
+
- Guest customer creation
|
|
248
|
+
- Booking confirmation
|
|
249
|
+
|
|
250
|
+
## Admin Calendar
|
|
251
|
+
|
|
252
|
+
The plugin automatically adds a calendar dashboard to your Payload admin panel showing:
|
|
253
|
+
- Week and day views
|
|
254
|
+
- Color-coded events by service
|
|
255
|
+
- Click to view appointment details
|
|
256
|
+
- Quick navigation between dates
|
|
257
|
+
|
|
258
|
+
## Development
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Install dependencies
|
|
262
|
+
npm install
|
|
263
|
+
|
|
264
|
+
# Run dev environment
|
|
265
|
+
npm run dev
|
|
266
|
+
|
|
267
|
+
# Run tests
|
|
268
|
+
npm test
|
|
269
|
+
|
|
270
|
+
# Lint code
|
|
271
|
+
npm run lint
|
|
272
|
+
|
|
273
|
+
# Format code
|
|
274
|
+
npm run format
|
|
275
|
+
|
|
276
|
+
# Build for production
|
|
277
|
+
npm run build
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Requirements
|
|
281
|
+
|
|
282
|
+
- PayloadCMS 3.37.0 or higher
|
|
283
|
+
- Node.js 18.20.2+ or 20.9.0+
|
|
284
|
+
- React 19.x
|
|
285
|
+
|
|
286
|
+
## Contributing
|
|
287
|
+
|
|
288
|
+
Contributions are welcome! Please read our [contributing guidelines](./CONTRIBUTING.md) and submit a pull request.
|
|
289
|
+
|
|
290
|
+
1. Fork the repository
|
|
291
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
292
|
+
3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
|
|
293
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
294
|
+
5. Open a Pull Request
|
|
295
|
+
|
|
296
|
+
## License
|
|
297
|
+
|
|
298
|
+
MIT License - see [LICENSE](./LICENSE) for details.
|
|
299
|
+
|
|
300
|
+
## Author
|
|
301
|
+
|
|
302
|
+
Created by [Daniel T Sasser II](https://github.com/Gorombo)
|
|
303
|
+
|
|
304
|
+
## Links
|
|
305
|
+
|
|
306
|
+
- [PayloadCMS](https://payloadcms.com)
|
|
307
|
+
- [Plugin Documentation](https://payloadcms.com/docs/plugins/overview)
|
|
308
|
+
- [Report Issues](https://github.com/dansasser/gorombo-appointment-plugin/issues)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
export const Appointments = {
|
|
2
|
+
slug: 'appointments',
|
|
3
|
+
access: {
|
|
4
|
+
create: ()=>true,
|
|
5
|
+
delete: ({ req })=>!!req.user,
|
|
6
|
+
read: ()=>true,
|
|
7
|
+
update: ({ req })=>!!req.user
|
|
8
|
+
},
|
|
9
|
+
admin: {
|
|
10
|
+
defaultColumns: [
|
|
11
|
+
'title',
|
|
12
|
+
'startDateTime',
|
|
13
|
+
'service',
|
|
14
|
+
'status'
|
|
15
|
+
],
|
|
16
|
+
group: 'Scheduling',
|
|
17
|
+
useAsTitle: 'title'
|
|
18
|
+
},
|
|
19
|
+
fields: [
|
|
20
|
+
{
|
|
21
|
+
name: 'title',
|
|
22
|
+
type: 'text',
|
|
23
|
+
admin: {
|
|
24
|
+
description: 'Auto-generated display title',
|
|
25
|
+
hidden: true
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'type',
|
|
30
|
+
type: 'select',
|
|
31
|
+
admin: {
|
|
32
|
+
position: 'sidebar'
|
|
33
|
+
},
|
|
34
|
+
defaultValue: 'appointment',
|
|
35
|
+
options: [
|
|
36
|
+
{
|
|
37
|
+
label: 'Appointment',
|
|
38
|
+
value: 'appointment'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: 'Blockout',
|
|
42
|
+
value: 'blockout'
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
required: true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'service',
|
|
49
|
+
type: 'relationship',
|
|
50
|
+
admin: {
|
|
51
|
+
condition: (data)=>data?.type === 'appointment'
|
|
52
|
+
},
|
|
53
|
+
relationTo: 'services',
|
|
54
|
+
validate: (value, { siblingData })=>{
|
|
55
|
+
if (siblingData?.type === 'appointment' && !value) {
|
|
56
|
+
return 'Service is required for appointments';
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'customer',
|
|
63
|
+
type: 'relationship',
|
|
64
|
+
admin: {
|
|
65
|
+
condition: (data)=>data?.type === 'appointment',
|
|
66
|
+
description: 'Registered user booking the appointment'
|
|
67
|
+
},
|
|
68
|
+
relationTo: 'users'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'guest',
|
|
72
|
+
type: 'relationship',
|
|
73
|
+
admin: {
|
|
74
|
+
condition: (data)=>data?.type === 'appointment',
|
|
75
|
+
description: 'Guest customer (non-registered user)'
|
|
76
|
+
},
|
|
77
|
+
relationTo: 'guest-customers'
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'teamMember',
|
|
81
|
+
type: 'relationship',
|
|
82
|
+
admin: {
|
|
83
|
+
description: 'Staff member assigned to this appointment',
|
|
84
|
+
position: 'sidebar'
|
|
85
|
+
},
|
|
86
|
+
relationTo: 'team-members'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'startDateTime',
|
|
90
|
+
type: 'date',
|
|
91
|
+
admin: {
|
|
92
|
+
date: {
|
|
93
|
+
displayFormat: 'MMM d, yyyy h:mm a',
|
|
94
|
+
pickerAppearance: 'dayAndTime'
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
required: true
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'endDateTime',
|
|
101
|
+
type: 'date',
|
|
102
|
+
admin: {
|
|
103
|
+
date: {
|
|
104
|
+
displayFormat: 'MMM d, yyyy h:mm a',
|
|
105
|
+
pickerAppearance: 'dayAndTime'
|
|
106
|
+
},
|
|
107
|
+
description: 'Auto-calculated from service duration',
|
|
108
|
+
readOnly: true
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'status',
|
|
113
|
+
type: 'select',
|
|
114
|
+
admin: {
|
|
115
|
+
position: 'sidebar'
|
|
116
|
+
},
|
|
117
|
+
defaultValue: 'scheduled',
|
|
118
|
+
options: [
|
|
119
|
+
{
|
|
120
|
+
label: 'Scheduled',
|
|
121
|
+
value: 'scheduled'
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
label: 'Confirmed',
|
|
125
|
+
value: 'confirmed'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
label: 'Completed',
|
|
129
|
+
value: 'completed'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: 'Cancelled',
|
|
133
|
+
value: 'cancelled'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: 'No-show',
|
|
137
|
+
value: 'no-show'
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
required: true
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'notes',
|
|
144
|
+
type: 'textarea',
|
|
145
|
+
admin: {
|
|
146
|
+
description: 'Internal notes about this appointment'
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'blockoutReason',
|
|
151
|
+
type: 'text',
|
|
152
|
+
admin: {
|
|
153
|
+
condition: (data)=>data?.type === 'blockout',
|
|
154
|
+
description: 'Reason for the blockout (e.g., lunch, meeting)'
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
labels: {
|
|
159
|
+
plural: 'Appointments',
|
|
160
|
+
singular: 'Appointment'
|
|
161
|
+
},
|
|
162
|
+
timestamps: true
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
//# sourceMappingURL=Appointments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/collections/Appointments.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport const Appointments: CollectionConfig = {\n slug: 'appointments',\n access: {\n create: () => true,\n delete: ({ req }) => !!req.user,\n read: () => true,\n update: ({ req }) => !!req.user,\n },\n admin: {\n defaultColumns: ['title', 'startDateTime', 'service', 'status'],\n group: 'Scheduling',\n useAsTitle: 'title',\n },\n fields: [\n {\n name: 'title',\n type: 'text',\n admin: {\n description: 'Auto-generated display title',\n hidden: true,\n },\n },\n {\n name: 'type',\n type: 'select',\n admin: {\n position: 'sidebar',\n },\n defaultValue: 'appointment',\n options: [\n { label: 'Appointment', value: 'appointment' },\n { label: 'Blockout', value: 'blockout' },\n ],\n required: true,\n },\n {\n name: 'service',\n type: 'relationship',\n admin: {\n condition: (data) => data?.type === 'appointment',\n },\n relationTo: 'services',\n validate: (value: unknown, { siblingData }: { siblingData: Record<string, unknown> }) => {\n if (siblingData?.type === 'appointment' && !value) {\n return 'Service is required for appointments'\n }\n return true\n },\n },\n {\n name: 'customer',\n type: 'relationship',\n admin: {\n condition: (data) => data?.type === 'appointment',\n description: 'Registered user booking the appointment',\n },\n relationTo: 'users',\n },\n {\n name: 'guest',\n type: 'relationship',\n admin: {\n condition: (data) => data?.type === 'appointment',\n description: 'Guest customer (non-registered user)',\n },\n relationTo: 'guest-customers',\n },\n {\n name: 'teamMember',\n type: 'relationship',\n admin: {\n description: 'Staff member assigned to this appointment',\n position: 'sidebar',\n },\n relationTo: 'team-members',\n },\n {\n name: 'startDateTime',\n type: 'date',\n admin: {\n date: {\n displayFormat: 'MMM d, yyyy h:mm a',\n pickerAppearance: 'dayAndTime',\n },\n },\n required: true,\n },\n {\n name: 'endDateTime',\n type: 'date',\n admin: {\n date: {\n displayFormat: 'MMM d, yyyy h:mm a',\n pickerAppearance: 'dayAndTime',\n },\n description: 'Auto-calculated from service duration',\n readOnly: true,\n },\n },\n {\n name: 'status',\n type: 'select',\n admin: {\n position: 'sidebar',\n },\n defaultValue: 'scheduled',\n options: [\n { label: 'Scheduled', value: 'scheduled' },\n { label: 'Confirmed', value: 'confirmed' },\n { label: 'Completed', value: 'completed' },\n { label: 'Cancelled', value: 'cancelled' },\n { label: 'No-show', value: 'no-show' },\n ],\n required: true,\n },\n {\n name: 'notes',\n type: 'textarea',\n admin: {\n description: 'Internal notes about this appointment',\n },\n },\n {\n name: 'blockoutReason',\n type: 'text',\n admin: {\n condition: (data) => data?.type === 'blockout',\n description: 'Reason for the blockout (e.g., lunch, meeting)',\n },\n },\n ],\n labels: {\n plural: 'Appointments',\n singular: 'Appointment',\n },\n timestamps: true,\n}\n"],"names":["Appointments","slug","access","create","delete","req","user","read","update","admin","defaultColumns","group","useAsTitle","fields","name","type","description","hidden","position","defaultValue","options","label","value","required","condition","data","relationTo","validate","siblingData","date","displayFormat","pickerAppearance","readOnly","labels","plural","singular","timestamps"],"mappings":"AAEA,OAAO,MAAMA,eAAiC;IAC5CC,MAAM;IACNC,QAAQ;QACNC,QAAQ,IAAM;QACdC,QAAQ,CAAC,EAAEC,GAAG,EAAE,GAAK,CAAC,CAACA,IAAIC,IAAI;QAC/BC,MAAM,IAAM;QACZC,QAAQ,CAAC,EAAEH,GAAG,EAAE,GAAK,CAAC,CAACA,IAAIC,IAAI;IACjC;IACAG,OAAO;QACLC,gBAAgB;YAAC;YAAS;YAAiB;YAAW;SAAS;QAC/DC,OAAO;QACPC,YAAY;IACd;IACAC,QAAQ;QACN;YACEC,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLO,aAAa;gBACbC,QAAQ;YACV;QACF;QACA;YACEH,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLS,UAAU;YACZ;YACAC,cAAc;YACdC,SAAS;gBACP;oBAAEC,OAAO;oBAAeC,OAAO;gBAAc;gBAC7C;oBAAED,OAAO;oBAAYC,OAAO;gBAAW;aACxC;YACDC,UAAU;QACZ;QACA;YACET,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLe,WAAW,CAACC,OAASA,MAAMV,SAAS;YACtC;YACAW,YAAY;YACZC,UAAU,CAACL,OAAgB,EAAEM,WAAW,EAA4C;gBAClF,IAAIA,aAAab,SAAS,iBAAiB,CAACO,OAAO;oBACjD,OAAO;gBACT;gBACA,OAAO;YACT;QACF;QACA;YACER,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLe,WAAW,CAACC,OAASA,MAAMV,SAAS;gBACpCC,aAAa;YACf;YACAU,YAAY;QACd;QACA;YACEZ,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLe,WAAW,CAACC,OAASA,MAAMV,SAAS;gBACpCC,aAAa;YACf;YACAU,YAAY;QACd;QACA;YACEZ,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLO,aAAa;gBACbE,UAAU;YACZ;YACAQ,YAAY;QACd;QACA;YACEZ,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLoB,MAAM;oBACJC,eAAe;oBACfC,kBAAkB;gBACpB;YACF;YACAR,UAAU;QACZ;QACA;YACET,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLoB,MAAM;oBACJC,eAAe;oBACfC,kBAAkB;gBACpB;gBACAf,aAAa;gBACbgB,UAAU;YACZ;QACF;QACA;YACElB,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLS,UAAU;YACZ;YACAC,cAAc;YACdC,SAAS;gBACP;oBAAEC,OAAO;oBAAaC,OAAO;gBAAY;gBACzC;oBAAED,OAAO;oBAAaC,OAAO;gBAAY;gBACzC;oBAAED,OAAO;oBAAaC,OAAO;gBAAY;gBACzC;oBAAED,OAAO;oBAAaC,OAAO;gBAAY;gBACzC;oBAAED,OAAO;oBAAWC,OAAO;gBAAU;aACtC;YACDC,UAAU;QACZ;QACA;YACET,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLO,aAAa;YACf;QACF;QACA;YACEF,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLe,WAAW,CAACC,OAASA,MAAMV,SAAS;gBACpCC,aAAa;YACf;QACF;KACD;IACDiB,QAAQ;QACNC,QAAQ;QACRC,UAAU;IACZ;IACAC,YAAY;AACd,EAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export const GuestCustomers = {
|
|
2
|
+
slug: 'guest-customers',
|
|
3
|
+
access: {
|
|
4
|
+
create: ()=>true,
|
|
5
|
+
delete: ({ req })=>!!req.user,
|
|
6
|
+
read: ({ req })=>!!req.user,
|
|
7
|
+
update: ({ req })=>!!req.user
|
|
8
|
+
},
|
|
9
|
+
admin: {
|
|
10
|
+
defaultColumns: [
|
|
11
|
+
'firstName',
|
|
12
|
+
'lastName',
|
|
13
|
+
'email',
|
|
14
|
+
'phone',
|
|
15
|
+
'createdAt'
|
|
16
|
+
],
|
|
17
|
+
group: 'Scheduling',
|
|
18
|
+
useAsTitle: 'email'
|
|
19
|
+
},
|
|
20
|
+
fields: [
|
|
21
|
+
{
|
|
22
|
+
name: 'firstName',
|
|
23
|
+
type: 'text',
|
|
24
|
+
required: true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'lastName',
|
|
28
|
+
type: 'text',
|
|
29
|
+
required: true
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'email',
|
|
33
|
+
type: 'email',
|
|
34
|
+
required: true,
|
|
35
|
+
unique: true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'phone',
|
|
39
|
+
type: 'text',
|
|
40
|
+
admin: {
|
|
41
|
+
description: 'Contact phone number'
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'company',
|
|
46
|
+
type: 'text',
|
|
47
|
+
admin: {
|
|
48
|
+
description: 'Company or organization name'
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'notes',
|
|
53
|
+
type: 'textarea',
|
|
54
|
+
admin: {
|
|
55
|
+
description: 'Internal notes about this guest'
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'marketingOptIn',
|
|
60
|
+
type: 'checkbox',
|
|
61
|
+
admin: {
|
|
62
|
+
description: 'Opted in to marketing communications',
|
|
63
|
+
position: 'sidebar'
|
|
64
|
+
},
|
|
65
|
+
defaultValue: false
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'source',
|
|
69
|
+
type: 'select',
|
|
70
|
+
admin: {
|
|
71
|
+
description: 'How did they find us',
|
|
72
|
+
position: 'sidebar'
|
|
73
|
+
},
|
|
74
|
+
defaultValue: 'website',
|
|
75
|
+
options: [
|
|
76
|
+
{
|
|
77
|
+
label: 'Website',
|
|
78
|
+
value: 'website'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
label: 'Referral',
|
|
82
|
+
value: 'referral'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: 'Walk-in',
|
|
86
|
+
value: 'walkin'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
label: 'Phone',
|
|
90
|
+
value: 'phone'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: 'Other',
|
|
94
|
+
value: 'other'
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
labels: {
|
|
100
|
+
plural: 'Guest Customers',
|
|
101
|
+
singular: 'Guest Customer'
|
|
102
|
+
},
|
|
103
|
+
timestamps: true
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=GuestCustomers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/collections/GuestCustomers.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport const GuestCustomers: CollectionConfig = {\n slug: 'guest-customers',\n access: {\n create: () => true,\n delete: ({ req }) => !!req.user,\n read: ({ req }) => !!req.user,\n update: ({ req }) => !!req.user,\n },\n admin: {\n defaultColumns: ['firstName', 'lastName', 'email', 'phone', 'createdAt'],\n group: 'Scheduling',\n useAsTitle: 'email',\n },\n fields: [\n {\n name: 'firstName',\n type: 'text',\n required: true,\n },\n {\n name: 'lastName',\n type: 'text',\n required: true,\n },\n {\n name: 'email',\n type: 'email',\n required: true,\n unique: true,\n },\n {\n name: 'phone',\n type: 'text',\n admin: {\n description: 'Contact phone number',\n },\n },\n {\n name: 'company',\n type: 'text',\n admin: {\n description: 'Company or organization name',\n },\n },\n {\n name: 'notes',\n type: 'textarea',\n admin: {\n description: 'Internal notes about this guest',\n },\n },\n {\n name: 'marketingOptIn',\n type: 'checkbox',\n admin: {\n description: 'Opted in to marketing communications',\n position: 'sidebar',\n },\n defaultValue: false,\n },\n {\n name: 'source',\n type: 'select',\n admin: {\n description: 'How did they find us',\n position: 'sidebar',\n },\n defaultValue: 'website',\n options: [\n { label: 'Website', value: 'website' },\n { label: 'Referral', value: 'referral' },\n { label: 'Walk-in', value: 'walkin' },\n { label: 'Phone', value: 'phone' },\n { label: 'Other', value: 'other' },\n ],\n },\n ],\n labels: {\n plural: 'Guest Customers',\n singular: 'Guest Customer',\n },\n timestamps: true,\n}\n"],"names":["GuestCustomers","slug","access","create","delete","req","user","read","update","admin","defaultColumns","group","useAsTitle","fields","name","type","required","unique","description","position","defaultValue","options","label","value","labels","plural","singular","timestamps"],"mappings":"AAEA,OAAO,MAAMA,iBAAmC;IAC9CC,MAAM;IACNC,QAAQ;QACNC,QAAQ,IAAM;QACdC,QAAQ,CAAC,EAAEC,GAAG,EAAE,GAAK,CAAC,CAACA,IAAIC,IAAI;QAC/BC,MAAM,CAAC,EAAEF,GAAG,EAAE,GAAK,CAAC,CAACA,IAAIC,IAAI;QAC7BE,QAAQ,CAAC,EAAEH,GAAG,EAAE,GAAK,CAAC,CAACA,IAAIC,IAAI;IACjC;IACAG,OAAO;QACLC,gBAAgB;YAAC;YAAa;YAAY;YAAS;YAAS;SAAY;QACxEC,OAAO;QACPC,YAAY;IACd;IACAC,QAAQ;QACN;YACEC,MAAM;YACNC,MAAM;YACNC,UAAU;QACZ;QACA;YACEF,MAAM;YACNC,MAAM;YACNC,UAAU;QACZ;QACA;YACEF,MAAM;YACNC,MAAM;YACNC,UAAU;YACVC,QAAQ;QACV;QACA;YACEH,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLS,aAAa;YACf;QACF;QACA;YACEJ,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLS,aAAa;YACf;QACF;QACA;YACEJ,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLS,aAAa;YACf;QACF;QACA;YACEJ,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLS,aAAa;gBACbC,UAAU;YACZ;YACAC,cAAc;QAChB;QACA;YACEN,MAAM;YACNC,MAAM;YACNN,OAAO;gBACLS,aAAa;gBACbC,UAAU;YACZ;YACAC,cAAc;YACdC,SAAS;gBACP;oBAAEC,OAAO;oBAAWC,OAAO;gBAAU;gBACrC;oBAAED,OAAO;oBAAYC,OAAO;gBAAW;gBACvC;oBAAED,OAAO;oBAAWC,OAAO;gBAAS;gBACpC;oBAAED,OAAO;oBAASC,OAAO;gBAAQ;gBACjC;oBAAED,OAAO;oBAASC,OAAO;gBAAQ;aAClC;QACH;KACD;IACDC,QAAQ;QACNC,QAAQ;QACRC,UAAU;IACZ;IACAC,YAAY;AACd,EAAC"}
|