feat: enforce checklist/minibar completion before marking task done

- ApiError now carries errors[] array from 422 responses
- updateStatus re-throws 422 so TaskCard can surface errors
- TaskCard: minibarCheckedLocal state + 'Подтвердить проверку минибара' button
- TaskCard: async handleComplete with completionErrors display
- Backend validation errors shown inline in completion panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 23:09:03 +03:00
parent 3c1a6ccebf
commit d98389e2bc
8 changed files with 289 additions and 24 deletions

View File

@@ -7,8 +7,10 @@ const BASE = (import.meta.env.VITE_API_URL as string | undefined) ?? 'https://ap
// ── Errors ──────────────────────────────────────────────────────────────────
export class ApiError extends Error {
constructor(public status: number, message: string) {
errors?: string[]
constructor(public status: number, message: string, errors?: string[]) {
super(message)
this.errors = errors
}
}
@@ -115,8 +117,10 @@ async function req<T>(
const data: unknown = await res.json()
if (!res.ok) {
const msg = (data as Record<string, string>)?.error ?? 'Request failed'
throw new ApiError(res.status, msg)
const d = data as Record<string, unknown>
const msg = (d?.error as string) ?? 'Request failed'
const errors = Array.isArray(d?.errors) ? (d.errors as string[]) : undefined
throw new ApiError(res.status, msg, errors)
}
return transformKeys(data) as T
@@ -652,12 +656,13 @@ export const api = {
task_type: data.taskType ?? null,
}),
updateTemplate: (slug: string, templateId: string, data: { name?: string; taskType?: string | null; isActive?: boolean; sortOrder?: number }) =>
updateTemplate: (slug: string, templateId: string, data: { name?: string; taskType?: string | null; isActive?: boolean; sortOrder?: number; requireBeforeComplete?: boolean }) =>
req<ChecklistTemplate>('PATCH', `/api/hotels/${slug}/checklist-templates/${templateId}`, {
name: data.name,
task_type: data.taskType,
is_active: data.isActive,
sort_order: data.sortOrder,
require_before_complete: data.requireBeforeComplete,
}),
deleteTemplate: (slug: string, templateId: string) =>
@@ -686,6 +691,14 @@ export const api = {
uncompleteItem: (slug: string, taskId: string, itemId: string) =>
req<void>('DELETE', `/api/hotels/${slug}/housekeeping/${taskId}/checklist/${itemId}/complete`),
getHousekeepingRules: (slug: string) =>
req<{ requireMinibarCheck: boolean }>('GET', `/api/hotels/${slug}/housekeeping-rules`),
updateHousekeepingRules: (slug: string, data: { requireMinibarCheck: boolean }) =>
req<{ requireMinibarCheck: boolean }>('PATCH', `/api/hotels/${slug}/housekeeping-rules`, {
require_minibar_check: data.requireMinibarCheck,
}),
},
// ── Minibar ───────────────────────────────────────────────────────────────
@@ -842,6 +855,7 @@ export interface HkPayload {
status?: string; assignee_id?: string; notes?: string; due_date?: string
category?: string; photos?: string[]
resolution_notes?: string; resolution_photos?: string[]
minibar_checked?: boolean
}
export interface HkSettings {
@@ -1280,6 +1294,7 @@ export interface ChecklistTemplate {
name: string
taskType: string | null
isActive: boolean
requireBeforeComplete: boolean
sortOrder: number
createdAt: string
items: ChecklistItem[]