booking capacity check for time slots
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,38 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_586073990")
|
||||||
|
|
||||||
|
// update field
|
||||||
|
collection.fields.addAt(7, new Field({
|
||||||
|
"hidden": false,
|
||||||
|
"id": "number3301820327",
|
||||||
|
"max": null,
|
||||||
|
"min": 1,
|
||||||
|
"name": "max_booking_capacity",
|
||||||
|
"onlyInt": false,
|
||||||
|
"presentable": false,
|
||||||
|
"required": true,
|
||||||
|
"system": false,
|
||||||
|
"type": "number"
|
||||||
|
}))
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
}, (app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_586073990")
|
||||||
|
|
||||||
|
// update field
|
||||||
|
collection.fields.addAt(7, new Field({
|
||||||
|
"hidden": false,
|
||||||
|
"id": "number3301820327",
|
||||||
|
"max": null,
|
||||||
|
"min": 1,
|
||||||
|
"name": "max_capacity",
|
||||||
|
"onlyInt": false,
|
||||||
|
"presentable": false,
|
||||||
|
"required": true,
|
||||||
|
"system": false,
|
||||||
|
"type": "number"
|
||||||
|
}))
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
})
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_43114331")
|
||||||
|
|
||||||
|
// update field
|
||||||
|
collection.fields.addAt(9, new Field({
|
||||||
|
"hidden": false,
|
||||||
|
"id": "number1421793101",
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"name": "min_participants_capacity",
|
||||||
|
"onlyInt": false,
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "number"
|
||||||
|
}))
|
||||||
|
|
||||||
|
// update field
|
||||||
|
collection.fields.addAt(10, new Field({
|
||||||
|
"hidden": false,
|
||||||
|
"id": "number3301820327",
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"name": "max_participants_capacity",
|
||||||
|
"onlyInt": false,
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "number"
|
||||||
|
}))
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
}, (app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_43114331")
|
||||||
|
|
||||||
|
// update field
|
||||||
|
collection.fields.addAt(9, new Field({
|
||||||
|
"hidden": false,
|
||||||
|
"id": "number1421793101",
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"name": "min_capacity",
|
||||||
|
"onlyInt": false,
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "number"
|
||||||
|
}))
|
||||||
|
|
||||||
|
// update field
|
||||||
|
collection.fields.addAt(10, new Field({
|
||||||
|
"hidden": false,
|
||||||
|
"id": "number3301820327",
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"name": "max_capacity",
|
||||||
|
"onlyInt": false,
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "number"
|
||||||
|
}))
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
})
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_586073990")
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.fields.removeById("number3301820327")
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
}, (app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_586073990")
|
||||||
|
|
||||||
|
// add field
|
||||||
|
collection.fields.addAt(7, new Field({
|
||||||
|
"hidden": false,
|
||||||
|
"id": "number3301820327",
|
||||||
|
"max": null,
|
||||||
|
"min": 1,
|
||||||
|
"name": "max_booking_capacity",
|
||||||
|
"onlyInt": false,
|
||||||
|
"presentable": false,
|
||||||
|
"required": true,
|
||||||
|
"system": false,
|
||||||
|
"type": "number"
|
||||||
|
}))
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
})
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_43114331")
|
||||||
|
|
||||||
|
// add field
|
||||||
|
collection.fields.addAt(12, new Field({
|
||||||
|
"hidden": false,
|
||||||
|
"id": "number2396794873",
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"name": "booking_capacity",
|
||||||
|
"onlyInt": false,
|
||||||
|
"presentable": false,
|
||||||
|
"required": true,
|
||||||
|
"system": false,
|
||||||
|
"type": "number"
|
||||||
|
}))
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
}, (app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_43114331")
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.fields.removeById("number2396794873")
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
})
|
||||||
22
apps/pocketbase/pb_migrations/1756243147_updated_bookings.js
Normal file
22
apps/pocketbase/pb_migrations/1756243147_updated_bookings.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_986407980")
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
unmarshal({
|
||||||
|
"listRule": "",
|
||||||
|
"viewRule": ""
|
||||||
|
}, collection)
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
}, (app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_986407980")
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
unmarshal({
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null
|
||||||
|
}, collection)
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
})
|
||||||
@@ -119,21 +119,58 @@ const BookingInterface = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateChange = (date: string) => {
|
const handleDateChange = async (date: string) => {
|
||||||
setSelectedDate(date);
|
setSelectedDate(date);
|
||||||
setSelectedTimeSlot('');
|
setSelectedTimeSlot('');
|
||||||
|
|
||||||
// Get time slots for the selected date from availableSlotsByDate
|
// Get time slots for the selected date from availableSlotsByDate
|
||||||
const slotsForDate = availableSlotsByDate[date] || [];
|
const slotsForDate = availableSlotsByDate[date] || [];
|
||||||
console.log(`Time slots for ${date}:`, slotsForDate);
|
|
||||||
|
|
||||||
// Convert to TimeSlot format for TimeSlotSelector
|
// Get bookings for this date filtered by booking type IDs
|
||||||
const formattedTimeSlots: TimeSlot[] = slotsForDate.map((slot, index) => ({
|
let bookingOverlapCounts: { [key: string]: number } = {};
|
||||||
|
try {
|
||||||
|
const bookings = await bookingApi.getBookingsForDate(date, [selectedBookingType]);
|
||||||
|
|
||||||
|
// Count overlapping bookings for each time slot
|
||||||
|
slotsForDate.forEach(slot => {
|
||||||
|
const slotStart = new Date(slot.start_time);
|
||||||
|
const slotEnd = new Date(slot.end_time);
|
||||||
|
|
||||||
|
const overlappingBookings = bookings.filter(booking => {
|
||||||
|
const bookingStart = new Date(booking.start_time);
|
||||||
|
const bookingEnd = new Date(booking.end_time);
|
||||||
|
|
||||||
|
// Check if bookings overlap with time slot
|
||||||
|
return bookingStart < slotEnd && bookingEnd > slotStart;
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalParticipants = overlappingBookings.reduce((sum, booking) =>
|
||||||
|
sum + (booking.participants_count || 0), 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const key = `${slot.start_time}-${slot.end_time}`;
|
||||||
|
bookingOverlapCounts[key] = totalParticipants;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Booking overlap counts:', bookingOverlapCounts);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching bookings for date:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to TimeSlot format and filter out fully booked slots
|
||||||
|
const bookingTypeCapacity = selectedBookingTypeData?.booking_capacity || 8;
|
||||||
|
const availableTimeSlots = slotsForDate.filter(slot => {
|
||||||
|
const key = `${slot.start_time}-${slot.end_time}`;
|
||||||
|
const overlappingCount = bookingOverlapCounts[key] || 0;
|
||||||
|
return overlappingCount < bookingTypeCapacity;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedTimeSlots: TimeSlot[] = availableTimeSlots.map((slot, index) => ({
|
||||||
id: `slot-${date}-${index}`,
|
id: `slot-${date}-${index}`,
|
||||||
start_time: slot.start_time,
|
start_time: slot.start_time,
|
||||||
end_time: slot.end_time,
|
end_time: slot.end_time,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
max_capacity: 8,
|
booking_capacity: bookingTypeCapacity,
|
||||||
booking_types: [selectedBookingType],
|
booking_types: [selectedBookingType],
|
||||||
is_reccuring: false,
|
is_reccuring: false,
|
||||||
recurrence_pattern: undefined,
|
recurrence_pattern: undefined,
|
||||||
|
|||||||
@@ -35,11 +35,14 @@ const DateSelector: React.FC<DateSelectorProps> = ({
|
|||||||
date.setDate(today.getDate() + i);
|
date.setDate(today.getDate() + i);
|
||||||
const dateString = date.toISOString().split('T')[0] || '';
|
const dateString = date.toISOString().split('T')[0] || '';
|
||||||
|
|
||||||
|
// Create date object from the ISO string to avoid timezone issues
|
||||||
|
const displayDate = new Date(dateString + 'T12:00:00.000Z');
|
||||||
|
|
||||||
days.push({
|
days.push({
|
||||||
date: dateString,
|
date: dateString,
|
||||||
day: date.getDate(),
|
day: parseInt(dateString.split('-')[2]),
|
||||||
month: date.toLocaleDateString('en', { month: 'short' }),
|
month: displayDate.toLocaleDateString('en', { month: 'short', timeZone: 'UTC' }),
|
||||||
dayName: date.toLocaleDateString('en', { weekday: 'short' }),
|
dayName: displayDate.toLocaleDateString('en', { weekday: 'short', timeZone: 'UTC' }),
|
||||||
available: availableDates.includes(dateString)
|
available: availableDates.includes(dateString)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import PocketBase from 'pocketbase';
|
import PocketBase from 'pocketbase';
|
||||||
import { BookingType, TimeSlot } from '@/types/bookings';
|
import { BookingType, TimeSlot, Booking } from '@/types/bookings';
|
||||||
|
|
||||||
// Initialize PocketBase client
|
// Initialize PocketBase client
|
||||||
export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL || 'http://127.0.0.1:8090');
|
export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL || 'http://127.0.0.1:8090');
|
||||||
@@ -122,4 +122,26 @@ export const bookingApi = {
|
|||||||
|
|
||||||
return availableSlotsByDate;
|
return availableSlotsByDate;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Get all bookings for a specific date filtered by booking type IDs
|
||||||
|
async getBookingsForDate(
|
||||||
|
date: string,
|
||||||
|
bookingTypeIds: string[]
|
||||||
|
): Promise<Booking[]> {
|
||||||
|
try {
|
||||||
|
// Create filter for booking type IDs
|
||||||
|
const bookingTypeFilter = bookingTypeIds.map(id => `booking_type = "${id}"`).join(' || ');
|
||||||
|
|
||||||
|
const bookings = await pb.collection('bookings').getFullList<Booking>({
|
||||||
|
filter: `status = "confirmed" && start_time ~ "${date}" && (${bookingTypeFilter})`,
|
||||||
|
sort: 'start_time'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Bookings for ${date}:`, bookings);
|
||||||
|
return bookings;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching bookings for date:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
@@ -9,7 +9,7 @@ export interface BaseRecord {
|
|||||||
export interface Resource extends BaseRecord {
|
export interface Resource extends BaseRecord {
|
||||||
name: string;
|
name: string;
|
||||||
type: 'wheel' | 'workstation';
|
type: 'wheel' | 'workstation';
|
||||||
capacity: number;
|
usage_capacity: number;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,9 +23,10 @@ export interface BookingType extends BaseRecord {
|
|||||||
min_duration: number;
|
min_duration: number;
|
||||||
price_per_person: number;
|
price_per_person: number;
|
||||||
resources: string; // relation to Resource
|
resources: string; // relation to Resource
|
||||||
min_capacity: number;
|
min_participants_capacity: number;
|
||||||
max_capacity: number;
|
max_participants_capacity: number;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
|
booking_capacity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time Slot entity
|
// Time Slot entity
|
||||||
@@ -36,7 +37,6 @@ export interface TimeSlot extends BaseRecord {
|
|||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
is_reccuring?: boolean;
|
is_reccuring?: boolean;
|
||||||
recurrence_pattern?: RecurrencePattern;
|
recurrence_pattern?: RecurrencePattern;
|
||||||
max_capacity: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurrence pattern structure
|
// Recurrence pattern structure
|
||||||
|
|||||||
Reference in New Issue
Block a user