'use client'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslations } from 'next-intl'; import { Button } from '@/components/ui/shared/Button'; import { FORM_SELECT_CLASS } from '@/components/shared/formSelectStyles'; import { SearchBar } from '@/components/ui/shared/SearchBar'; import { TaskCaseGroupHeader } from '@/components/ui/lab/TaskCaseGroupHeader'; import { TaskProsthesisGroupHeader } from '@/components/ui/lab/TaskProsthesisGroupHeader'; import { TaskRow } from '@/components/ui/lab/TaskRow'; import { groupTasksForDisplay } from '@/components/lab/taskListGrouping'; import { getUserFacingError } from '@/components/shared/formatApiError'; import { canEditTasks, canViewTasks } from '@/components/shared/permissions'; import { useAuth } from '@/lib/hooks/useAuth'; import { useToast } from '@/lib/hooks/useToast'; import { prosthesisCatalogApi } from '@/lib/api/prosthesis-catalog'; import { tasksApi } from '@/lib/api/tasks'; import type { LabTaskListItem, LabTaskStatus, ListLabTasksParams, PaginatedLabTasks, TaskFilterOptions, TaskSortField, } from '@/types/cases'; import type { ProsthesisCatalogEntry } from '@/types/treatment-catalog'; const PAGE_SIZE = 50; export function TasksPage() { const t = useTranslations('tasks'); const tErrors = useTranslations('errors'); const { currentOrganization, user, isAuthReady } = useAuth(); const { showError, setError, messages: toastMessages } = useToast(); const [tasks, setTasks] = useState([]); const [pagination, setPagination] = useState({ page: 1, limit: PAGE_SIZE, total: 0, totalPages: 1, }); const [filterOptions, setFilterOptions] = useState({ clinics: [], workflowSteps: [], }); const [prosthesisCatalog, setProsthesisCatalog] = useState([]); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [updatingTaskId, setUpdatingTaskId] = useState(null); const [expandedCommentsTaskId, setExpandedCommentsTaskId] = useState(null); const [search, setSearch] = useState(''); const [clinicId, setClinicId] = useState(''); const [statusFilter, setStatusFilter] = useState<'' | LabTaskStatus>('IN_PROGRESS'); const [stepCompleted, setStepCompleted] = useState(''); const [sortBy, setSortBy] = useState('date'); const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc'); const canView = canViewTasks(currentOrganization); const canEdit = canEditTasks(currentOrganization); const locale = user?.language ?? 'en'; const tRef = useRef(t); tRef.current = t; const statusOptions: { value: LabTaskStatus; label: string }[] = useMemo( () => [ { value: 'IN_PROGRESS', label: t('statusInProgress') }, { value: 'COMPLETED', label: t('statusCompleted') }, ], [t], ); const listParams = useMemo((): ListLabTasksParams => { const params: ListLabTasksParams = { page, limit: PAGE_SIZE, sortBy, sortDir, }; if (search.trim()) params.q = search.trim(); if (clinicId) params.clinicOrganizationId = clinicId; if (statusFilter) params.status = statusFilter; if (stepCompleted) params.stepCompleted = stepCompleted; return params; }, [page, search, clinicId, statusFilter, stepCompleted, sortBy, sortDir]); const displayModel = useMemo( () => groupTasksForDisplay(tasks, sortBy), [tasks, sortBy], ); const groupingDisabled = sortBy !== 'date'; const loadTasks = useCallback(async () => { setLoading(true); setError(''); try { const response = await tasksApi.list(listParams); setTasks(response.data.items); setPagination(response.data.pagination); } catch (error: unknown) { showError(getUserFacingError(error, tErrors, tRef.current('errorLoadList'))); } finally { setLoading(false); } }, [listParams, showError, setError, tErrors]); useEffect(() => { if (!canView) return; const timeout = setTimeout(() => void loadTasks(), search ? 300 : 0); return () => clearTimeout(timeout); }, [canView, loadTasks, search]); useEffect(() => { if (!canView) return; void (async () => { try { const [optionsRes, catalogRes] = await Promise.all([ tasksApi.filterOptions(), prosthesisCatalogApi.list(), ]); setFilterOptions(optionsRes.data); setProsthesisCatalog(catalogRes.data); } catch { // Non-blocking — filters fall back to empty options. } })(); }, [canView]); const handleStatusUpdate = useCallback( async (taskId: string, status: LabTaskStatus) => { if (!canEdit) return; setUpdatingTaskId(taskId); setError(''); try { await tasksApi.updateStatus(taskId, status); await loadTasks(); } catch (error: unknown) { showError(getUserFacingError(error, tErrors, t('errorUpdateTask'))); } finally { setUpdatingTaskId(null); } }, [canEdit, loadTasks, setError, showError, t, tErrors], ); const filterSelectClass = `${FORM_SELECT_CLASS} w-full rounded-md px-2 py-1.5 text-sm`; const sortHintKey = useMemo(() => { switch (sortBy) { case 'clinic': return 'groupingOffClinic'; case 'patient': return 'groupingOffPatient'; case 'prosthesis': return 'groupingOffProsthesis'; case 'taskType': return 'groupingOffTaskType'; case 'status': return 'groupingOffStatus'; default: return null; } }, [sortBy]); if (!isAuthReady) { return
{t('loading')}
; } if (!canView) { return (

{t('noPermissionTitle')}

{t('noPermissionBody')}

); } return (

{t('title')}

{t('subtitle')}

{ setSearch(v); setPage(1); }} placeholder={t('searchPlaceholder')} />
{groupingDisabled && sortHintKey ? (

{t(sortHintKey)}

) : null}
{loading && tasks.length === 0 ? (

{t('loading')}

) : tasks.length === 0 ? (

{t('emptyList')}

) : displayModel.mode === 'grouped' ? (
{displayModel.cases.map((caseGroup) => (
{caseGroup.prosthesisGroups.map((prosthesisGroup) => (
    {prosthesisGroup.tasks.map((task) => ( void handleStatusUpdate(id, status)} onToggleComments={(id) => setExpandedCommentsTaskId((prev) => (prev === id ? null : id)) } onCommentError={showError} /> ))}
))}
))}
) : (
    {displayModel.tasks.map((task) => ( void handleStatusUpdate(id, status)} onToggleComments={(id) => setExpandedCommentsTaskId((prev) => (prev === id ? null : id)) } onCommentError={showError} /> ))}
)}
{pagination.totalPages > 1 && (

{t('pageSummary', { page: pagination.page, totalPages: pagination.totalPages, total: pagination.total, })}

)}
); }