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

@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { Plus, Trash2, ChevronUp, ChevronDown, Pencil, Check, X, Loader2, ListChecks, ToggleLeft, ToggleRight } from 'lucide-react'
import { Plus, Trash2, ChevronUp, ChevronDown, Pencil, Check, X, Loader2, ListChecks, ToggleLeft, ToggleRight, ShieldAlert } from 'lucide-react'
import { api, type ChecklistTemplate, type ChecklistItem } from '../lib/api'
import { useAuth } from '../contexts/AuthContext'
import { cn } from '../lib/utils'
@@ -97,6 +97,13 @@ export function ChecklistSettingsPage() {
} catch { /* ignore */ }
}
const toggleRequire = async (t: ChecklistTemplate) => {
try {
await api.checklists.updateTemplate(slug, t.id, { requireBeforeComplete: !t.requireBeforeComplete })
setTemplates(prev => prev.map(x => x.id === t.id ? { ...x, requireBeforeComplete: !x.requireBeforeComplete } : x))
} catch { /* ignore */ }
}
const deleteTemplate = async (id: string) => {
if (!confirm('Удалить шаблон вместе со всеми пунктами?')) return
try {
@@ -323,6 +330,30 @@ export function ChecklistSettingsPage() {
Добавить
</button>
</div>
{/* Require before complete toggle */}
<div
className={cn(
'flex items-center justify-between mt-3 pt-3 border-t border-slate-100 dark:border-slate-700 px-1 py-1.5 rounded-lg cursor-pointer select-none',
tpl.requireBeforeComplete
? 'bg-amber-50 dark:bg-amber-900/10'
: 'hover:bg-slate-50 dark:hover:bg-slate-800/50',
)}
onClick={() => toggleRequire(tpl)}
>
<div className="flex items-center gap-2">
<ShieldAlert size={14} className={tpl.requireBeforeComplete ? 'text-amber-500' : 'text-slate-400'} />
<div>
<p className={cn('text-xs font-medium', tpl.requireBeforeComplete ? 'text-amber-700 dark:text-amber-400' : 'text-slate-600 dark:text-slate-400')}>
Требовать выполнения перед завершением уборки
</p>
<p className="text-xs text-slate-400">Горничная не сможет закрыть задачу, пока не отметит все пункты</p>
</div>
</div>
{tpl.requireBeforeComplete
? <ToggleRight size={18} className="text-amber-500 shrink-0" />
: <ToggleLeft size={18} className="text-slate-300 dark:text-slate-600 shrink-0" />}
</div>
</div>
</div>
))}