diff --git a/backend/migrations/028_task_started_at.sql b/backend/migrations/028_task_started_at.sql new file mode 100644 index 0000000..b340c23 --- /dev/null +++ b/backend/migrations/028_task_started_at.sql @@ -0,0 +1 @@ +ALTER TABLE housekeeping_tasks ADD COLUMN IF NOT EXISTS started_at TIMESTAMPTZ; diff --git a/backend/src/routes/housekeeping.ts b/backend/src/routes/housekeeping.ts index 5cebd7d..1d6c629 100644 --- a/backend/src/routes/housekeeping.ts +++ b/backend/src/routes/housekeeping.ts @@ -60,7 +60,7 @@ const housekeeping: FastifyPluginAsync = async (fastify) => { // ── POST /api/hotels/:slug/housekeeping ──────────────────────────────────── fastify.post( '/api/hotels/:slug/housekeeping', { onRequest: [fastify.authenticate] }, @@ -72,13 +72,21 @@ const housekeeping: FastifyPluginAsync = async (fastify) => { const hotelId = await getHotelId(slug) if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' }) - const { room_id, type, priority = 'medium', assignee_id, notes, due_date, category = 'housekeeping' } = request.body - const { rows } = await db.query( + const { room_id, type, priority = 'medium', assignee_id, notes, due_date, category = 'housekeeping', photos = [] } = request.body + const { rows: inserted } = await db.query( `INSERT INTO housekeeping_tasks - (hotel_id, room_id, type, priority, assignee_id, notes, due_date, category) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING *`, + (hotel_id, room_id, type, priority, assignee_id, notes, due_date, category, photos) + VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING id`, [hotelId, room_id ?? null, type, priority, - assignee_id ?? null, notes ?? null, due_date ?? null, category], + assignee_id ?? null, notes ?? null, due_date ?? null, category, photos], + ) + const { rows } = await db.query( + `SELECT t.*, r.number AS room_number, r.type AS room_type, u.name AS assignee_name + FROM housekeeping_tasks t + LEFT JOIN rooms r ON r.id = t.room_id + LEFT JOIN users u ON u.id = t.assignee_id + WHERE t.id = $1`, + [inserted[0].id], ) return reply.code(201).send(rows[0]) }, @@ -109,7 +117,10 @@ const housekeeping: FastifyPluginAsync = async (fastify) => { } } - // Auto-set completed_at when marking done + // Auto-set timestamps based on status transitions + if (request.body.status === 'in_progress') { + updates.push(`started_at = COALESCE(started_at, NOW())`) + } if (request.body.status === 'done') { updates.push(`completed_at = NOW()`) } else if (request.body.status && request.body.status !== 'done') { @@ -119,12 +130,21 @@ const housekeeping: FastifyPluginAsync = async (fastify) => { if (updates.length === 0) return reply.code(400).send({ error: 'Nothing to update' }) values.push(id, hotelId) - const { rows } = await db.query( + const { rows: updated } = await db.query( `UPDATE housekeeping_tasks SET ${updates.join(', ')} - WHERE id = $${idx} AND hotel_id = $${idx + 1} RETURNING *`, + WHERE id = $${idx} AND hotel_id = $${idx + 1} RETURNING id`, values, ) - if (!rows[0]) return reply.code(404).send({ error: 'Task not found' }) + if (!updated[0]) return reply.code(404).send({ error: 'Task not found' }) + + const { rows } = await db.query( + `SELECT t.*, r.number AS room_number, r.type AS room_type, u.name AS assignee_name + FROM housekeeping_tasks t + LEFT JOIN rooms r ON r.id = t.room_id + LEFT JOIN users u ON u.id = t.assignee_id + WHERE t.id = $1`, + [updated[0].id], + ) const task = rows[0] // When task is marked in_progress → room becomes 'cleaning' diff --git a/src/pages/HousekeepingPage.tsx b/src/pages/HousekeepingPage.tsx index 875630e..75a4881 100644 --- a/src/pages/HousekeepingPage.tsx +++ b/src/pages/HousekeepingPage.tsx @@ -12,6 +12,19 @@ import { useNotifications } from '../contexts/NotificationsContext' import { format, subDays, addDays, parseISO } from 'date-fns' import { ru } from 'date-fns/locale' +// Normalize snake_case WS task objects to camelCase +function normalizeTask(raw: Record): Record { + return { + ...raw, + roomNumber: raw.roomNumber ?? raw.room_number, + roomId: raw.roomId ?? raw.room_id, + assigneeName: raw.assigneeName ?? raw.assignee_name, + dueDate: raw.dueDate ?? raw.due_date, + completedAt: raw.completedAt ?? raw.completed_at, + startedAt: raw.startedAt ?? raw.started_at, + } +} + function Toggle({ on, onChange }: { on: boolean; onChange: () => void }) { return (