Enhance real-time space status updates and handling
- Improve WebSocket connection reliability with updated transport options. - Introduce logic to prioritize real-time updates over initial data for live views. - Add logging for better debugging of WebSocket events and data propagation. - Update UI components (`SpaceCard`, `FloorplanViewer`) to avoid overwrites from stale data. - Refactor date handling for consistent behavior across components.main
parent
1b3c3044ca
commit
453fe68e42
|
|
@ -4,11 +4,13 @@
|
|||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="6c8b3c54-eabb-40e5-967f-c7b594c750bc" name="Changes" comment="Add "My Bookings" page and enable reservation updates and cancellations - Implement `+page.svelte` for managing user reservations with real-time updates. - Add API endpoints for updating and canceling reservations with enhanced validation and authorization. - Introduce `UpdateReservationDto` for refined reservation update handling. - Update `reservations.service.ts` to handle overlapping checks, time adjustments, and admin actions. - Extend layout to include a link to the "My Bookings" page. - Add `date-fns` library for date formatting.">
|
||||
<change beforePath="$PROJECT_DIR$/apps/api/prisma/schema.prisma" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/prisma/schema.prisma" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/api/src/spaces/spaces.controller.ts" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/src/spaces/spaces.controller.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/api/src/spaces/spaces.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/src/spaces/spaces.service.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/web/src/routes/dashboard/+page.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/src/routes/dashboard/+page.svelte" afterDir="false" />
|
||||
<list default="true" id="6c8b3c54-eabb-40e5-967f-c7b594c750bc" name="Changes" comment="Add building and floor management with admin editor modal - Implement `BuildingEditorModal.svelte` for creating, editing, and deleting buildings and floors. - Add `CreateBuildingDto`, `UpdateBuildingDto`, `CreateFloorDto`, and `UpdateFloorDto` for validation. - Extend `SpacesService` and `SpacesController` with endpoints for building and floor CRUD operations. - Update Prisma schema to include building descriptions and image URLs, and floor names. - Enhance dashboard with building management functionality for admin users.">
|
||||
<change beforePath="$PROJECT_DIR$/apps/api/src/events/events.gateway.ts" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/src/events/events.gateway.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/api/src/reservations/reservations.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/src/reservations/reservations.service.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/web/src/lib/components/BookingModal.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/src/lib/components/BookingModal.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/web/src/lib/components/FloorplanViewer.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/src/lib/components/FloorplanViewer.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/web/src/lib/components/SpaceCard.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/src/lib/components/SpaceCard.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/apps/web/src/routes/dashboard/floor/[id]/+page.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/src/routes/dashboard/floor/[id]/+page.svelte" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
|
|
@ -130,7 +132,7 @@
|
|||
<updated>1774643312599</updated>
|
||||
<workItem from="1774643313611" duration="74000" />
|
||||
<workItem from="1774643422240" duration="5278000" />
|
||||
<workItem from="1774706361502" duration="4842000" />
|
||||
<workItem from="1774706361502" duration="5157000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Initialize Hot Desking app with web and API services - Set up SvelteKit for the web interface with Tailwind CSS. - Build the API using NestJS with Prisma ORM for database interaction. - Add environment variable management and docker-compose for PostgreSQL and PgAdmin instances. - Create shared data models for consistent typing across services. - Establish basic routing and layouts for web and API services.">
|
||||
<option name="closed" value="true" />
|
||||
|
|
@ -212,7 +214,15 @@
|
|||
<option name="project" value="LOCAL" />
|
||||
<updated>1774710987605</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="11" />
|
||||
<task id="LOCAL-00011" summary="Add building and floor management with admin editor modal - Implement `BuildingEditorModal.svelte` for creating, editing, and deleting buildings and floors. - Add `CreateBuildingDto`, `UpdateBuildingDto`, `CreateFloorDto`, and `UpdateFloorDto` for validation. - Extend `SpacesService` and `SpacesController` with endpoints for building and floor CRUD operations. - Update Prisma schema to include building descriptions and image URLs, and floor names. - Enhance dashboard with building management functionality for admin users.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1774711356754</created>
|
||||
<option name="number" value="00011" />
|
||||
<option name="presentableId" value="LOCAL-00011" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1774711356754</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="12" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
|
|
@ -230,6 +240,7 @@
|
|||
<MESSAGE value="Add user management and registration features with invite-based onboarding - Implement `+page.svelte` for admin user management, allowing role updates, password resets, and invite link generation." />
|
||||
<MESSAGE value="Add user management and registration features with invite-based onboarding - Implement `+page.svelte` for admin user management, allowing role updates, password resets, and invite link generation. Update `UsersController` and `UsersService` to support user role updates, password changes, and invite token generation. - Create DTOs (`RegisterDto`, `UpdateUserDto`) for input validation in user management endpoints. - Extend `AuthService` and `AuthController` with invite-based registration logic. - Update admin layout to include "Users" link for easy access to user management." />
|
||||
<MESSAGE value="Add "My Bookings" page and enable reservation updates and cancellations - Implement `+page.svelte` for managing user reservations with real-time updates. - Add API endpoints for updating and canceling reservations with enhanced validation and authorization. - Introduce `UpdateReservationDto` for refined reservation update handling. - Update `reservations.service.ts` to handle overlapping checks, time adjustments, and admin actions. - Extend layout to include a link to the "My Bookings" page. - Add `date-fns` library for date formatting." />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="Add "My Bookings" page and enable reservation updates and cancellations - Implement `+page.svelte` for managing user reservations with real-time updates. - Add API endpoints for updating and canceling reservations with enhanced validation and authorization. - Introduce `UpdateReservationDto` for refined reservation update handling. - Update `reservations.service.ts` to handle overlapping checks, time adjustments, and admin actions. - Extend layout to include a link to the "My Bookings" page. - Add `date-fns` library for date formatting." />
|
||||
<MESSAGE value="Add building and floor management with admin editor modal - Implement `BuildingEditorModal.svelte` for creating, editing, and deleting buildings and floors. - Add `CreateBuildingDto`, `UpdateBuildingDto`, `CreateFloorDto`, and `UpdateFloorDto` for validation. - Extend `SpacesService` and `SpacesController` with endpoints for building and floor CRUD operations. - Update Prisma schema to include building descriptions and image URLs, and floor names. - Enhance dashboard with building management functionality for admin users." />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="Add building and floor management with admin editor modal - Implement `BuildingEditorModal.svelte` for creating, editing, and deleting buildings and floors. - Add `CreateBuildingDto`, `UpdateBuildingDto`, `CreateFloorDto`, and `UpdateFloorDto` for validation. - Extend `SpacesService` and `SpacesController` with endpoints for building and floor CRUD operations. - Update Prisma schema to include building descriptions and image URLs, and floor names. - Enhance dashboard with building management functionality for admin users." />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -27,7 +27,7 @@ export class EventsGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
|||
@SubscribeMessage('join_floor')
|
||||
handleJoinFloor(client: Socket, floorId: string) {
|
||||
client.join(floorId);
|
||||
console.log(`Client ${client.id} joined floor: ${floorId}`);
|
||||
console.log(`Client ${client.id} joined floor: ${floorId}. Rooms:`, Array.from(client.rooms));
|
||||
}
|
||||
|
||||
@SubscribeMessage('leave_floor')
|
||||
|
|
@ -37,6 +37,7 @@ export class EventsGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
|||
}
|
||||
|
||||
emitSpaceStatusChanged(floorId: string, spaceId: string, isOccupied: boolean, reservationData?: any) {
|
||||
console.log(`Emitting space_status_changed to floor ${floorId}:`, { spaceId, isOccupied });
|
||||
this.server.to(floorId).emit('space_status_changed', {
|
||||
spaceId,
|
||||
isOccupied,
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ export class ReservationsService {
|
|||
}
|
||||
});
|
||||
|
||||
console.log('Emitted space_status_changed for new reservation', space.floorId, spaceId);
|
||||
|
||||
return reservation;
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +136,8 @@ export class ReservationsService {
|
|||
false,
|
||||
);
|
||||
|
||||
console.log('Emitted space_status_changed for cancelled reservation', reservation.space.floorId, reservation.spaceId);
|
||||
|
||||
return { message: 'Reservation cancelled' };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@
|
|||
})
|
||||
});
|
||||
|
||||
console.log('Booking created successfully');
|
||||
dispatch('close');
|
||||
} catch (e: any) {
|
||||
error = e.message || "Failed to create booking";
|
||||
|
|
|
|||
|
|
@ -16,11 +16,18 @@
|
|||
let containerWidth = 800;
|
||||
let containerHeight = 600;
|
||||
|
||||
// Track if we have received any real-time updates to avoid overwriting them with stale initial status
|
||||
let realtimeUpdates: Record<string, any> = {};
|
||||
|
||||
$: {
|
||||
// Initialize space status from the current state
|
||||
// Initialize space status from the current state (e.g. from API)
|
||||
spaces.forEach(s => {
|
||||
// For desks, we check if there's any reservation for the selected date
|
||||
// For meeting rooms, we check if there's a reservation overlapping with the current/selected time
|
||||
// Don't overwrite if we already have a real-time update for today
|
||||
if (isToday && realtimeUpdates[s.id]) {
|
||||
spaceStatus[s.id] = realtimeUpdates[s.id];
|
||||
return;
|
||||
}
|
||||
|
||||
const activeReservation = s.reservations && s.reservations.length > 0 ? s.reservations[0] : null;
|
||||
spaceStatus[s.id] = {
|
||||
isOccupied: !!activeReservation,
|
||||
|
|
@ -74,23 +81,37 @@
|
|||
containerHeight += 40;
|
||||
}
|
||||
|
||||
// Disable real-time updates if viewing a different date than today
|
||||
$: isToday = new Date().toDateString() === selectedDate.toDateString();
|
||||
$: isToday = new Date(selectedDate.getTime()).setHours(0,0,0,0) === new Date().setHours(0,0,0,0);
|
||||
|
||||
onMount(() => {
|
||||
const socketUrl = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3000';
|
||||
socket = io(socketUrl);
|
||||
socket = io(socketUrl, {
|
||||
transports: ['websocket'], // Prefer WebSockets
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('Connected to WebSocket for floor:', floorId);
|
||||
socket.emit('join_floor', floorId);
|
||||
});
|
||||
|
||||
socket.on('space_status_changed', (data: { spaceId: string, isOccupied: boolean, reservation?: any }) => {
|
||||
if (!isToday) return; // Ignore real-time updates for historical/future views
|
||||
spaceStatus[data.spaceId] = {
|
||||
console.log('Space status changed received:', data);
|
||||
if (!isToday) {
|
||||
console.log('Ignoring real-time update: not today', {
|
||||
selectedDate: selectedDate.toDateString(),
|
||||
now: new Date().toDateString()
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const update = {
|
||||
isOccupied: data.isOccupied,
|
||||
reservation: data.reservation
|
||||
};
|
||||
|
||||
// Save to realtimeUpdates to prevent overwriting by API re-fetches
|
||||
realtimeUpdates[data.spaceId] = update;
|
||||
spaceStatus[data.spaceId] = update;
|
||||
spaceStatus = { ...spaceStatus }; // Trigger reactivity
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,24 +12,35 @@
|
|||
let isOccupied = initialStatus;
|
||||
let socket: Socket;
|
||||
|
||||
$: isToday = new Date().toDateString() === selectedDate.toDateString();
|
||||
// Track if we have received any real-time updates for this space
|
||||
let realtimeOccupied: boolean | null = null;
|
||||
|
||||
$: isToday = new Date(selectedDate.getTime()).setHours(0,0,0,0) === new Date().setHours(0,0,0,0);
|
||||
|
||||
$: if (initialStatus !== undefined) {
|
||||
if (isToday && realtimeOccupied !== null) {
|
||||
isOccupied = realtimeOccupied;
|
||||
} else {
|
||||
isOccupied = initialStatus;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Connect to WebSocket using the environment variable for the backend URL.
|
||||
const socketUrl = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3000';
|
||||
socket = io(socketUrl);
|
||||
socket = io(socketUrl, {
|
||||
transports: ['websocket'],
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('Connected to WebSocket');
|
||||
console.log('Connected to WebSocket (SpaceCard)');
|
||||
socket.emit('join_floor', floorId);
|
||||
});
|
||||
|
||||
socket.on('space_status_changed', (data: { spaceId: string, isOccupied: boolean }) => {
|
||||
if (data.spaceId === spaceId && isToday) {
|
||||
console.log('Space status updated for:', spaceId, data.isOccupied);
|
||||
realtimeOccupied = data.isOccupied;
|
||||
isOccupied = data.isOccupied;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,31 +10,33 @@
|
|||
|
||||
let floor: any = null;
|
||||
let loading = true;
|
||||
let selectedDateString = new Date().toISOString().split('T')[0];
|
||||
let selectedDateString = new Date().toLocaleDateString('en-CA'); // YYYY-MM-DD in local time
|
||||
let selectedDate = new Date();
|
||||
|
||||
$: {
|
||||
selectedDate = new Date(selectedDateString);
|
||||
if (selectedDateString === new Date().toISOString().split('T')[0]) {
|
||||
selectedDate = new Date(selectedDateString + 'T00:00:00'); // Initialize to start of day in local time
|
||||
if (selectedDateString === new Date().toLocaleDateString('en-CA')) {
|
||||
selectedDate = new Date();
|
||||
} else {
|
||||
selectedDate.setHours(7, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
let floorIdFromUrl = $page.params.id;
|
||||
$: floorIdFromUrl = $page.params.id;
|
||||
|
||||
onMount(async () => {
|
||||
const floorId = $page.params.id;
|
||||
if (!$token) {
|
||||
goto(`/login?redirect=/dashboard/floor/${floorId}`);
|
||||
goto(`/login?redirect=/dashboard/floor/${floorIdFromUrl}`);
|
||||
return;
|
||||
}
|
||||
await loadFloor(floorId);
|
||||
await loadFloor(floorIdFromUrl, selectedDate);
|
||||
loading = false;
|
||||
});
|
||||
|
||||
async function loadFloor(floorId: string) {
|
||||
async function loadFloor(id: string, date: Date) {
|
||||
try {
|
||||
floor = await apiFetch(`/spaces/floor/${floorId}?date=${selectedDate.toISOString()}`);
|
||||
floor = await apiFetch(`/spaces/floor/${id}?date=${date.toISOString()}`);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
if (e.message?.includes('401')) {
|
||||
|
|
@ -43,8 +45,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
$: if (floor && floor.id && selectedDate) {
|
||||
loadFloor(floor.id);
|
||||
$: if (floorIdFromUrl && selectedDate) {
|
||||
loadFloor(floorIdFromUrl, selectedDate);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue