@ossy/booking 1.15.7 → 1.16.1
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/README.md +26 -19
- package/package.json +7 -7
- package/src/AvailabilityEditor.jsx +22 -15
- package/src/Definition.js +1 -23
- package/src/SalesSection.jsx +9 -4
- package/src/availability-setup.page.jsx +94 -126
- package/src/booking-card.component.jsx +30 -33
- package/src/booking-detail.page.jsx +65 -204
- package/src/booking-resources.js +10 -0
- package/src/bookings.page.jsx +54 -56
- package/src/cancel-booking.action.js +1 -61
- package/src/cancel-booking.task.js +60 -0
- package/src/confirm-booking.action.js +1 -82
- package/src/confirm-booking.task.js +81 -0
- package/src/create-booking.action.js +1 -168
- package/src/create-booking.task.js +170 -0
- package/src/create-service.action.js +1 -0
- package/src/create-service.task.js +46 -0
- package/src/decline-booking.action.js +1 -68
- package/src/decline-booking.task.js +67 -0
- package/src/delete-service.action.js +1 -0
- package/src/delete-service.task.js +41 -0
- package/src/en.translations.json +246 -0
- package/src/get-availability.action.js +1 -52
- package/src/get-availability.task.js +51 -0
- package/src/get-available-slots.action.js +1 -89
- package/src/get-available-slots.task.js +99 -0
- package/src/get-services.action.js +1 -0
- package/src/get-services.task.js +37 -0
- package/src/home.page.jsx +48 -5
- package/src/index.js +20 -1
- package/src/invoke-error-message.js +10 -0
- package/src/list-bookings.action.js +1 -36
- package/src/list-bookings.task.js +35 -0
- package/src/locations.js +14 -0
- package/src/public-booking.page.jsx +200 -245
- package/src/save-availability.action.js +1 -73
- package/src/save-availability.task.js +74 -0
- package/src/send-booking-reminder.task.js +2 -2
- package/src/services.page.jsx +167 -46
- package/src/sv.translations.json +246 -0
- package/src/update-service.action.js +1 -0
- package/src/update-service.task.js +52 -0
- package/src/BookingForm.jsx +0 -82
- package/src/availability.page.jsx +0 -41
- package/src/booking-page.page.jsx +0 -42
|
@@ -1,52 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const id = 'booking/get-availability'
|
|
4
|
-
export const access = 'workspace'
|
|
5
|
-
|
|
6
|
-
export async function run({ payload, req, log }) {
|
|
7
|
-
const workspaceId = req?.workspaceId
|
|
8
|
-
|
|
9
|
-
if (!workspaceId) {
|
|
10
|
-
throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
log?.info(`[booking/get-availability] Loading availability for workspace ${workspaceId}`)
|
|
14
|
-
|
|
15
|
-
const resources = await ResourcesQueries.GetResources({
|
|
16
|
-
type: '@ossy/booking/availability',
|
|
17
|
-
belongsTo: workspaceId,
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
if (!resources.length) {
|
|
21
|
-
log?.info('[booking/get-availability] No availability configured')
|
|
22
|
-
return null
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// New format: single resource with weeklyWindows array
|
|
26
|
-
const configResource = resources.find(r => Array.isArray(r.content?.weeklyWindows))
|
|
27
|
-
if (configResource) {
|
|
28
|
-
return {
|
|
29
|
-
id: configResource.id,
|
|
30
|
-
weeklyWindows: configResource.content.weeklyWindows ?? [],
|
|
31
|
-
sessionDurations: configResource.content.sessionDurations ?? [60],
|
|
32
|
-
bufferMinutes: configResource.content.bufferMinutes ?? 0,
|
|
33
|
-
timezone: configResource.content.timezone ?? 'Europe/Stockholm',
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Legacy format: one resource per day — reconstruct config shape
|
|
38
|
-
const firstResource = resources[0]
|
|
39
|
-
return {
|
|
40
|
-
id: firstResource.id,
|
|
41
|
-
weeklyWindows: resources
|
|
42
|
-
.map(r => ({
|
|
43
|
-
dayOfWeek: r.content?.dayOfWeek,
|
|
44
|
-
startTime: r.content?.startTime ?? '09:00',
|
|
45
|
-
endTime: r.content?.endTime ?? '17:00',
|
|
46
|
-
}))
|
|
47
|
-
.filter(w => w.dayOfWeek != null),
|
|
48
|
-
sessionDurations: [firstResource?.content?.sessionDuration ?? 60],
|
|
49
|
-
bufferMinutes: firstResource?.content?.bufferMinutes ?? 0,
|
|
50
|
-
timezone: firstResource?.content?.timezone ?? 'Europe/Stockholm',
|
|
51
|
-
}
|
|
52
|
-
}
|
|
1
|
+
export const metadata = { id: 'booking/get-availability', access: 'workspace' }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getBookingResources } from './booking-resources.js'
|
|
2
|
+
|
|
3
|
+
export const metadata = { id: 'booking/get-availability' }
|
|
4
|
+
|
|
5
|
+
export async function run({ payload, req, log }) {
|
|
6
|
+
const workspaceId = req?.workspaceId
|
|
7
|
+
|
|
8
|
+
if (!workspaceId) {
|
|
9
|
+
throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
log?.info(`[booking/get-availability] Loading availability for workspace ${workspaceId}`)
|
|
13
|
+
|
|
14
|
+
const resources = await getBookingResources({
|
|
15
|
+
type: '@ossy/booking/availability',
|
|
16
|
+
belongsTo: workspaceId,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
if (!resources.length) {
|
|
20
|
+
log?.info('[booking/get-availability] No availability configured')
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// New format: single resource with weeklyWindows array
|
|
25
|
+
const configResource = resources.find(r => Array.isArray(r.content?.weeklyWindows))
|
|
26
|
+
if (configResource) {
|
|
27
|
+
return {
|
|
28
|
+
id: configResource.id,
|
|
29
|
+
weeklyWindows: configResource.content.weeklyWindows ?? [],
|
|
30
|
+
sessionDurations: configResource.content.sessionDurations ?? [60],
|
|
31
|
+
bufferMinutes: configResource.content.bufferMinutes ?? 0,
|
|
32
|
+
timezone: configResource.content.timezone ?? 'Europe/Stockholm',
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Legacy format: one resource per day — reconstruct config shape
|
|
37
|
+
const firstResource = resources[0]
|
|
38
|
+
return {
|
|
39
|
+
id: firstResource.id,
|
|
40
|
+
weeklyWindows: resources
|
|
41
|
+
.map(r => ({
|
|
42
|
+
dayOfWeek: r.content?.dayOfWeek,
|
|
43
|
+
startTime: r.content?.startTime ?? '09:00',
|
|
44
|
+
endTime: r.content?.endTime ?? '17:00',
|
|
45
|
+
}))
|
|
46
|
+
.filter(w => w.dayOfWeek != null),
|
|
47
|
+
sessionDurations: [firstResource?.content?.sessionDuration ?? 60],
|
|
48
|
+
bufferMinutes: firstResource?.content?.bufferMinutes ?? 0,
|
|
49
|
+
timezone: firstResource?.content?.timezone ?? 'Europe/Stockholm',
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,89 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { getAvailableSlots } from './availability-engine.js'
|
|
3
|
-
|
|
4
|
-
export const id = 'booking/get-available-slots'
|
|
5
|
-
export const access = 'public'
|
|
6
|
-
|
|
7
|
-
const DEFAULT_DURATION = 60 // minutes
|
|
8
|
-
const DEFAULT_RANGE_DAYS = 30
|
|
9
|
-
|
|
10
|
-
export async function run({ payload, log }) {
|
|
11
|
-
const { consultantId, duration, from, to } = payload ?? {}
|
|
12
|
-
|
|
13
|
-
if (!consultantId) {
|
|
14
|
-
throw Object.assign(new Error('consultantId is required'), { status: 400 })
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
log?.info(`[booking/get-available-slots] Loading slots for consultant ${consultantId}`)
|
|
18
|
-
|
|
19
|
-
const availabilityResources = await ResourcesQueries.GetResources({
|
|
20
|
-
type: '@ossy/booking/availability',
|
|
21
|
-
belongsTo: consultantId,
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
if (!availabilityResources.length) {
|
|
25
|
-
log?.info('[booking/get-available-slots] No availability resources found, returning []')
|
|
26
|
-
return []
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// New format: single resource with weeklyWindows array in content
|
|
30
|
-
const configResource = availabilityResources.find(r => Array.isArray(r.content?.weeklyWindows))
|
|
31
|
-
let weeklyWindows, firstResource
|
|
32
|
-
|
|
33
|
-
if (configResource) {
|
|
34
|
-
weeklyWindows = configResource.content.weeklyWindows ?? []
|
|
35
|
-
firstResource = configResource
|
|
36
|
-
} else {
|
|
37
|
-
// Legacy format: one resource per day
|
|
38
|
-
firstResource = availabilityResources[0]
|
|
39
|
-
weeklyWindows = availabilityResources
|
|
40
|
-
.map(r => ({
|
|
41
|
-
dayOfWeek: r.content?.dayOfWeek,
|
|
42
|
-
startTime: r.content?.startTime ?? '09:00',
|
|
43
|
-
endTime: r.content?.endTime ?? '17:00',
|
|
44
|
-
}))
|
|
45
|
-
.filter(w => w.dayOfWeek != null)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const availability = {
|
|
49
|
-
weeklyWindows,
|
|
50
|
-
bufferMinutes: firstResource?.content?.bufferMinutes ?? 0,
|
|
51
|
-
blackoutDates: firstResource?.content?.blackoutDates ?? [],
|
|
52
|
-
timezone: firstResource?.content?.timezone ?? 'Europe/Stockholm',
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const fromDate = from ? new Date(from) : new Date()
|
|
56
|
-
const toDate = to
|
|
57
|
-
? new Date(to)
|
|
58
|
-
: new Date(Date.now() + DEFAULT_RANGE_DAYS * 24 * 60 * 60 * 1000)
|
|
59
|
-
|
|
60
|
-
const allBookingResources = await ResourcesQueries.GetResources({
|
|
61
|
-
type: '@ossy/booking/booking',
|
|
62
|
-
belongsTo: consultantId,
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
const confirmedBookings = allBookingResources
|
|
66
|
-
.filter(b => b.content?.status === 'confirmed')
|
|
67
|
-
.map(b => ({
|
|
68
|
-
startAt: b.content.startsAt,
|
|
69
|
-
endAt: b.content.endsAt,
|
|
70
|
-
status: 'confirmed',
|
|
71
|
-
}))
|
|
72
|
-
|
|
73
|
-
const sessionDurations = configResource
|
|
74
|
-
? (configResource.content.sessionDurations ?? [DEFAULT_DURATION])
|
|
75
|
-
: [firstResource?.content?.sessionDuration ?? DEFAULT_DURATION]
|
|
76
|
-
|
|
77
|
-
const slotDuration = duration ?? sessionDurations[0] ?? DEFAULT_DURATION
|
|
78
|
-
|
|
79
|
-
const slots = getAvailableSlots({
|
|
80
|
-
availability,
|
|
81
|
-
bookings: confirmedBookings,
|
|
82
|
-
fromDate,
|
|
83
|
-
toDate,
|
|
84
|
-
duration: slotDuration,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
log?.info(`[booking/get-available-slots] Returning ${slots.length} slots`)
|
|
88
|
-
return slots
|
|
89
|
-
}
|
|
1
|
+
export const metadata = { id: 'booking/get-available-slots', access: 'public' }
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { getAvailableSlots } from './availability-engine.js'
|
|
2
|
+
import { getBookingResources } from './booking-resources.js'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'booking/get-available-slots' }
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DURATION = 60 // minutes
|
|
7
|
+
const DEFAULT_RANGE_DAYS = 30
|
|
8
|
+
|
|
9
|
+
export async function run ({ payload, log }) {
|
|
10
|
+
const { consultantId, duration, serviceId, from, to } = payload ?? {}
|
|
11
|
+
|
|
12
|
+
if (!consultantId) {
|
|
13
|
+
throw Object.assign(new Error('consultantId is required'), { status: 400 })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
log?.info(`[booking/get-available-slots] Loading slots for consultant ${consultantId}`)
|
|
17
|
+
|
|
18
|
+
let slotDuration = duration
|
|
19
|
+
|
|
20
|
+
if (serviceId) {
|
|
21
|
+
const serviceResources = await getBookingResources({
|
|
22
|
+
type: '@ossy/booking/service',
|
|
23
|
+
belongsTo: consultantId,
|
|
24
|
+
})
|
|
25
|
+
const service = serviceResources.find(r => r.id === serviceId)
|
|
26
|
+
if (service?.content?.duration) {
|
|
27
|
+
slotDuration = service.content.duration
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const availabilityResources = await getBookingResources({
|
|
32
|
+
type: '@ossy/booking/availability',
|
|
33
|
+
belongsTo: consultantId,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
if (!availabilityResources.length) {
|
|
37
|
+
log?.info('[booking/get-available-slots] No availability resources found, returning []')
|
|
38
|
+
return []
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const configResource = availabilityResources.find(r => Array.isArray(r.content?.weeklyWindows))
|
|
42
|
+
let weeklyWindows, firstResource
|
|
43
|
+
|
|
44
|
+
if (configResource) {
|
|
45
|
+
weeklyWindows = configResource.content.weeklyWindows ?? []
|
|
46
|
+
firstResource = configResource
|
|
47
|
+
} else {
|
|
48
|
+
firstResource = availabilityResources[0]
|
|
49
|
+
weeklyWindows = availabilityResources
|
|
50
|
+
.map(r => ({
|
|
51
|
+
dayOfWeek: r.content?.dayOfWeek,
|
|
52
|
+
startTime: r.content?.startTime ?? '09:00',
|
|
53
|
+
endTime: r.content?.endTime ?? '17:00',
|
|
54
|
+
}))
|
|
55
|
+
.filter(w => w.dayOfWeek != null)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const availability = {
|
|
59
|
+
weeklyWindows,
|
|
60
|
+
bufferMinutes: firstResource?.content?.bufferMinutes ?? 0,
|
|
61
|
+
blackoutDates: firstResource?.content?.blackoutDates ?? [],
|
|
62
|
+
timezone: firstResource?.content?.timezone ?? 'Europe/Stockholm',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const fromDate = from ? new Date(from) : new Date()
|
|
66
|
+
const toDate = to
|
|
67
|
+
? new Date(to)
|
|
68
|
+
: new Date(Date.now() + DEFAULT_RANGE_DAYS * 24 * 60 * 60 * 1000)
|
|
69
|
+
|
|
70
|
+
const allBookingResources = await getBookingResources({
|
|
71
|
+
type: '@ossy/booking/booking',
|
|
72
|
+
belongsTo: consultantId,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const confirmedBookings = allBookingResources
|
|
76
|
+
.filter(b => b.content?.status === 'confirmed')
|
|
77
|
+
.map(b => ({
|
|
78
|
+
startAt: b.content.startsAt,
|
|
79
|
+
endAt: b.content.endsAt,
|
|
80
|
+
status: 'confirmed',
|
|
81
|
+
}))
|
|
82
|
+
|
|
83
|
+
const sessionDurations = configResource
|
|
84
|
+
? (configResource.content.sessionDurations ?? [DEFAULT_DURATION])
|
|
85
|
+
: [firstResource?.content?.sessionDuration ?? DEFAULT_DURATION]
|
|
86
|
+
|
|
87
|
+
const resolvedDuration = slotDuration ?? sessionDurations[0] ?? DEFAULT_DURATION
|
|
88
|
+
|
|
89
|
+
const slots = getAvailableSlots({
|
|
90
|
+
availability,
|
|
91
|
+
bookings: confirmedBookings,
|
|
92
|
+
fromDate,
|
|
93
|
+
toDate,
|
|
94
|
+
duration: resolvedDuration,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
log?.info(`[booking/get-available-slots] Returning ${slots.length} slots`)
|
|
98
|
+
return slots
|
|
99
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const metadata = { id: 'booking/get-services', access: 'public' }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getBookingResources } from './booking-resources.js'
|
|
2
|
+
|
|
3
|
+
export const metadata = { id: 'booking/get-services' }
|
|
4
|
+
|
|
5
|
+
export async function run ({ payload, req, log }) {
|
|
6
|
+
const workspaceId = payload?.consultantId ?? payload?.workspaceId ?? req?.workspaceId
|
|
7
|
+
|
|
8
|
+
if (!workspaceId) {
|
|
9
|
+
throw Object.assign(new Error('consultantId or workspaceId is required'), { status: 400 })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
log?.info(`[booking/get-services] Loading services for ${workspaceId}`)
|
|
13
|
+
|
|
14
|
+
const resources = await getBookingResources({
|
|
15
|
+
type: '@ossy/booking/service',
|
|
16
|
+
belongsTo: workspaceId,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const includeInactive = payload?.includeInactive === true
|
|
20
|
+
|
|
21
|
+
const services = resources
|
|
22
|
+
.map(r => ({
|
|
23
|
+
id: r.id,
|
|
24
|
+
name: r.content?.name ?? r.name,
|
|
25
|
+
duration: r.content?.duration ?? 60,
|
|
26
|
+
price: r.content?.price ?? 0,
|
|
27
|
+
currency: r.content?.currency ?? 'SEK',
|
|
28
|
+
description: r.content?.description ?? '',
|
|
29
|
+
active: r.content?.active !== false,
|
|
30
|
+
}))
|
|
31
|
+
.filter(s => includeInactive || s.active)
|
|
32
|
+
.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
|
|
33
|
+
|
|
34
|
+
log?.info(`[booking/get-services] Returning ${services.length} service(s)`)
|
|
35
|
+
|
|
36
|
+
return services
|
|
37
|
+
}
|
package/src/home.page.jsx
CHANGED
|
@@ -1,13 +1,56 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import { features } from './features.js'
|
|
2
|
+
import { useLocale } from '@ossy/design-system'
|
|
4
3
|
import SalesSection from './SalesSection.jsx'
|
|
5
4
|
|
|
6
5
|
export const metadata = {
|
|
7
6
|
id: 'booking/home',
|
|
8
|
-
title: 'Bokningskalender – Ossy',
|
|
9
|
-
description: 'Låt kunder boka tid direkt. Dela en länk, kunder väljer tid, du fokuserar på jobbet.',
|
|
10
7
|
path: { sv: '/boka', en: '/booking' },
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
function useBookingHomeContent () {
|
|
11
|
+
const { t } = useLocale()
|
|
12
|
+
|
|
13
|
+
const cover = {
|
|
14
|
+
title: t('booking.home.cover.title'),
|
|
15
|
+
text: t('booking.home.cover.text'),
|
|
16
|
+
actions: [
|
|
17
|
+
{ label: t('booking.home.cover.ctaPrimary'), variant: 'cta', href: '/sign-up' },
|
|
18
|
+
{ label: t('booking.home.cover.ctaSecondary'), variant: 'secondary', href: '/boka/demo' },
|
|
19
|
+
],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const features = [
|
|
23
|
+
{
|
|
24
|
+
title: t('booking.home.features.page.title'),
|
|
25
|
+
icon: 'share',
|
|
26
|
+
text: t('booking.home.features.page.text'),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: t('booking.home.features.confirmations.title'),
|
|
30
|
+
icon: 'check-circle',
|
|
31
|
+
text: t('booking.home.features.confirmations.text'),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: t('booking.home.features.availability.title'),
|
|
35
|
+
icon: 'calendar',
|
|
36
|
+
text: t('booking.home.features.availability.text'),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
title: t('booking.home.features.email.title'),
|
|
40
|
+
icon: 'mail',
|
|
41
|
+
text: t('booking.home.features.email.text'),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: t('booking.home.features.doubleBook.title'),
|
|
45
|
+
icon: 'warning',
|
|
46
|
+
text: t('booking.home.features.doubleBook.text'),
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
return { cover, features }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default function BookingHomePage () {
|
|
54
|
+
const { cover, features } = useBookingHomeContent()
|
|
55
|
+
return <SalesSection cover={cover} features={features} />
|
|
56
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
export { Definition } from './Definition.js'
|
|
2
|
+
export {
|
|
3
|
+
location,
|
|
4
|
+
services,
|
|
5
|
+
bookings,
|
|
6
|
+
availability,
|
|
7
|
+
locationByType,
|
|
8
|
+
} from './locations.js'
|
|
9
|
+
export { metadata as CreateBooking } from './create-booking.action.js'
|
|
10
|
+
export { metadata as CreateService } from './create-service.action.js'
|
|
11
|
+
export { metadata as UpdateService } from './update-service.action.js'
|
|
12
|
+
export { metadata as DeleteService } from './delete-service.action.js'
|
|
13
|
+
export { metadata as SaveAvailability } from './save-availability.action.js'
|
|
14
|
+
export { metadata as ConfirmBooking } from './confirm-booking.action.js'
|
|
15
|
+
export { metadata as DeclineBooking } from './decline-booking.action.js'
|
|
16
|
+
export { metadata as CancelBooking } from './cancel-booking.action.js'
|
|
17
|
+
export { metadata as GetAvailableSlots } from './get-available-slots.action.js'
|
|
18
|
+
export { metadata as GetAvailability } from './get-availability.action.js'
|
|
19
|
+
export { metadata as ListBookings } from './list-bookings.action.js'
|
|
20
|
+
export { metadata as GetServices } from './get-services.action.js'
|
|
21
|
+
export { invokeErrorMessage } from './invoke-error-message.js'
|
|
2
22
|
|
|
3
23
|
export { ServiceCard } from './ServiceCard.jsx'
|
|
4
|
-
export { BookingForm } from './BookingForm.jsx'
|
|
5
24
|
export { BookingList } from './BookingList.jsx'
|
|
6
25
|
export { AvailabilityEditor } from './AvailabilityEditor.jsx'
|
|
7
26
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Normalize errors from `sdk.invoke` (Response, string, or Error). */
|
|
2
|
+
export async function invokeErrorMessage(err, fallback) {
|
|
3
|
+
if (typeof err === 'string') return err
|
|
4
|
+
if (err instanceof Response) {
|
|
5
|
+
const data = await err.json().catch(() => ({}))
|
|
6
|
+
return data?.message ?? data?.error ?? fallback
|
|
7
|
+
}
|
|
8
|
+
if (err instanceof Error) return err.message ?? fallback
|
|
9
|
+
return fallback
|
|
10
|
+
}
|
|
@@ -1,36 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const id = 'booking/list'
|
|
4
|
-
export const access = 'workspace'
|
|
5
|
-
|
|
6
|
-
export async function run({ payload, req, log }) {
|
|
7
|
-
const workspaceId = req?.workspaceId
|
|
8
|
-
const status = payload?.status ?? null
|
|
9
|
-
|
|
10
|
-
log?.info(`[booking/list] Loading bookings for workspace ${workspaceId}`, { status })
|
|
11
|
-
|
|
12
|
-
const resources = await ResourcesQueries.GetResources({
|
|
13
|
-
type: '@ossy/booking/booking',
|
|
14
|
-
belongsTo: workspaceId,
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
let bookings = resources.map(r => ({
|
|
18
|
-
id: r.id,
|
|
19
|
-
...r.content,
|
|
20
|
-
createdAt: r.createdAt,
|
|
21
|
-
}))
|
|
22
|
-
|
|
23
|
-
if (status) {
|
|
24
|
-
bookings = bookings.filter(b => b.status === status)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
bookings.sort((a, b) => {
|
|
28
|
-
const dateA = new Date(a.startsAt ?? a.createdAt ?? 0).getTime()
|
|
29
|
-
const dateB = new Date(b.startsAt ?? b.createdAt ?? 0).getTime()
|
|
30
|
-
return dateB - dateA
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
log?.info(`[booking/list] Found ${bookings.length} booking(s)`)
|
|
34
|
-
|
|
35
|
-
return bookings
|
|
36
|
-
}
|
|
1
|
+
export const metadata = { id: 'booking/list', access: 'workspace' }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getBookingResources } from './booking-resources.js'
|
|
2
|
+
|
|
3
|
+
export const metadata = { id: 'booking/list' }
|
|
4
|
+
|
|
5
|
+
export async function run({ payload, req, log }) {
|
|
6
|
+
const workspaceId = req?.workspaceId
|
|
7
|
+
const status = payload?.status ?? null
|
|
8
|
+
|
|
9
|
+
log?.info(`[booking/list] Loading bookings for workspace ${workspaceId}`, { status })
|
|
10
|
+
|
|
11
|
+
const resources = await getBookingResources({
|
|
12
|
+
type: '@ossy/booking/booking',
|
|
13
|
+
belongsTo: workspaceId,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
let bookings = resources.map(r => ({
|
|
17
|
+
id: r.id,
|
|
18
|
+
...r.content,
|
|
19
|
+
createdAt: r.createdAt,
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
if (status) {
|
|
23
|
+
bookings = bookings.filter(b => b.status === status)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
bookings.sort((a, b) => {
|
|
27
|
+
const dateA = new Date(a.startsAt ?? a.createdAt ?? 0).getTime()
|
|
28
|
+
const dateB = new Date(b.startsAt ?? b.createdAt ?? 0).getTime()
|
|
29
|
+
return dateB - dateA
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
log?.info(`[booking/list] Found ${bookings.length} booking(s)`)
|
|
33
|
+
|
|
34
|
+
return bookings
|
|
35
|
+
}
|
package/src/locations.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Booking feature root. Sub-areas use the paths below. */
|
|
2
|
+
export const location = '/@ossy/booking/'
|
|
3
|
+
|
|
4
|
+
/** Canonical resource locations per area. */
|
|
5
|
+
export const services = '/@ossy/booking/services/'
|
|
6
|
+
export const bookings = '/@ossy/booking/bookings/'
|
|
7
|
+
export const availability = '/@ossy/booking/availability/'
|
|
8
|
+
|
|
9
|
+
/** Map resource type → canonical location (for create actions). */
|
|
10
|
+
export const locationByType = {
|
|
11
|
+
'@ossy/booking/service': services,
|
|
12
|
+
'@ossy/booking/booking': bookings,
|
|
13
|
+
'@ossy/booking/availability': availability,
|
|
14
|
+
}
|