diff --git a/src/app/weekly-checkin/[id]/page.tsx b/src/app/weekly-checkin/[id]/page.tsx index aef8caa..7648f1b 100644 --- a/src/app/weekly-checkin/[id]/page.tsx +++ b/src/app/weekly-checkin/[id]/page.tsx @@ -87,9 +87,25 @@ export default async function WeeklyCheckInSessionPage({ params }: WeeklyCheckIn - {/* Current Quarter OKRs */} + {/* Current Quarter OKRs - editable by participant or team admin */} {currentQuarterOKRs.length > 0 && ( - + { + const participantTeamIds = new Set( + currentQuarterOKRs.map((okr) => okr.team?.id).filter(Boolean) as string[] + ); + const adminTeamIds = userTeams + .filter((t) => t.userRole === 'ADMIN') + .map((t) => t.id); + return adminTeamIds.some((tid) => participantTeamIds.has(tid)); + })() + } + /> )} {/* Live Wrapper + Board */} diff --git a/src/components/weekly-checkin/CurrentQuarterOKRs.tsx b/src/components/weekly-checkin/CurrentQuarterOKRs.tsx index 7274f44..78a0009 100644 --- a/src/components/weekly-checkin/CurrentQuarterOKRs.tsx +++ b/src/components/weekly-checkin/CurrentQuarterOKRs.tsx @@ -1,10 +1,13 @@ 'use client'; import { useState } from 'react'; +import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui'; import { Badge } from '@/components/ui'; -import type { OKR } from '@/lib/types'; +import { Input } from '@/components/ui'; +import { Button } from '@/components/ui'; +import type { OKR, KeyResult } from '@/lib/types'; import { OKR_STATUS_LABELS } from '@/lib/types'; type OKRWithTeam = OKR & { @@ -17,10 +20,12 @@ type OKRWithTeam = OKR & { interface CurrentQuarterOKRsProps { okrs: OKRWithTeam[]; period: string; + canEdit?: boolean; } -export function CurrentQuarterOKRs({ okrs, period }: CurrentQuarterOKRsProps) { +export function CurrentQuarterOKRs({ okrs, period, canEdit = false }: CurrentQuarterOKRsProps) { const [isExpanded, setIsExpanded] = useState(true); + const router = useRouter(); if (okrs.length === 0) { return null; @@ -60,7 +65,11 @@ export function CurrentQuarterOKRs({ okrs, period }: CurrentQuarterOKRsProps) {
-

{okr.objective}

+ router.refresh()} + /> {okr.progress !== undefined && ( - {okr.progress}% + {okr.progress}% )}
{okr.description && ( @@ -80,24 +89,18 @@ export function CurrentQuarterOKRs({ okrs, period }: CurrentQuarterOKRsProps) { )} {okr.keyResults && okr.keyResults.length > 0 && (
    - {okr.keyResults.slice(0, 3).map((kr) => { - const krProgress = kr.targetValue > 0 - ? Math.round((kr.currentValue / kr.targetValue) * 100) - : 0; - return ( -
  • - - {kr.title} - - {kr.currentValue}/{kr.targetValue} {kr.unit} - - ({krProgress}%) -
  • - ); - })} - {okr.keyResults.length > 3 && ( + {okr.keyResults.slice(0, 5).map((kr) => ( + router.refresh()} + /> + ))} + {okr.keyResults.length > 5 && (
  • - +{okr.keyResults.length - 3} autre{okr.keyResults.length - 3 > 1 ? 's' : ''} + +{okr.keyResults.length - 5} autre{okr.keyResults.length - 5 > 1 ? 's' : ''}
  • )}
@@ -130,6 +133,179 @@ export function CurrentQuarterOKRs({ okrs, period }: CurrentQuarterOKRsProps) { ); } +function EditableObjective({ + okr, + canEdit, + onUpdate, +}: { + okr: OKRWithTeam; + canEdit: boolean; + onUpdate: () => void; +}) { + const [isEditing, setIsEditing] = useState(false); + const [objective, setObjective] = useState(okr.objective); + const [updating, setUpdating] = useState(false); + + const handleSave = async () => { + setUpdating(true); + try { + const res = await fetch(`/api/okrs/${okr.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ objective: objective.trim() }), + }); + if (!res.ok) { + const err = await res.json(); + alert(err.error || 'Erreur lors de la mise à jour'); + return; + } + setIsEditing(false); + onUpdate(); + } catch (e) { + console.error(e); + alert('Erreur lors de la mise à jour'); + } finally { + setUpdating(false); + } + }; + + if (canEdit && isEditing) { + return ( +
+ setObjective(e.target.value)} + className="flex-1 h-8 text-sm" + autoFocus + /> + + +
+ ); + } + + return ( +

setIsEditing(true) : undefined} + role={canEdit ? 'button' : undefined} + > + {okr.objective} +

+ ); +} + +function EditableKeyResultRow({ + kr, + okrId, + canEdit, + onUpdate, +}: { + kr: KeyResult; + okrId: string; + canEdit: boolean; + onUpdate: () => void; +}) { + const [isEditing, setIsEditing] = useState(false); + const [currentValue, setCurrentValue] = useState(kr.currentValue); + const [updating, setUpdating] = useState(false); + + const krProgress = + kr.targetValue > 0 ? Math.round((kr.currentValue / kr.targetValue) * 100) : 0; + + const handleSave = async () => { + setUpdating(true); + try { + const res = await fetch(`/api/okrs/${okrId}/key-results/${kr.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ currentValue: Number(currentValue) }), + }); + if (!res.ok) { + const err = await res.json(); + alert(err.error || 'Erreur lors de la mise à jour'); + return; + } + setIsEditing(false); + onUpdate(); + } catch (e) { + console.error(e); + alert('Erreur lors de la mise à jour'); + } finally { + setUpdating(false); + } + }; + + if (canEdit && isEditing) { + return ( +
  • + + {kr.title} +
    + setCurrentValue(Number(e.target.value))} + min={0} + max={kr.targetValue * 2} + step="0.1" + className="h-6 w-16 text-xs" + /> + / {kr.targetValue} {kr.unit} +
    +
    + + +
    +
  • + ); + } + + return ( +
  • + + {kr.title} + + {kr.currentValue}/{kr.targetValue} {kr.unit} + + ({krProgress}%) + {canEdit && ( + + )} +
  • + ); +} + function getOKRStatusColor(status: OKR['status']): { bg: string; color: string } { switch (status) { case 'NOT_STARTED':