feat: housekeeper can leave completion comment when finishing a cleaning task
This commit is contained in:
@@ -171,9 +171,11 @@ export function HousekeepingPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const updateStatus = async (id: string, status: HousekeepingTask['status']) => {
|
||||
const updateStatus = async (id: string, status: HousekeepingTask['status'], resolutionNotes?: string) => {
|
||||
try {
|
||||
const updated = await api.housekeeping.update(slug, id, { status })
|
||||
const payload: Record<string, unknown> = { status }
|
||||
if (resolutionNotes) payload.resolution_notes = resolutionNotes
|
||||
const updated = await api.housekeeping.update(slug, id, payload)
|
||||
if (updated.status === 'done') {
|
||||
setActiveTasks(prev => prev.filter(t => t.id !== id))
|
||||
if (activeTab === 'history' && updated.completedAt?.startsWith(historyDate)) {
|
||||
@@ -582,7 +584,7 @@ const SEVERITY_CONFIG = {
|
||||
|
||||
function TaskCard({ task, onStatusChange, onReport }: {
|
||||
task: HousekeepingTask
|
||||
onStatusChange: (id: string, status: HousekeepingTask['status']) => void
|
||||
onStatusChange: (id: string, status: HousekeepingTask['status'], resolutionNotes?: string) => void
|
||||
onReport: (id: string, note: string, severity: 'low' | 'medium' | 'high', photos: string[]) => void
|
||||
}) {
|
||||
const [reportOpen, setReportOpen] = useState(false)
|
||||
@@ -591,6 +593,8 @@ function TaskCard({ task, onStatusChange, onReport }: {
|
||||
const [reportPhotos, setReportPhotos] = useState<string[]>([])
|
||||
const [photoUploading, setPhotoUploading] = useState(false)
|
||||
const photoInputRef = useRef<HTMLInputElement>(null)
|
||||
const [completingOpen, setCompletingOpen] = useState(false)
|
||||
const [completionComment, setCompletionComment] = useState('')
|
||||
|
||||
const uploadReportPhoto = async (file: File) => {
|
||||
setPhotoUploading(true)
|
||||
@@ -755,6 +759,42 @@ function TaskCard({ task, onStatusChange, onReport }: {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{completingOpen && (
|
||||
<div className="border-t border-slate-100 dark:border-slate-700 pt-2.5 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs font-semibold text-slate-700 dark:text-slate-300 flex items-center gap-1">
|
||||
<CheckCircle2 size={11} className="text-emerald-500" /> Завершение уборки
|
||||
</p>
|
||||
<button onClick={() => setCompletingOpen(false)} className="text-slate-400 hover:text-slate-600">
|
||||
<XIcon size={13} />
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
autoFocus
|
||||
className="w-full text-xs border border-slate-200 dark:border-slate-600 rounded-lg p-2 bg-white dark:bg-slate-700 text-slate-700 dark:text-slate-300 resize-none focus:outline-none focus:border-emerald-400"
|
||||
rows={2}
|
||||
placeholder="Комментарий (необязательно)..."
|
||||
value={completionComment}
|
||||
onChange={e => setCompletionComment(e.target.value)}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter' && e.ctrlKey) {
|
||||
onStatusChange(task.id, 'done', completionComment.trim() || undefined)
|
||||
setCompletingOpen(false)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
onStatusChange(task.id, 'done', completionComment.trim() || undefined)
|
||||
setCompletingOpen(false)
|
||||
}}
|
||||
className="w-full flex items-center justify-center gap-1 text-xs py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-700 text-white font-medium transition-colors"
|
||||
>
|
||||
<CheckCircle2 size={11} /> Завершить уборку
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-1.5 pt-1">
|
||||
{task.status === 'pending' && (
|
||||
<button
|
||||
@@ -764,15 +804,15 @@ function TaskCard({ task, onStatusChange, onReport }: {
|
||||
Начать
|
||||
</button>
|
||||
)}
|
||||
{task.status === 'in_progress' && (
|
||||
{task.status === 'in_progress' && !completingOpen && (
|
||||
<button
|
||||
onClick={() => onStatusChange(task.id, 'done')}
|
||||
onClick={() => { setCompletingOpen(true); setReportOpen(false) }}
|
||||
className="flex-1 text-xs py-1.5 rounded-lg bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-300 font-medium hover:bg-emerald-100 dark:hover:bg-emerald-900/30 transition-colors"
|
||||
>
|
||||
Завершить
|
||||
</button>
|
||||
)}
|
||||
{!reportOpen && (
|
||||
{!reportOpen && !completingOpen && (
|
||||
<button
|
||||
onClick={() => setReportOpen(true)}
|
||||
title="Сообщить о неисправности"
|
||||
@@ -828,6 +868,12 @@ function HistoryCard({ task }: { task: HousekeepingTask }) {
|
||||
{task.notes}
|
||||
</p>
|
||||
)}
|
||||
{task.resolutionNotes && (
|
||||
<p className="text-xs text-emerald-700 dark:text-emerald-300 mt-1.5 bg-emerald-50 dark:bg-emerald-900/20 rounded-lg px-2 py-1.5 flex items-start gap-1.5">
|
||||
<CheckCircle2 size={11} className="shrink-0 mt-0.5" />
|
||||
{task.resolutionNotes}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center gap-3 mt-1.5 text-xs text-slate-500 dark:text-slate-400 flex-wrap">
|
||||
{(task.assignedToName || taskAny.assigneeName) && (
|
||||
<span className="flex items-center gap-1">
|
||||
|
||||
Reference in New Issue
Block a user