feat: migrate tag management to server actions

- Completed migration of tag management to server actions by creating `actions/tags.ts` for CRUD operations.
- Removed obsolete API routes for tags (`/api/tags`, `/api/tags/[id]`) and updated related components to utilize new server actions.
- Updated `TagForm` and `useTags` hook to handle tag creation, updating, and deletion through server actions, improving performance and simplifying client-side logic.
- Cleaned up `tags-client.ts` by removing unused types and methods, focusing on validation only.
- Marked related TODO items as complete in `TODO.md`.
This commit is contained in:
Julien Froidefond
2025-09-18 21:00:28 +02:00
parent 8b98c467d0
commit b12dd4f8dc
8 changed files with 253 additions and 317 deletions

View File

@@ -1,18 +1,18 @@
'use client';
import { useState, useEffect } from 'react';
import { useState, useEffect, useTransition } from 'react';
import { Tag } from '@/lib/types';
import { TagsClient } from '@/clients/tags-client';
import { Modal } from '@/components/ui/Modal';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { createTag, updateTag } from '@/actions/tags';
interface TagFormProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (data: { name: string; color: string }) => Promise<void>;
onSuccess?: () => Promise<void>; // Callback après succès pour refresh
tag?: Tag | null; // Si fourni, mode édition
loading?: boolean;
}
const PRESET_COLORS = [
@@ -30,7 +30,8 @@ const PRESET_COLORS = [
'#F43F5E', // Rose
];
export function TagForm({ isOpen, onClose, onSubmit, tag, loading = false }: TagFormProps) {
export function TagForm({ isOpen, onClose, onSuccess, tag }: TagFormProps) {
const [isPending, startTransition] = useTransition();
const [formData, setFormData] = useState({
name: '',
color: '#3B82F6',
@@ -66,12 +67,42 @@ export function TagForm({ isOpen, onClose, onSubmit, tag, loading = false }: Tag
return;
}
try {
await onSubmit(formData);
onClose();
} catch (error) {
setErrors([error instanceof Error ? error.message : 'Erreur inconnue']);
}
startTransition(async () => {
try {
let result;
if (tag) {
// Mode édition
result = await updateTag(tag.id, {
name: formData.name,
color: formData.color,
isPinned: formData.isPinned
});
} else {
// Mode création
result = await createTag(formData.name, formData.color);
}
if (result.success) {
onClose();
// Reset form
setFormData({
name: '',
color: TagsClient.generateRandomColor(),
isPinned: false
});
setErrors([]);
// Refresh la liste des tags
if (onSuccess) {
await onSuccess();
}
} else {
setErrors([result.error || 'Erreur inconnue']);
}
} catch (error) {
setErrors([error instanceof Error ? error.message : 'Erreur inconnue']);
}
});
};
const handleColorSelect = (color: string) => {
@@ -102,7 +133,7 @@ export function TagForm({ isOpen, onClose, onSubmit, tag, loading = false }: Tag
}}
placeholder="Nom du tag..."
maxLength={50}
disabled={loading}
disabled={isPending}
className="w-full"
/>
</div>
@@ -150,7 +181,7 @@ export function TagForm({ isOpen, onClose, onSubmit, tag, loading = false }: Tag
type="color"
value={formData.color}
onChange={handleCustomColorChange}
disabled={loading}
disabled={isPending}
className="w-12 h-8 rounded border border-slate-600 bg-slate-800 cursor-pointer disabled:cursor-not-allowed"
/>
<Input
@@ -163,7 +194,7 @@ export function TagForm({ isOpen, onClose, onSubmit, tag, loading = false }: Tag
}}
placeholder="#RRGGBB"
maxLength={7}
disabled={loading}
disabled={isPending}
className="w-24 text-xs font-mono"
/>
</div>
@@ -180,7 +211,7 @@ export function TagForm({ isOpen, onClose, onSubmit, tag, loading = false }: Tag
type="checkbox"
checked={formData.isPinned}
onChange={(e) => setFormData(prev => ({ ...prev, isPinned: e.target.checked }))}
disabled={loading}
disabled={isPending}
className="w-4 h-4 rounded border border-slate-600 bg-slate-800 text-purple-600 focus:ring-purple-500 focus:ring-2 disabled:cursor-not-allowed"
/>
<span className="text-sm text-slate-300">
@@ -210,16 +241,16 @@ export function TagForm({ isOpen, onClose, onSubmit, tag, loading = false }: Tag
type="button"
variant="secondary"
onClick={onClose}
disabled={loading}
disabled={isPending}
>
Annuler
</Button>
<Button
type="submit"
variant="primary"
disabled={loading || !formData.name.trim()}
disabled={isPending || !formData.name.trim()}
>
{loading ? 'Enregistrement...' : (tag ? 'Mettre à jour' : 'Créer')}
{isPending ? 'Enregistrement...' : (tag ? 'Mettre à jour' : 'Créer')}
</Button>
</div>
</form>