diff --git a/FEATURE_GROUP_CALENDAR_COLOR.md b/FEATURE_GROUP_CALENDAR_COLOR.md new file mode 100644 index 0000000..9984edb --- /dev/null +++ b/FEATURE_GROUP_CALENDAR_COLOR.md @@ -0,0 +1,265 @@ +# 🎹 FonctionnalitĂ© : Modification de la couleur des calendriers de groupes + +## 📋 Vue d'ensemble + +Cette fonctionnalitĂ© permet aux membres d'un groupe de personnaliser la couleur de la pastille du calendrier associĂ© Ă  leur groupe. Chaque groupe créé possĂšde un calendrier automatique nommĂ© `"Groupe: {nom}"`, et cette couleur est visible dans tous les affichages de calendrier (agenda, vision, etc.). + +## ✹ FonctionnalitĂ©s implĂ©mentĂ©es + +### 1. **API Backend** (`/app/api/groups/[groupId]/calendar/route.ts`) + +#### Nouveaux endpoints : + +**GET `/api/groups/[groupId]/calendar`** +- RĂ©cupĂšre le calendrier associĂ© Ă  un groupe +- Retourne l'objet calendrier avec sa couleur actuelle + +**PATCH `/api/groups/[groupId]/calendar`** +- Modifie uniquement la couleur du calendrier d'un groupe +- Body : `{ "color": "#FF5733" }` +- Validation du format hexadĂ©cimal +- VĂ©rification que l'utilisateur est membre du groupe +- Retourne le calendrier mis Ă  jour + +#### SĂ©curitĂ© : +- ✅ Authentification requise +- ✅ VĂ©rification de l'appartenance au groupe via Keycloak +- ✅ Validation du format de couleur (hex : `#RRGGBB` ou `#RGB`) + +--- + +### 2. **Interface utilisateur - Page Groupes** (`/components/groups/groups-table.tsx`) + +#### Modifications : + +1. **Affichage de la pastille de couleur** + - Chaque groupe affiche maintenant une pastille colorĂ©e Ă  cĂŽtĂ© de son nom + - Couleur par dĂ©faut : `#4f46e5` (indigo) + +2. **Nouveau bouton "Couleur du calendrier"** + - Accessible via le menu actions (icĂŽne ⋯) + - IcĂŽne palette (🎹) + +3. **Dialog de sĂ©lection de couleur** + - Palette de 16 couleurs prĂ©dĂ©finies + - Input manuel pour code hexadĂ©cimal + - Aperçu en temps rĂ©el de la couleur sĂ©lectionnĂ©e + - Validation avant sauvegarde + +#### Palette de couleurs disponibles : +```typescript +const colorPalette = [ + "#4f46e5", // Indigo + "#0891b2", // Cyan + "#0e7490", // Teal + "#16a34a", // Green + "#65a30d", // Lime + "#ca8a04", // Amber + "#d97706", // Orange + "#dc2626", // Red + "#e11d48", // Rose + "#9333ea", // Purple + "#7c3aed", // Violet + "#2563eb", // Blue + "#0284c7", // Sky + "#059669", // Emerald + "#84cc16", // Lime + "#eab308", // Yellow +]; +``` + +--- + +### 3. **Interface utilisateur - Page Missions/Équipe** (`/app/missions/equipe/page.tsx`) + +#### Modifications identiques Ă  GroupsTable : + +1. **Affichage de la couleur** + - IcĂŽne du groupe (đŸ‘„) affichĂ©e sur fond de la couleur du calendrier + - Texte en blanc pour meilleure lisibilitĂ© + +2. **Bouton direct dans la table** + - IcĂŽne palette (🎹) directement dans les actions + - Tooltip "Couleur du calendrier" + +3. **Dialog de sĂ©lection** + - MĂȘme interface que dans GroupsTable + - Sauvegarde avec loader pendant l'opĂ©ration + +--- + +## 🔄 Flux de donnĂ©es + +### Chargement initial : +``` +1. Page charge les groupes via GET /api/groups +2. Pour chaque groupe : + └─> GET /api/groups/{groupId}/calendar + └─> RĂ©cupĂšre la couleur du calendrier + └─> Stocke dans state local : group.calendarColor +``` + +### Modification de couleur : +``` +1. Utilisateur clique sur icĂŽne palette +2. Dialog s'ouvre avec couleur actuelle +3. Utilisateur sĂ©lectionne nouvelle couleur +4. Clic "Enregistrer" + └─> PATCH /api/groups/{groupId}/calendar { color: "#newcolor" } + └─> VĂ©rification membre du groupe (Keycloak) + └─> Mise Ă  jour en base de donnĂ©es (Prisma) + └─> Mise Ă  jour du state local + └─> Toast de confirmation +5. Couleur visible immĂ©diatement partout +``` + +--- + +## 🎯 Points d'affichage de la couleur + +La couleur du calendrier de groupe est maintenant visible dans : + +1. **`/groups`** - Page groupes + - Table des groupes (pastille Ă  cĂŽtĂ© du nom) + +2. **`/missions/equipe`** - Page gestion Ă©quipe + - Table des groupes (icĂŽne colorĂ©e) + +3. **`/agenda`** - Page agenda + - Liste des calendriers (pastille Ă  gauche) + - ÉvĂ©nements dans le calendrier (barre colorĂ©e) + +4. **`/vision`** - Page visioconfĂ©rences + - Calendrier des rĂ©unions + - ÉvĂ©nements du jour + +5. **Widgets calendrier** - Tous les composants calendrier + - `calendar-widget.tsx` + - `calendar-client.tsx` + - `calendar.tsx` + +--- + +## đŸ§Ș Tests suggĂ©rĂ©s + +### Test 1 : Modification de couleur +1. Aller sur `/groups` ou `/missions/equipe` +2. Cliquer sur ⋯ → "Couleur du calendrier" +3. Choisir une nouvelle couleur +4. Cliquer "Enregistrer" +5. ✅ VĂ©rifier : Pastille mise Ă  jour immĂ©diatement + +### Test 2 : Validation format +1. Ouvrir dialog couleur +2. Entrer manuellement un code invalide : `#GGGGGG` +3. Cliquer "Enregistrer" +4. ✅ VĂ©rifier : Message d'erreur affichĂ© + +### Test 3 : Permissions +1. Se connecter avec utilisateur NON membre du groupe +2. Essayer de modifier la couleur +3. ✅ VĂ©rifier : Erreur 403 "Vous devez ĂȘtre membre du groupe" + +### Test 4 : Persistance +1. Modifier couleur d'un groupe +2. Actualiser la page +3. ✅ VĂ©rifier : Couleur conservĂ©e +4. Aller sur `/agenda` +5. ✅ VĂ©rifier : Couleur visible dans le calendrier + +--- + +## 📁 Fichiers modifiĂ©s + +### Nouveaux fichiers : +- `app/api/groups/[groupId]/calendar/route.ts` (nouveau endpoint API) + +### Fichiers modifiĂ©s : +- `components/groups/groups-table.tsx` + - Import `Palette` de lucide-react + - Ajout interface `calendarColor?` Ă  Group + - Ajout Ă©tats `colorPickerDialog`, `selectedGroupForColor`, `selectedColor` + - Fonction `fetchGroups()` modifiĂ©e pour charger couleurs + - Fonctions `handleOpenColorPicker()` et `handleSaveColor()` + - Palette de couleurs `colorPalette` + - Dialog de sĂ©lection de couleur + - Affichage pastille dans table + +- `app/missions/equipe/page.tsx` + - Import `Palette` de lucide-react + - Ajout interface `calendarColor?` Ă  Group + - Ajout Ă©tats color picker + - Fonction `fetchData()` modifiĂ©e pour charger couleurs + - Fonctions `handleOpenColorPicker()` et `handleSaveColor()` + - Palette de couleurs + - Bouton palette dans actions + - Dialog de sĂ©lection de couleur + - Affichage icĂŽne colorĂ©e + +--- + +## 🔼 AmĂ©liorations futures possibles + +1. **Color picker avancĂ©** + - Utiliser un vrai color picker (ex: `react-color`) + - Support HSL, RGB en plus de hex + +2. **ThĂšmes prĂ©dĂ©finis** + - Palettes thĂ©matiques (pastel, vif, entreprise, etc.) + - Sauvegarde de palettes personnalisĂ©es + +3. **Permissions granulaires** + - Seuls les admins du groupe peuvent changer la couleur + - RĂŽle "gestionnaire de groupe" dans Keycloak + +4. **Historique des couleurs** + - Log des changements de couleur + - PossibilitĂ© de revenir en arriĂšre + +5. **Synchronisation** + - Webhook pour notifier les autres utilisateurs en temps rĂ©el + - WebSocket pour mise Ă  jour instantanĂ©e sans refresh + +--- + +## 📊 Impact sur les performances + +- **Chargement initial** : +1 requĂȘte API par groupe (GET calendar) +- **Modification** : 1 requĂȘte PATCH, pas de refresh complet +- **Cache** : Les calendriers sont dĂ©jĂ  en cache Redis (GET /api/calendars) +- **Optimisation** : PossibilitĂ© de batch les requĂȘtes GET calendar en une seule + +--- + +## 🐛 ProblĂšmes connus + +Aucun problĂšme connu pour le moment. + +--- + +## ✅ Checklist de dĂ©ploiement + +- [x] API créée et testĂ©e +- [x] Interface utilisateur implĂ©mentĂ©e +- [x] Validation des permissions +- [x] Gestion des erreurs +- [x] Pas d'erreurs de linting +- [ ] Tests unitaires (Ă  ajouter) +- [ ] Tests E2E (Ă  ajouter) +- [ ] Documentation utilisateur (Ă  ajouter) + +--- + +## 📞 Support + +Pour toute question ou problĂšme : +1. VĂ©rifier que l'utilisateur est bien membre du groupe +2. VĂ©rifier le format de la couleur (hex valide) +3. VĂ©rifier les logs serveur pour les erreurs Keycloak +4. VĂ©rifier que le calendrier du groupe existe en base + +--- + +**Date de crĂ©ation** : 20 janvier 2026 +**Version** : 1.0 +**Auteur** : Senior Developer Assistant diff --git a/app/api/groups/[groupId]/calendar/route.ts b/app/api/groups/[groupId]/calendar/route.ts new file mode 100644 index 0000000..cf32034 --- /dev/null +++ b/app/api/groups/[groupId]/calendar/route.ts @@ -0,0 +1,246 @@ +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/options"; +import { NextResponse } from "next/server"; +import { prisma } from "@/lib/prisma"; +import { logger } from "@/lib/logger"; + +async function getAdminToken() { + try { + const tokenResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'client_credentials', + client_id: process.env.KEYCLOAK_CLIENT_ID!, + client_secret: process.env.KEYCLOAK_CLIENT_SECRET!, + }), + } + ); + + const data = await tokenResponse.json(); + if (!tokenResponse.ok || !data.access_token) { + logger.error('Token Error', { error: data }); + return null; + } + + return data.access_token; + } catch (error) { + logger.error('Token Error', { + error: error instanceof Error ? error.message : String(error) + }); + return null; + } +} + +/** + * PATCH /api/groups/[groupId]/calendar + * Updates the color of a group's calendar + */ +export async function PATCH( + req: Request, + props: { params: Promise<{ groupId: string }> } +) { + const params = await props.params; + + try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json({ error: "Non autorisĂ©" }, { status: 401 }); + } + + const { color } = await req.json(); + + if (!color) { + return NextResponse.json( + { error: "La couleur est requise" }, + { status: 400 } + ); + } + + // Validate color format (hex color) + const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; + if (!hexColorRegex.test(color)) { + return NextResponse.json( + { error: "Format de couleur invalide. Utilisez un code hexadĂ©cimal (ex: #FF5733)" }, + { status: 400 } + ); + } + + const token = await getAdminToken(); + if (!token) { + return NextResponse.json( + { error: "Erreur d'authentification" }, + { status: 500 } + ); + } + + // Get group details from Keycloak + const groupResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/groups/${params.groupId}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + if (!groupResponse.ok) { + logger.error('Group not found', { groupId: params.groupId }); + return NextResponse.json( + { error: "Groupe non trouvĂ©" }, + { status: 404 } + ); + } + + const group = await groupResponse.json(); + + // Check if user is a member of the group + const membersResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/groups/${params.groupId}/members`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + if (membersResponse.ok) { + const members = await membersResponse.json(); + const isMember = members.some((member: any) => member.id === session.user.id); + + if (!isMember) { + logger.warn('User not a member of group', { + userId: session.user.id, + groupId: params.groupId + }); + return NextResponse.json( + { error: "Vous devez ĂȘtre membre du groupe pour modifier sa couleur" }, + { status: 403 } + ); + } + } + + // Find the group's calendar + const calendar = await prisma.calendar.findFirst({ + where: { + name: `Groupe: ${group.name}`, + }, + }); + + if (!calendar) { + logger.error('Calendar not found for group', { + groupId: params.groupId, + groupName: group.name + }); + return NextResponse.json( + { error: "Calendrier du groupe non trouvĂ©" }, + { status: 404 } + ); + } + + // Update calendar color + const updatedCalendar = await prisma.calendar.update({ + where: { + id: calendar.id, + }, + data: { + color: color, + }, + }); + + logger.debug('Group calendar color updated', { + groupId: params.groupId, + calendarId: calendar.id, + newColor: color, + userId: session.user.id + }); + + return NextResponse.json({ + success: true, + calendar: updatedCalendar, + }); + } catch (error) { + logger.error('Error updating group calendar color', { + groupId: params.groupId, + error: error instanceof Error ? error.message : String(error) + }); + return NextResponse.json( + { error: "Erreur lors de la mise Ă  jour de la couleur" }, + { status: 500 } + ); + } +} + +/** + * GET /api/groups/[groupId]/calendar + * Gets the calendar associated with a group + */ +export async function GET( + req: Request, + props: { params: Promise<{ groupId: string }> } +) { + const params = await props.params; + + try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json({ error: "Non autorisĂ©" }, { status: 401 }); + } + + const token = await getAdminToken(); + if (!token) { + return NextResponse.json( + { error: "Erreur d'authentification" }, + { status: 500 } + ); + } + + // Get group details from Keycloak + const groupResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/groups/${params.groupId}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + if (!groupResponse.ok) { + return NextResponse.json( + { error: "Groupe non trouvĂ©" }, + { status: 404 } + ); + } + + const group = await groupResponse.json(); + + // Find the group's calendar + const calendar = await prisma.calendar.findFirst({ + where: { + name: `Groupe: ${group.name}`, + }, + }); + + if (!calendar) { + return NextResponse.json( + { error: "Calendrier du groupe non trouvĂ©" }, + { status: 404 } + ); + } + + return NextResponse.json(calendar); + } catch (error) { + logger.error('Error getting group calendar', { + groupId: params.groupId, + error: error instanceof Error ? error.message : String(error) + }); + return NextResponse.json( + { error: "Erreur lors de la rĂ©cupĂ©ration du calendrier" }, + { status: 500 } + ); + } +} diff --git a/app/missions/equipe/page.tsx b/app/missions/equipe/page.tsx index afed489..ff8513b 100644 --- a/app/missions/equipe/page.tsx +++ b/app/missions/equipe/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect } from "react"; -import { Search, Plus, MoreHorizontal, Trash2, Edit2, Users, UserPlus, X, Check, Loader2, FolderKanban } from "lucide-react"; +import { Search, Plus, MoreHorizontal, Trash2, Edit2, Users, UserPlus, X, Check, Loader2, FolderKanban, Palette } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { useToast } from "@/components/ui/use-toast"; @@ -30,6 +30,7 @@ interface Group { name: string; path: string; membersCount: number; + calendarColor?: string; } type ActiveTab = "users" | "groups"; @@ -59,6 +60,11 @@ export default function EquipePage() { const [userGroups, setUserGroups] = useState([]); const [availableGroups, setAvailableGroups] = useState([]); + // Color picker state + const [colorPickerDialog, setColorPickerDialog] = useState(false); + const [selectedGroupForColor, setSelectedGroupForColor] = useState(null); + const [selectedColor, setSelectedColor] = useState("#4f46e5"); + // New user dialog state const [newUserDialogOpen, setNewUserDialogOpen] = useState(false); const [newUserData, setNewUserData] = useState({ @@ -95,7 +101,24 @@ export default function EquipePage() { if (groupsRes.ok) { const groupsData = await groupsRes.json(); - setGroups(Array.isArray(groupsData) ? groupsData : []); + + // Fetch calendar colors for each group + const groupsWithColors = await Promise.all( + (Array.isArray(groupsData) ? groupsData : []).map(async (group: Group) => { + try { + const calendarResponse = await fetch(`/api/groups/${group.id}/calendar`); + if (calendarResponse.ok) { + const calendar = await calendarResponse.json(); + return { ...group, calendarColor: calendar.color || "#4f46e5" }; + } + } catch (error) { + console.warn(`Could not fetch calendar for group ${group.id}`); + } + return { ...group, calendarColor: "#4f46e5" }; + }) + ); + + setGroups(groupsWithColors); } if (rolesRes.ok) { @@ -477,6 +500,61 @@ export default function EquipePage() { } }; + const handleOpenColorPicker = (group: Group) => { + setSelectedGroupForColor(group); + setSelectedColor(group.calendarColor || "#4f46e5"); + setColorPickerDialog(true); + }; + + const handleSaveColor = async () => { + if (!selectedGroupForColor) return; + + setActionLoading(selectedGroupForColor.id); + try { + const response = await fetch(`/api/groups/${selectedGroupForColor.id}/calendar`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ color: selectedColor }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || "Erreur lors de la mise Ă  jour de la couleur"); + } + + // Update local state + setGroups(prev => prev.map(g => + g.id === selectedGroupForColor.id + ? { ...g, calendarColor: selectedColor } + : g + )); + + setColorPickerDialog(false); + setSelectedGroupForColor(null); + setSelectedColor("#4f46e5"); + + toast({ + title: "SuccĂšs", + description: "La couleur du calendrier a Ă©tĂ© mise Ă  jour", + }); + } catch (error) { + toast({ + title: "Erreur", + description: error instanceof Error ? error.message : "Une erreur est survenue", + variant: "destructive", + }); + } finally { + setActionLoading(null); + } + }; + + // Predefined color palette + const colorPalette = [ + "#4f46e5", "#0891b2", "#0e7490", "#16a34a", "#65a30d", "#ca8a04", + "#d97706", "#dc2626", "#e11d48", "#9333ea", "#7c3aed", "#2563eb", + "#0284c7", "#059669", "#84cc16", "#eab308" + ]; + const createGroup = async () => { if (!newGroupName.trim()) { toast({ title: "Erreur", description: "Le nom du groupe est requis", variant: "destructive" }); @@ -932,8 +1010,11 @@ export default function EquipePage() {
-
- +
+
{group.name}
@@ -952,15 +1033,27 @@ export default function EquipePage() { onClick={() => handleEditGroup(group)} disabled={actionLoading === group.id} className="h-8 w-8 p-0" + title="Modifier" > + @@ -1272,6 +1366,81 @@ export default function EquipePage() {
)} + + {/* Color Picker Dialog */} + {colorPickerDialog && ( + + + + + Changer la couleur du calendrier - {selectedGroupForColor?.name} + + +
+
+ +
+
+
+ setSelectedColor(e.target.value)} + className="bg-white text-gray-900 border-gray-300 font-mono" + placeholder="#4f46e5" + /> +
+
+
+ +
+ +
+ {colorPalette.map((color) => ( +
+
+ +
+ + +
+
+ +
+ )} ); } diff --git a/components/groups/groups-table.tsx b/components/groups/groups-table.tsx index 1c6960d..8cfe8d7 100644 --- a/components/groups/groups-table.tsx +++ b/components/groups/groups-table.tsx @@ -11,7 +11,7 @@ import { } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Plus, MoreHorizontal, Trash, Edit, Users } from "lucide-react"; +import { Plus, MoreHorizontal, Trash, Edit, Users, Palette } from "lucide-react"; import { Dialog, DialogContent, @@ -35,6 +35,7 @@ interface Group { name: string; path: string; membersCount: number; + calendarColor?: string; } interface User { @@ -100,6 +101,9 @@ export function GroupsTable({ userRole = [] }: GroupsTableProps) { const [manageMembersDialog, setManageMembersDialog] = useState(false); const [groupMembers, setGroupMembers] = useState([]); const [availableUsers, setAvailableUsers] = useState([]); + const [colorPickerDialog, setColorPickerDialog] = useState(false); + const [selectedGroupForColor, setSelectedGroupForColor] = useState(null); + const [selectedColor, setSelectedColor] = useState("#4f46e5"); useEffect(() => { fetchGroups(); @@ -118,18 +122,34 @@ export function GroupsTable({ userRole = [] }: GroupsTableProps) { const groupsWithCounts = await Promise.all( (Array.isArray(data) ? data : []).map(async (group) => { try { + // Fetch members count const membersResponse = await fetch(`/api/groups/${group.id}/members`); + let membersCount = 0; if (membersResponse.ok) { const members = await membersResponse.json(); - return { - ...group, - membersCount: Array.isArray(members) ? members.length : 0 - }; + membersCount = Array.isArray(members) ? members.length : 0; } - return group; + + // Fetch calendar color + let calendarColor = "#4f46e5"; // Default color + try { + const calendarResponse = await fetch(`/api/groups/${group.id}/calendar`); + if (calendarResponse.ok) { + const calendar = await calendarResponse.json(); + calendarColor = calendar.color || calendarColor; + } + } catch (calendarError) { + console.warn(`Could not fetch calendar for group ${group.id}:`, calendarError); + } + + return { + ...group, + membersCount, + calendarColor, + }; } catch (error) { - console.error(`Error fetching members for group ${group.id}:`, error); - return group; + console.error(`Error fetching data for group ${group.id}:`, error); + return { ...group, membersCount: 0, calendarColor: "#4f46e5" }; } }) ); @@ -391,6 +411,75 @@ export function GroupsTable({ userRole = [] }: GroupsTableProps) { setAvailableUsers([]); }, []); + const resetColorPickerForm = useCallback(() => { + setSelectedGroupForColor(null); + setSelectedColor("#4f46e5"); + }, []); + + const handleOpenColorPicker = (group: Group) => { + setSelectedGroupForColor(group); + setSelectedColor(group.calendarColor || "#4f46e5"); + setColorPickerDialog(true); + }; + + const handleSaveColor = async () => { + if (!selectedGroupForColor) return; + + try { + const response = await fetch(`/api/groups/${selectedGroupForColor.id}/calendar`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ color: selectedColor }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || "Erreur lors de la mise à jour de la couleur"); + } + + // Update local state + setGroups(prev => prev.map(g => + g.id === selectedGroupForColor.id + ? { ...g, calendarColor: selectedColor } + : g + )); + + setColorPickerDialog(false); + toast({ + title: "SuccÚs", + description: "La couleur du calendrier a été mise à jour", + }); + } catch (error) { + toast({ + title: "Erreur", + description: error instanceof Error ? error.message : "Une erreur est survenue", + variant: "destructive", + }); + } + }; + + // Predefined color palette + const colorPalette = [ + "#4f46e5", // Indigo + "#0891b2", // Cyan + "#0e7490", // Teal + "#16a34a", // Green + "#65a30d", // Lime + "#ca8a04", // Amber + "#d97706", // Orange + "#dc2626", // Red + "#e11d48", // Rose + "#9333ea", // Purple + "#7c3aed", // Violet + "#2563eb", // Blue + "#0284c7", // Sky + "#059669", // Emerald + "#84cc16", // Lime + "#eab308", // Yellow + ]; + if (loading) return
Loading...
; return ( @@ -455,7 +544,15 @@ export function GroupsTable({ userRole = [] }: GroupsTableProps) { ) .map((group) => ( - {group.name} + +
+
+ {group.name} +
+ {group.path} {group.membersCount} @@ -478,6 +575,13 @@ export function GroupsTable({ userRole = [] }: GroupsTableProps) { Modifier + handleOpenColorPicker(group)} + className="text-gray-900 hover:bg-gray-100" + > + + Couleur du calendrier + handleManageMembers(group.id)} className="text-gray-900 hover:bg-gray-100" @@ -530,6 +634,76 @@ export function GroupsTable({ userRole = [] }: GroupsTableProps) {
+ + + + Changer la couleur du calendrier - {selectedGroupForColor?.name} + + +
+
+ +
+
+
+ setSelectedColor(e.target.value)} + className="bg-white text-gray-900 border-gray-300 font-mono" + placeholder="#4f46e5" + /> +
+
+
+ +
+ +
+ {colorPalette.map((color) => ( +
+
+ +
+ + +
+
+ +