'use client'; import { useState, useEffect } from 'react'; import { Input } from '@/components/ui'; import { Textarea } from '@/components/ui'; import { Button } from '@/components/ui'; import { Select } from '@/components/ui'; import type { CreateOKRInput, CreateKeyResultInput, TeamMember, KeyResult } from '@/lib/types'; import { PERIOD_SUGGESTIONS } from '@/lib/types'; // Calcule les dates de début et de fin pour un trimestre donné function getQuarterDates(period: string): { startDate: string; endDate: string } | null { // Format attendu: "Q1 2025", "Q2 2026", etc. const match = period.match(/^Q(\d)\s+(\d{4})$/); if (!match) { return null; } const quarter = parseInt(match[1], 10); const year = parseInt(match[2], 10); let startMonth = 0; // Janvier = 0 let endMonth = 2; // Mars = 2 let endDay = 31; switch (quarter) { case 1: startMonth = 0; // Janvier endMonth = 2; // Mars endDay = 31; break; case 2: startMonth = 3; // Avril endMonth = 5; // Juin endDay = 30; break; case 3: startMonth = 6; // Juillet endMonth = 8; // Septembre endDay = 30; break; case 4: startMonth = 9; // Octobre endMonth = 11; // Décembre endDay = 31; break; default: return null; } const startDate = new Date(year, startMonth, 1); const endDate = new Date(year, endMonth, endDay); return { startDate: startDate.toISOString().split('T')[0], endDate: endDate.toISOString().split('T')[0], }; } interface KeyResultEditInput extends CreateKeyResultInput { id?: string; // If present, it's an existing Key Result to update } type KeyResultUpdate = { id: string; title?: string; targetValue?: number; unit?: string; order?: number; }; type OKRFormSubmitData = | (CreateOKRInput & { startDate: Date | string; endDate: Date | string; keyResultsUpdates?: { create?: CreateKeyResultInput[]; update?: KeyResultUpdate[]; delete?: string[]; }; }) | CreateOKRInput; interface OKRFormProps { teamMembers: TeamMember[]; onSubmit: (data: OKRFormSubmitData) => Promise; onCancel: () => void; initialData?: Partial & { keyResults?: KeyResult[] }; } export function OKRForm({ teamMembers, onSubmit, onCancel, initialData }: OKRFormProps) { const [teamMemberId, setTeamMemberId] = useState(initialData?.teamMemberId || ''); const [objective, setObjective] = useState(initialData?.objective || ''); const [description, setDescription] = useState(initialData?.description || ''); const [period, setPeriod] = useState(initialData?.period || ''); const [customPeriod, setCustomPeriod] = useState(''); const [startDate, setStartDate] = useState( initialData?.startDate ? new Date(initialData.startDate).toISOString().split('T')[0] : '' ); const [endDate, setEndDate] = useState( initialData?.endDate ? new Date(initialData.endDate).toISOString().split('T')[0] : '' ); // Initialize Key Results from existing ones if in edit mode, otherwise start with one empty const [keyResults, setKeyResults] = useState(() => { if (initialData?.keyResults && initialData.keyResults.length > 0) { return initialData.keyResults.map((kr): KeyResultEditInput => { const result = { title: kr.title, targetValue: kr.targetValue, unit: kr.unit || '%', order: kr.order, }; // @ts-expect-error - id is added to extend CreateKeyResultInput to KeyResultEditInput result.id = kr.id; return result as KeyResultEditInput; }); } return [{ title: '', targetValue: 100, unit: '%', order: 0 }]; }); const [submitting, setSubmitting] = useState(false); // Mise à jour automatique des dates quand la période change useEffect(() => { if (period && period !== 'custom' && period !== '') { const dates = getQuarterDates(period); if (dates) { setStartDate(dates.startDate); setEndDate(dates.endDate); } } }, [period]); const addKeyResult = () => { if (keyResults.length >= 5) { alert('Maximum 5 Key Results autorisés'); return; } setKeyResults([ ...keyResults, { title: '', targetValue: 100, unit: '%', order: keyResults.length }, ]); }; const removeKeyResult = (index: number) => { if (keyResults.length <= 1) { alert('Au moins un Key Result est requis'); return; } setKeyResults(keyResults.filter((_, i) => i !== index).map((kr, i) => ({ ...kr, order: i }))); }; const updateKeyResult = ( index: number, field: keyof KeyResultEditInput, value: string | number ) => { const updated = [...keyResults]; updated[index] = { ...updated[index], [field]: value }; setKeyResults(updated); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!teamMemberId || !objective || !period || !startDate || !endDate) { alert('Veuillez remplir tous les champs requis'); return; } // Validate Key Results if (keyResults.some((kr) => !kr.title || kr.targetValue <= 0)) { alert('Tous les Key Results doivent avoir un titre et une valeur cible > 0'); return; } const finalPeriod = period === 'custom' ? customPeriod : period; if (!finalPeriod) { alert('Veuillez spécifier une période'); return; } setSubmitting(true); try { // Convert dates to ISO strings for JSON serialization const startDateObj = new Date(startDate); const endDateObj = new Date(endDate); if (isNaN(startDateObj.getTime()) || isNaN(endDateObj.getTime())) { alert('Dates invalides'); setSubmitting(false); return; } const isEditMode = !!initialData?.teamMemberId; if (isEditMode) { // Type guard for Key Results with id type KeyResultWithId = KeyResultEditInput & { id: string }; const hasId = (kr: KeyResultEditInput): kr is KeyResultWithId => !!kr.id; // In edit mode, separate existing Key Results from new ones const existingKeyResults: KeyResultWithId[] = keyResults.filter(hasId); const originalKeyResults: KeyResult[] = initialData?.keyResults || []; const originalIds = new Set(originalKeyResults.map((kr: KeyResult) => kr.id)); const currentIds = new Set(existingKeyResults.map((kr: KeyResultWithId) => kr.id)); // Find deleted Key Results const deletedIds = Array.from(originalIds).filter((id) => !currentIds.has(id)); // Find updated Key Results (compare with original) const updated = existingKeyResults .map((kr: KeyResultWithId) => { const original = originalKeyResults.find((okr: KeyResult) => okr.id === kr.id); if (!original) return null; const changes: { id: string; title?: string; targetValue?: number; unit?: string; order?: number; } = { id: kr.id }; if (original.title !== kr.title) changes.title = kr.title; if (original.targetValue !== kr.targetValue) changes.targetValue = kr.targetValue; if (original.unit !== kr.unit) changes.unit = kr.unit; if (original.order !== kr.order) changes.order = kr.order; return Object.keys(changes).length > 1 ? changes : null; // More than just 'id' }) .filter( ( u ): u is { id: string; title?: string; targetValue?: number; unit?: string; order?: number; } => u !== null ); // Update order for all Key Results based on their position const allKeyResultsWithOrder = keyResults.map((kr, i) => ({ ...kr, order: i })); const existingWithOrder = allKeyResultsWithOrder.filter(hasId) as KeyResultWithId[]; const newWithOrder = allKeyResultsWithOrder.filter((kr) => !kr.id); // Update order for existing Key Results that changed position const orderUpdates = existingWithOrder .map((kr) => { const original = originalKeyResults.find((okr: KeyResult) => okr.id === kr.id); if (!original || original.order === kr.order) return null; return { id: kr.id, order: kr.order }; }) .filter((u): u is { id: string; order: number } => u !== null); // Merge order updates with other updates const allUpdates = [...updated]; orderUpdates.forEach((orderUpdate) => { const existingUpdate = allUpdates.find((u) => u.id === orderUpdate.id); if (existingUpdate) { existingUpdate.order = orderUpdate.order; } else { allUpdates.push(orderUpdate); } }); await onSubmit({ teamMemberId, objective, description: description || undefined, period: finalPeriod, startDate: startDateObj.toISOString() as Date | string, endDate: endDateObj.toISOString() as Date | string, keyResults: [], // Not used in edit mode keyResultsUpdates: { create: newWithOrder.length > 0 ? newWithOrder.map((kr) => ({ title: kr.title, targetValue: kr.targetValue, unit: kr.unit || '%', order: kr.order, })) : undefined, update: allUpdates.length > 0 ? allUpdates : undefined, delete: deletedIds.length > 0 ? deletedIds : undefined, }, } as unknown as OKRFormSubmitData); } else { // In create mode, just send Key Results normally await onSubmit({ teamMemberId, objective, description: description || undefined, period: finalPeriod, startDate: startDateObj, endDate: endDateObj, keyResults: keyResults.map((kr, i) => ({ ...kr, order: i })), }); } } catch (error) { console.error('Error submitting OKR:', error); } finally { setSubmitting(false); } }; return (
{/* Team Member */} setObjective(e.target.value)} placeholder="Ex: Améliorer la qualité du code" required /> {/* Description */}