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:
@@ -5,7 +5,7 @@ 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 } from '@/lib/types';
|
||||
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é
|
||||
@@ -57,11 +57,15 @@ function getQuarterDates(period: string): { startDate: string; endDate: string }
|
||||
};
|
||||
}
|
||||
|
||||
interface KeyResultEditInput extends CreateKeyResultInput {
|
||||
id?: string; // If present, it's an existing Key Result to update
|
||||
}
|
||||
|
||||
interface OKRFormProps {
|
||||
teamMembers: TeamMember[];
|
||||
onSubmit: (data: CreateOKRInput) => Promise<void>;
|
||||
onSubmit: (data: CreateOKRInput & { keyResultsUpdates?: { create?: CreateKeyResultInput[]; update?: Array<{ id: string; title?: string; targetValue?: number; unit?: string; order?: number }>; delete?: string[] } }) => Promise<void>;
|
||||
onCancel: () => void;
|
||||
initialData?: Partial<CreateOKRInput>;
|
||||
initialData?: Partial<CreateOKRInput> & { keyResults?: KeyResult[] };
|
||||
}
|
||||
|
||||
export function OKRForm({ teamMembers, onSubmit, onCancel, initialData }: OKRFormProps) {
|
||||
@@ -76,11 +80,19 @@ export function OKRForm({ teamMembers, onSubmit, onCancel, initialData }: OKRFor
|
||||
const [endDate, setEndDate] = useState(
|
||||
initialData?.endDate ? new Date(initialData.endDate).toISOString().split('T')[0] : ''
|
||||
);
|
||||
const [keyResults, setKeyResults] = useState<CreateKeyResultInput[]>(
|
||||
initialData?.keyResults || [
|
||||
{ title: '', targetValue: 100, unit: '%', order: 0 },
|
||||
]
|
||||
);
|
||||
// Initialize Key Results from existing ones if in edit mode, otherwise start with one empty
|
||||
const [keyResults, setKeyResults] = useState<KeyResultEditInput[]>(() => {
|
||||
if (initialData?.keyResults && initialData.keyResults.length > 0) {
|
||||
return initialData.keyResults.map((kr) => ({
|
||||
id: kr.id,
|
||||
title: kr.title,
|
||||
targetValue: kr.targetValue,
|
||||
unit: kr.unit,
|
||||
order: kr.order,
|
||||
}));
|
||||
}
|
||||
return [{ title: '', targetValue: 100, unit: '%', order: 0 }];
|
||||
});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
// Mise à jour automatique des dates quand la période change
|
||||
@@ -113,7 +125,7 @@ export function OKRForm({ teamMembers, onSubmit, onCancel, initialData }: OKRFor
|
||||
setKeyResults(keyResults.filter((_, i) => i !== index).map((kr, i) => ({ ...kr, order: i })));
|
||||
};
|
||||
|
||||
const updateKeyResult = (index: number, field: keyof CreateKeyResultInput, value: any) => {
|
||||
const updateKeyResult = (index: number, field: keyof KeyResultEditInput, value: any) => {
|
||||
const updated = [...keyResults];
|
||||
updated[index] = { ...updated[index], [field]: value };
|
||||
setKeyResults(updated);
|
||||
@@ -127,6 +139,7 @@ export function OKRForm({ teamMembers, onSubmit, onCancel, initialData }: OKRFor
|
||||
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;
|
||||
@@ -150,15 +163,86 @@ export function OKRForm({ teamMembers, onSubmit, onCancel, initialData }: OKRFor
|
||||
return;
|
||||
}
|
||||
|
||||
await onSubmit({
|
||||
teamMemberId,
|
||||
objective,
|
||||
description: description || undefined,
|
||||
period: finalPeriod,
|
||||
startDate: startDateObj.toISOString() as any,
|
||||
endDate: endDateObj.toISOString() as any,
|
||||
keyResults: keyResults.map((kr, i) => ({ ...kr, order: i })),
|
||||
});
|
||||
const isEditMode = !!initialData?.teamMemberId;
|
||||
|
||||
if (isEditMode) {
|
||||
// In edit mode, separate existing Key Results from new ones
|
||||
const existingKeyResults = keyResults.filter((kr) => kr.id);
|
||||
const newKeyResults = keyResults.filter((kr) => !kr.id);
|
||||
const originalKeyResults = initialData?.keyResults || [];
|
||||
const originalIds = new Set(originalKeyResults.map((kr) => kr.id));
|
||||
const currentIds = new Set(existingKeyResults.map((kr) => 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) => {
|
||||
const original = originalKeyResults.find((okr) => 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((kr) => kr.id);
|
||||
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) => 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 any,
|
||||
endDate: endDateObj.toISOString() as any,
|
||||
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 any);
|
||||
} else {
|
||||
// In create mode, just send Key Results normally
|
||||
await onSubmit({
|
||||
teamMemberId,
|
||||
objective,
|
||||
description: description || undefined,
|
||||
period: finalPeriod,
|
||||
startDate: startDateObj.toISOString() as any,
|
||||
endDate: endDateObj.toISOString() as any,
|
||||
keyResults: keyResults.map((kr, i) => ({ ...kr, order: i })),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error submitting OKR:', error);
|
||||
} finally {
|
||||
@@ -273,7 +357,7 @@ export function OKRForm({ teamMembers, onSubmit, onCancel, initialData }: OKRFor
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{keyResults.map((kr, index) => (
|
||||
<div key={index} className="rounded-lg border border-border bg-card p-4">
|
||||
<div key={kr.id || `new-${index}`} className="rounded-lg border border-border bg-card p-4">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-foreground">Key Result {index + 1}</span>
|
||||
{keyResults.length > 1 && (
|
||||
@@ -331,7 +415,13 @@ export function OKRForm({ teamMembers, onSubmit, onCancel, initialData }: OKRFor
|
||||
disabled={submitting}
|
||||
className="bg-[var(--purple)] text-white hover:opacity-90 border-transparent"
|
||||
>
|
||||
{submitting ? 'Création...' : 'Créer l\'OKR'}
|
||||
{submitting
|
||||
? initialData?.teamMemberId
|
||||
? 'Modification...'
|
||||
: 'Création...'
|
||||
: initialData?.teamMemberId
|
||||
? 'Modifier l\'OKR'
|
||||
: 'Créer l\'OKR'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user