import { AlertTriangle, ArrowRight, Calendar, CheckCircle2, Clock, Package, RefreshCw, Trash2, Undo2, X, } from 'lucide-react'; import { useState } from 'react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import type { Asset, OccurrenceCheck } from '@/types'; type RecurringConflictModalProps = { isOpen: boolean; onClose: () => void; isLoading: boolean; checkResult: { occurrences: OccurrenceCheck[]; total_occurrences: number; conflicts_count: number; } | null; initialAssetIds: number[]; isSubmitting: boolean; onConfirmSubmit: ( occurrencesToSubmit: Array<{ starts_at: string; ends_at: string; asset_ids: number[]; }>, ) => void; }; export function RecurringConflictModal({ isOpen, onClose, isLoading, checkResult, initialAssetIds, isSubmitting, onConfirmSubmit, }: RecurringConflictModalProps) { const [omittedIndexes, setOmittedIndexes] = useState([]); // Key format: `${occurrenceIndex}_${originalAssetId}` -> replacement Asset const [replacements, setReplacements] = useState>({}); const [activeSelectorKey, setActiveSelectorKey] = useState( null, ); const occurrences = checkResult?.occurrences ?? []; function toggleOmit(index: number) { setOmittedIndexes((prev) => prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index], ); } function toggleReplacementSelector(key: string) { setActiveSelectorKey((prev) => (prev === key ? null : key)); } function selectReplacement( occIndex: number, originalAssetId: number, replacement: Asset, ) { setReplacements((prev) => ({ ...prev, [`${occIndex}_${originalAssetId}`]: replacement, })); setActiveSelectorKey(null); } function removeReplacement(occIndex: number, originalAssetId: number) { setReplacements((prev) => { const next = { ...prev }; delete next[`${occIndex}_${originalAssetId}`]; return next; }); } const activeOccurrences = occurrences.filter( (_, idx) => !omittedIndexes.includes(idx), ); const omittedCount = occurrences.length - activeOccurrences.length; // Check if there are unresolved conflicts in active occurrences const hasUnresolvedConflicts = occurrences.some((check, idx) => { if (omittedIndexes.includes(idx) || !check.has_conflict) { return false; } return check.conflicted_assets.some( (ca) => !replacements[`${idx}_${ca.id}`], ); }); const canSubmit = !isLoading && activeOccurrences.length > 0 && !hasUnresolvedConflicts && !isSubmitting; function handleConfirm() { const payload = occurrences .map((check, index) => ({ check, index })) .filter(({ index }) => !omittedIndexes.includes(index)) .map(({ check, index }) => { const finalAssetIds = initialAssetIds.map((origId) => { const replacement = replacements[`${index}_${origId}`]; return replacement ? replacement.id : origId; }); return { starts_at: check.starts_at, ends_at: check.ends_at, asset_ids: finalAssetIds, }; }); onConfirmSubmit(payload); } return ( !open && onClose()}> Confirmação de Série Semanal Análise de disponibilidade de todas as semanas da sua requisição recorrente.
{isLoading ? (

A verificar disponibilidade semana a semana…

A cruzar horários com o inventário e requisições existentes.

) : checkResult ? ( <> {/* Summary banner */}
Semanas totais

{checkResult.total_occurrences}

A requisitar

{activeOccurrences.length}

Conflitos detetados

0 ? 'text-amber-600 dark:text-amber-500' : 'text-emerald-600 dark:text-emerald-500' }`} > {checkResult.conflicts_count}

{/* Informative alert */} {checkResult.conflicts_count > 0 ? (

Existem conflitos de horário com outros pedidos!

Para cada dia assinalado, escolha se prefere{' '} apagar a reserva desse dia {' '} (excluindo-o da série) ou{' '} trocar o material {' '} por outro equipamento disponível.

) : (

Todos os equipamentos estão disponíveis para todas as semanas da série!

)} {/* Occurrence Cards List */}
{occurrences.map((check, index) => { const isOmitted = omittedIndexes.includes(index); const hasConflicts = check.has_conflict; const allConflictsResolved = hasConflicts && check.conflicted_assets.every( (ca) => replacements[ `${index}_${ca.id}` ], ); return (
Semana {index + 1}

{ check.date_formatted }

{ check.time_formatted }
{isOmitted ? ( Dia apagado / ignorado ) : allConflictsResolved ? ( Conflito resolvido com troca ) : hasConflicts ? ( Conflito de material ) : ( Equipamento disponível )}
{isOmitted ? ( ) : ( )}
{/* Details of items & conflicts */} {!isOmitted && (
{check.conflicted_assets.map( (cAsset) => { const key = `${index}_${cAsset.id}`; const replacement = replacements[ key ]; const isSelecting = activeSelectorKey === key; return (
Material em conflito:{' '} { cAsset.name }{' '} ( { cAsset.asset_tag } )

Já se encontra reservado por outro utilizador para este horário.

{replacement ? ( ) : ( )}
{/* Replacement indicator */} {replacement && (
Substituído por:{' '} { replacement.name } {' '} ( { replacement.asset_tag } )
)} {/* Interactive Alternatives Selector */} {isSelecting && !replacement && (
Equipamentos disponíveis para este dia ( { cAsset .alternatives .length } ):
{cAsset .alternatives .length === 0 ? (

Não foram encontrados equipamentos alternativos livres para este horário. Sugerimos apagar a reserva deste dia.

) : (
{cAsset.alternatives.map( ( alt, ) => (
selectReplacement( index, cAsset.id, alt, ) } >
{ alt.name } { alt.asset_tag } {alt.category_name && ( ·{' '} { alt.category_name } )}
), )}
)}
)}
); }, )} {/* Available assets for this occurrence */} {check.available_assets .length > 0 && (
Disponíveis: {check.available_assets .map( (a) => `${a.name} (${a.asset_tag})`, ) .join(', ')}
)}
)}
); })}
) : null}
{activeOccurrences.length > 0 ? ( A submeter{' '} {activeOccurrences.length}{' '} requisições {omittedCount > 0 ? ` (${omittedCount} dias ignorados)` : ''} . ) : ( Todos os dias foram apagados. Restaure pelo menos um dia para submeter. )} {hasUnresolvedConflicts && ( ⚠ Resolva os conflitos pendentes (trocando o material ou apagando o dia). )}
); }