feat: enhance OKR management by adding permission checks for editing and deleting, and updating OKR forms to handle key results more effectively
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 4m44s
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 4m44s
This commit is contained in:
140
src/app/teams/[id]/okrs/[okrId]/edit/page.tsx
Normal file
140
src/app/teams/[id]/okrs/[okrId]/edit/page.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { OKRForm } from '@/components/okrs';
|
||||
import { Card } from '@/components/ui';
|
||||
import type { CreateOKRInput, TeamMember, OKR, KeyResult } from '@/lib/types';
|
||||
|
||||
type OKRWithTeamMember = OKR & {
|
||||
teamMember: {
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
};
|
||||
userId: string;
|
||||
team: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export default function EditOKRPage() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const teamId = params.id as string;
|
||||
const okrId = params.okrId as string;
|
||||
const [okr, setOkr] = useState<OKRWithTeamMember | null>(null);
|
||||
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch OKR and team members in parallel
|
||||
Promise.all([
|
||||
fetch(`/api/okrs/${okrId}`).then((res) => {
|
||||
if (!res.ok) {
|
||||
throw new Error('OKR not found');
|
||||
}
|
||||
return res.json();
|
||||
}),
|
||||
fetch(`/api/teams/${teamId}`).then((res) => res.json()),
|
||||
])
|
||||
.then(([okrData, teamData]) => {
|
||||
setOkr(okrData);
|
||||
setTeamMembers(teamData.members || []);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching data:', error);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [okrId, teamId]);
|
||||
|
||||
const handleSubmit = async (data: CreateOKRInput & { keyResultsUpdates?: { create?: any[]; update?: any[]; delete?: string[] } }) => {
|
||||
// Convert to UpdateOKRInput format
|
||||
const updateData = {
|
||||
objective: data.objective,
|
||||
description: data.description || undefined,
|
||||
period: data.period,
|
||||
startDate: typeof data.startDate === 'string' ? new Date(data.startDate) : data.startDate,
|
||||
endDate: typeof data.endDate === 'string' ? new Date(data.endDate) : data.endDate,
|
||||
};
|
||||
|
||||
const payload: any = {
|
||||
...updateData,
|
||||
startDate: updateData.startDate.toISOString(),
|
||||
endDate: updateData.endDate.toISOString(),
|
||||
};
|
||||
|
||||
// Add Key Results updates if in edit mode
|
||||
if (data.keyResultsUpdates) {
|
||||
payload.keyResultsUpdates = data.keyResultsUpdates;
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/okrs/${okrId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Erreur lors de la mise à jour de l\'OKR');
|
||||
}
|
||||
|
||||
router.push(`/teams/${teamId}/okrs/${okrId}`);
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
||||
<div className="text-center">Chargement...</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (!okr) {
|
||||
return (
|
||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
||||
<div className="text-center">OKR non trouvé</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
// Prepare initial data for the form
|
||||
const initialData: Partial<CreateOKRInput> & { keyResults?: KeyResult[] } = {
|
||||
teamMemberId: okr.teamMemberId,
|
||||
objective: okr.objective,
|
||||
description: okr.description || undefined,
|
||||
period: okr.period,
|
||||
startDate: okr.startDate,
|
||||
endDate: okr.endDate,
|
||||
keyResults: okr.keyResults || [],
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
||||
<div className="mb-6">
|
||||
<Link href={`/teams/${teamId}/okrs/${okrId}`} className="text-muted hover:text-foreground">
|
||||
← Retour à l'OKR
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Card className="p-6">
|
||||
<h1 className="text-2xl font-bold text-foreground mb-6">Modifier l'OKR</h1>
|
||||
<OKRForm
|
||||
teamMembers={teamMembers}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() => router.push(`/teams/${teamId}/okrs/${okrId}`)}
|
||||
initialData={initialData}
|
||||
/>
|
||||
</Card>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,12 @@ type OKRWithTeamMember = OKR & {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
permissions?: {
|
||||
isAdmin: boolean;
|
||||
isConcernedMember: boolean;
|
||||
canEdit: boolean;
|
||||
canDelete: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export default function OKRDetailPage() {
|
||||
@@ -64,8 +70,6 @@ export default function OKRDetailPage() {
|
||||
const okrId = params.okrId as string;
|
||||
const [okr, setOkr] = useState<OKRWithTeamMember | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [isConcernedMember, setIsConcernedMember] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch OKR
|
||||
@@ -78,10 +82,6 @@ export default function OKRDetailPage() {
|
||||
})
|
||||
.then((data) => {
|
||||
setOkr(data);
|
||||
// Check if current user is admin or the concerned member
|
||||
// This will be properly checked server-side, but we set flags for UI
|
||||
setIsAdmin(data.teamMember?.team?.id ? true : false);
|
||||
setIsConcernedMember(data.teamMember?.userId ? true : false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching OKR:', error);
|
||||
@@ -141,7 +141,8 @@ export default function OKRDetailPage() {
|
||||
const progress = okr.progress || 0;
|
||||
const progressColor =
|
||||
progress >= 75 ? 'var(--success)' : progress >= 25 ? 'var(--accent)' : 'var(--destructive)';
|
||||
const canEdit = isAdmin || isConcernedMember;
|
||||
const canEdit = okr.permissions?.canEdit ?? false;
|
||||
const canDelete = okr.permissions?.canDelete ?? false;
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
||||
@@ -222,19 +223,30 @@ export default function OKRDetailPage() {
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
{isAdmin && (
|
||||
{(canEdit || canDelete) && (
|
||||
<div className="mt-4 flex gap-2">
|
||||
<Button
|
||||
onClick={handleDelete}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
style={{
|
||||
color: 'var(--destructive)',
|
||||
borderColor: 'var(--destructive)',
|
||||
}}
|
||||
>
|
||||
Supprimer
|
||||
</Button>
|
||||
{canEdit && (
|
||||
<Button
|
||||
onClick={() => router.push(`/teams/${teamId}/okrs/${okrId}/edit`)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Éditer
|
||||
</Button>
|
||||
)}
|
||||
{canDelete && (
|
||||
<Button
|
||||
onClick={handleDelete}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
style={{
|
||||
color: 'var(--destructive)',
|
||||
borderColor: 'var(--destructive)',
|
||||
}}
|
||||
>
|
||||
Supprimer
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@@ -78,7 +78,7 @@ export default async function TeamDetailPage({ params }: TeamDetailPageProps) {
|
||||
</Card>
|
||||
|
||||
{/* OKRs Section */}
|
||||
<OKRsList okrsData={okrsData} teamId={id} />
|
||||
<OKRsList okrsData={okrsData} teamId={id} isAdmin={isAdmin} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user