From 97045342b733221b1ec06eefb321aca11ad0c5e0 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Wed, 7 Jan 2026 17:18:16 +0100 Subject: [PATCH] feat: refactor ObjectivesPage to utilize ObjectivesList component for improved rendering and simplify OKR status handling in OKRCard with compact view option --- src/app/objectives/page.tsx | 238 +------------------------ src/components/okrs/OKRCard.tsx | 134 ++++++++++++-- src/components/okrs/OKRsList.tsx | 143 +++++++++++---- src/components/okrs/ObjectivesList.tsx | 122 +++++++++++++ src/components/okrs/index.ts | 1 + 5 files changed, 354 insertions(+), 284 deletions(-) create mode 100644 src/components/okrs/ObjectivesList.tsx diff --git a/src/app/objectives/page.tsx b/src/app/objectives/page.tsx index 14e98a1..bbe3bee 100644 --- a/src/app/objectives/page.tsx +++ b/src/app/objectives/page.tsx @@ -2,72 +2,8 @@ import { auth } from '@/lib/auth'; import { redirect } from 'next/navigation'; import Link from 'next/link'; import { getUserOKRs } from '@/services/okrs'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui'; -import { Badge } from '@/components/ui'; -import { getGravatarUrl } from '@/lib/gravatar'; -import type { OKRStatus, KeyResultStatus } from '@/lib/types'; -import { OKR_STATUS_LABELS, KEY_RESULT_STATUS_LABELS } from '@/lib/types'; - -// Helper functions for status colors -function getOKRStatusColor(status: OKRStatus): { bg: string; color: string } { - switch (status) { - case 'NOT_STARTED': - return { - bg: 'color-mix(in srgb, #6b7280 15%, transparent)', - color: '#6b7280', - }; - case 'IN_PROGRESS': - return { - bg: 'color-mix(in srgb, #3b82f6 15%, transparent)', - color: '#3b82f6', - }; - case 'COMPLETED': - return { - bg: 'color-mix(in srgb, #10b981 15%, transparent)', - color: '#10b981', - }; - case 'CANCELLED': - return { - bg: 'color-mix(in srgb, #ef4444 15%, transparent)', - color: '#ef4444', - }; - default: - return { - bg: 'color-mix(in srgb, #6b7280 15%, transparent)', - color: '#6b7280', - }; - } -} - -function getKeyResultStatusColor(status: KeyResultStatus): { bg: string; color: string } { - switch (status) { - case 'NOT_STARTED': - return { - bg: 'color-mix(in srgb, #6b7280 12%, transparent)', - color: '#6b7280', - }; - case 'IN_PROGRESS': - return { - bg: 'color-mix(in srgb, #3b82f6 12%, transparent)', - color: '#3b82f6', - }; - case 'COMPLETED': - return { - bg: 'color-mix(in srgb, #10b981 12%, transparent)', - color: '#10b981', - }; - case 'AT_RISK': - return { - bg: 'color-mix(in srgb, #f59e0b 12%, transparent)', - color: '#f59e0b', - }; - default: - return { - bg: 'color-mix(in srgb, #6b7280 12%, transparent)', - color: '#6b7280', - }; - } -} +import { Card } from '@/components/ui'; +import { ObjectivesList } from '@/components/okrs/ObjectivesList'; export default async function ObjectivesPage() { const session = await auth(); @@ -120,8 +56,8 @@ export default async function ObjectivesPage() {
🎯

Aucun OKR défini

- Vous n'avez pas encore d'OKR défini. Contactez un administrateur d'équipe pour - en créer. + Vous n'avez pas encore d'OKR défini. Contactez un administrateur d'équipe + pour en créer.

@@ -130,172 +66,8 @@ export default async function ObjectivesPage() { ) : ( -
- {periods.map((period) => { - const periodOKRs = okrsByPeriod[period]; - const totalProgress = - periodOKRs.reduce((sum, okr) => sum + (okr.progress || 0), 0) / periodOKRs.length; - - return ( -
- {/* Period Header */} -
-
- - {period} - - - {periodOKRs.length} OKR{periodOKRs.length !== 1 ? 's' : ''} - -
-
- Progression moyenne: {Math.round(totalProgress)}% -
-
- - {/* OKRs Grid */} -
- {periodOKRs.map((okr) => { - const progress = okr.progress || 0; - const progressColor = - progress >= 75 ? '#10b981' : progress >= 25 ? '#f59e0b' : '#ef4444'; - - return ( - - - -
- {okr.objective} - - {OKR_STATUS_LABELS[okr.status]} - -
- {okr.description && ( -

{okr.description}

- )} -
- 👥 - {okr.team.name} -
-
- - {/* Progress Bar */} -
-
- Progression - - {progress}% - -
-
-
-
-
- - {/* Key Results Preview */} - {okr.keyResults && okr.keyResults.length > 0 && ( -
-
- Key Results ({okr.keyResults.length}) -
-
- {okr.keyResults.slice(0, 3).map((kr) => { - const krProgress = - kr.targetValue > 0 ? (kr.currentValue / kr.targetValue) * 100 : 0; - const krProgressColor = - krProgress >= 100 - ? '#10b981' - : krProgress >= 50 - ? '#f59e0b' - : '#ef4444'; - - return ( -
-
- - {kr.title} - - - {KEY_RESULT_STATUS_LABELS[kr.status]} - -
-
- - {kr.currentValue} / {kr.targetValue} {kr.unit} - - - {Math.round(krProgress)}% - -
-
-
-
-
- ); - })} - {okr.keyResults.length > 3 && ( -
- +{okr.keyResults.length - 3} autre{okr.keyResults.length - 3 !== 1 ? 's' : ''} -
- )} -
-
- )} - - {/* Dates */} -
- - {new Date(okr.startDate).toLocaleDateString('fr-FR', { - day: 'numeric', - month: 'short', - })} - - - - {new Date(okr.endDate).toLocaleDateString('fr-FR', { - day: 'numeric', - month: 'short', - year: 'numeric', - })} - -
- - - - ); - })} -
-
- ); - })} -
+ )} ); } - diff --git a/src/components/okrs/OKRCard.tsx b/src/components/okrs/OKRCard.tsx index 3ebd27a..8a83d24 100644 --- a/src/components/okrs/OKRCard.tsx +++ b/src/components/okrs/OKRCard.tsx @@ -1,11 +1,10 @@ 'use client'; -import { useState, useTransition } from 'react'; +import { useTransition } 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 { Button } from '@/components/ui'; import { getGravatarUrl } from '@/lib/gravatar'; import type { OKR, KeyResult, OKRStatus, KeyResultStatus } from '@/lib/types'; import { OKR_STATUS_LABELS, KEY_RESULT_STATUS_LABELS } from '@/lib/types'; @@ -75,9 +74,10 @@ interface OKRCardProps { okr: OKR & { teamMember?: { user: { id: string; email: string; name: string | null } } }; teamId: string; isAdmin?: boolean; + compact?: boolean; } -export function OKRCard({ okr, teamId, isAdmin = false }: OKRCardProps) { +export function OKRCard({ okr, teamId, isAdmin = false, compact = false }: OKRCardProps) { const router = useRouter(); const [isPending, startTransition] = useTransition(); const progress = okr.progress || 0; @@ -100,18 +100,128 @@ export function OKRCard({ okr, teamId, isAdmin = false }: OKRCardProps) { if (!response.ok) { const error = await response.json(); - alert(error.error || 'Erreur lors de la suppression de l\'OKR'); + alert(error.error || "Erreur lors de la suppression de l'OKR"); return; } router.refresh(); } catch (error) { console.error('Error deleting OKR:', error); - alert('Erreur lors de la suppression de l\'OKR'); + alert("Erreur lors de la suppression de l'OKR"); } }); }; + if (compact) { + return ( + + + +
+
+ 🎯 +
+ + {okr.objective} + + {okr.teamMember && ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {okr.teamMember.user.name + + {okr.teamMember.user.name || okr.teamMember.user.email} + +
+ )} +
+
+
+ {isAdmin && ( + + )} + + {okr.period} + +
+
+
+ +
+ {/* Progress Bar */} +
+
+ Progression + + {progress}% + +
+
+
+
+
+ + {/* Status */} +
+ + {OKR_STATUS_LABELS[okr.status]} + + {okr.keyResults && okr.keyResults.length > 0 && ( + + {okr.keyResults.length} KR{okr.keyResults.length !== 1 ? 's' : ''} + + )} +
+
+ + + + ); + } + return ( @@ -141,7 +251,7 @@ export function OKRCard({ okr, teamId, isAdmin = false }: OKRCardProps) {
- + {/* Action Zone */}
{isAdmin && ( @@ -208,9 +318,7 @@ export function OKRCard({ okr, teamId, isAdmin = false }: OKRCardProps) { {/* Status */}
Statut: - - {OKR_STATUS_LABELS[okr.status]} - + {OKR_STATUS_LABELS[okr.status]}
{/* Key Results List */} @@ -223,7 +331,8 @@ export function OKRCard({ okr, teamId, isAdmin = false }: OKRCardProps) { {okr.keyResults .sort((a, b) => a.order - b.order) .map((kr: KeyResult) => { - const krProgress = kr.targetValue > 0 ? (kr.currentValue / kr.targetValue) * 100 : 0; + const krProgress = + kr.targetValue > 0 ? (kr.currentValue / kr.targetValue) * 100 : 0; const krProgressColor = krProgress >= 100 ? 'var(--success)' @@ -234,7 +343,9 @@ export function OKRCard({ okr, teamId, isAdmin = false }: OKRCardProps) { return (
- {kr.title} + + {kr.title} + ); } - diff --git a/src/components/okrs/OKRsList.tsx b/src/components/okrs/OKRsList.tsx index a387122..6a91579 100644 --- a/src/components/okrs/OKRsList.tsx +++ b/src/components/okrs/OKRsList.tsx @@ -1,12 +1,13 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { OKRCard } from './OKRCard'; -import { Card, ToggleGroup, type ToggleOption } from '@/components/ui'; +import { Card, ToggleGroup } from '@/components/ui'; import { getGravatarUrl } from '@/lib/gravatar'; import type { OKR } from '@/lib/types'; type ViewMode = 'grid' | 'grouped'; +type CardViewMode = 'detailed' | 'compact'; interface OKRsListProps { okrsData: Array<{ @@ -21,8 +22,23 @@ interface OKRsListProps { isAdmin?: boolean; } +const CARD_VIEW_STORAGE_KEY = 'okr-card-view-mode'; + export function OKRsList({ okrsData, teamId, isAdmin = false }: OKRsListProps) { const [viewMode, setViewMode] = useState('grouped'); + const [cardViewMode, setCardViewMode] = useState(() => { + if (typeof window !== 'undefined') { + const stored = localStorage.getItem(CARD_VIEW_STORAGE_KEY); + return (stored as CardViewMode) || 'detailed'; + } + return 'detailed'; + }); + + useEffect(() => { + if (typeof window !== 'undefined') { + localStorage.setItem(CARD_VIEW_STORAGE_KEY, cardViewMode); + } + }, [cardViewMode]); // Flatten OKRs for grid view const allOKRs = okrsData.flatMap((tm) => @@ -39,9 +55,7 @@ export function OKRsList({ okrsData, teamId, isAdmin = false }: OKRsListProps) {
🎯

Aucun OKR défini

-

- Aucun OKR n'a encore été défini pour cette équipe -

+

Aucun OKR n'a encore été défini pour cette équipe

); } @@ -49,37 +63,78 @@ export function OKRsList({ okrsData, teamId, isAdmin = false }: OKRsListProps) { return (
{/* View Toggle */} -
+

OKRs

- - - - ), - }, - { - value: 'grid', - label: 'Grille', - icon: ( - - - - ), - }, - ]} - /> +
+ + + + ), + }, + { + value: 'compact', + label: 'Mini', + icon: ( + + + + ), + }, + ]} + /> + + + + ), + }, + { + value: 'grid', + label: 'Grille', + icon: ( + + + + ), + }, + ]} + /> +
{/* Grouped View */} @@ -113,7 +168,9 @@ export function OKRsList({ okrsData, teamId, isAdmin = false }: OKRsListProps) {
{/* OKRs Grid */} -
+
{teamMember.okrs.map((okr) => ( ))}
@@ -133,13 +191,20 @@ export function OKRsList({ okrsData, teamId, isAdmin = false }: OKRsListProps) {
) : ( /* Grid View */ -
+
{allOKRs.map((okr) => ( - + ))}
)}
); } - diff --git a/src/components/okrs/ObjectivesList.tsx b/src/components/okrs/ObjectivesList.tsx new file mode 100644 index 0000000..2728f8c --- /dev/null +++ b/src/components/okrs/ObjectivesList.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { OKRCard } from './OKRCard'; +import { ToggleGroup } from '@/components/ui'; +import type { OKR } from '@/lib/types'; + +type CardViewMode = 'detailed' | 'compact'; + +interface ObjectivesListProps { + okrsByPeriod: Record< + string, + Array + >; + periods: string[]; +} + +const CARD_VIEW_STORAGE_KEY = 'okr-card-view-mode'; + +export function ObjectivesList({ okrsByPeriod, periods }: ObjectivesListProps) { + const [cardViewMode, setCardViewMode] = useState(() => { + if (typeof window !== 'undefined') { + const stored = localStorage.getItem(CARD_VIEW_STORAGE_KEY); + return (stored as CardViewMode) || 'detailed'; + } + return 'detailed'; + }); + + useEffect(() => { + if (typeof window !== 'undefined') { + localStorage.setItem(CARD_VIEW_STORAGE_KEY, cardViewMode); + } + }, [cardViewMode]); + + return ( +
+ {/* Global View Toggle */} +
+ + + + ), + }, + { + value: 'compact', + label: 'Mini', + icon: ( + + + + ), + }, + ]} + /> +
+ + {periods.map((period) => { + const periodOKRs = okrsByPeriod[period]; + const totalProgress = + periodOKRs.reduce((sum, okr) => sum + (okr.progress || 0), 0) / periodOKRs.length; + + return ( +
+ {/* Period Header */} +
+
+ + {period} + + + {periodOKRs.length} OKR{periodOKRs.length !== 1 ? 's' : ''} + +
+
+ Progression moyenne:{' '} + {Math.round(totalProgress)}% +
+
+ + {/* OKRs Grid */} +
+ {periodOKRs.map((okr) => ( + + ))} +
+
+ ); + })} +
+ ); +} diff --git a/src/components/okrs/index.ts b/src/components/okrs/index.ts index 9fe390f..1730db1 100644 --- a/src/components/okrs/index.ts +++ b/src/components/okrs/index.ts @@ -2,4 +2,5 @@ export { OKRCard } from './OKRCard'; export { OKRForm } from './OKRForm'; export { KeyResultItem } from './KeyResultItem'; export { OKRsList } from './OKRsList'; +export { ObjectivesList } from './ObjectivesList';