true message

This commit is contained in:
Manus Sandbox
2025-11-19 10:33:48 -05:00
parent 54cf4297bf
commit 2283e84b54
42 changed files with 38 additions and 8559 deletions

View File

@@ -1,43 +0,0 @@
import { Progress } from "@/components/ui/progress";
import { cn } from "@/lib/utils";
interface CapacityProgressBarProps {
current: number;
max: number;
className?: string;
}
export function CapacityProgressBar({ current, max, className }: CapacityProgressBarProps) {
const percentage = max > 0 ? (current / max) * 100 : 0;
// Définir la couleur en fonction du taux de remplissage
const getColorClass = () => {
if (percentage >= 90) return "bg-red-500";
if (percentage >= 70) return "bg-orange-500";
return "bg-green-500";
};
const getTextColorClass = () => {
if (percentage >= 90) return "text-red-700";
if (percentage >= 70) return "text-orange-700";
return "text-green-700";
};
return (
<div className={cn("space-y-1", className)}>
<div className="flex items-center justify-between text-sm gap-4">
<span className={cn("font-medium", getTextColorClass())}>
{current} / {max}
</span>
<span className={cn("text-xs", getTextColorClass())}>
{Math.round(percentage)}%
</span>
</div>
<Progress
value={percentage}
className="h-2"
indicatorClassName={getColorClass()}
/>
</div>
);
}

View File

@@ -21,7 +21,7 @@ import {
} from "@/components/ui/sidebar";
import { APP_LOGO, APP_TITLE, getLoginUrl } from "@/const";
import { useIsMobile } from "@/hooks/useMobile";
import { LayoutDashboard, LogOut, PanelLeft, Users, GraduationCap, Calendar, CalendarDays, BarChart3, UserCog, Mail, Settings, Bell } from "lucide-react";
import { LayoutDashboard, LogOut, PanelLeft, Users, GraduationCap, Calendar, BarChart3, UserCog, Mail, Settings } from "lucide-react";
import { CSSProperties, useEffect, useRef, useState } from "react";
import { useLocation } from "wouter";
import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton';
@@ -31,12 +31,10 @@ const menuItems = [
{ icon: LayoutDashboard, label: "Tableau de bord", path: "/admin" },
{ icon: GraduationCap, label: "Formations", path: "/admin/formations" },
{ icon: Calendar, label: "Séquences", path: "/admin/sequences" },
{ icon: CalendarDays, label: "Calendrier", path: "/admin/calendrier" },
{ icon: Users, label: "Apprenants", path: "/admin/apprenants" },
{ icon: UserCog, label: "Utilisateurs", path: "/admin/users" },
{ icon: Mail, label: "Templates d'emails", path: "/admin/email-templates" },
{ icon: Settings, label: "Configuration SMTP", path: "/admin/email-config" },
{ icon: Bell, label: "Rappels automatiques", path: "/admin/rappels" },
{ icon: BarChart3, label: "Rapport Public Cible", path: "/admin/rapport-public-cible" },
];

View File

@@ -1,135 +0,0 @@
import { useState } from "react";
import { Check, ChevronsUpDown, Plus } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { trpc } from "@/lib/trpc";
import { toast } from "sonner";
interface FormateurComboboxProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
}
export function FormateurCombobox({ value, onChange, placeholder = "Sélectionner un formateur..." }: FormateurComboboxProps) {
const [open, setOpen] = useState(false);
const [searchValue, setSearchValue] = useState("");
const { data: formateurs = [], refetch } = trpc.formateurs.list.useQuery();
const createMutation = trpc.formateurs.create.useMutation({
onSuccess: () => {
toast.success("Formateur ajouté");
refetch();
},
onError: (error) => {
toast.error(error.message || "Erreur lors de l'ajout du formateur");
},
});
const handleAddNew = () => {
if (!searchValue.trim()) {
toast.error("Veuillez entrer un nom de formateur");
return;
}
// Vérifier si le formateur existe déjà
const exists = formateurs.some(f => f.nom.toLowerCase() === searchValue.toLowerCase());
if (exists) {
toast.error("Ce formateur existe déjà");
return;
}
createMutation.mutate({ nom: searchValue.trim() });
onChange(searchValue.trim());
setSearchValue("");
setOpen(false);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
{value || placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput
placeholder="Rechercher ou ajouter un formateur..."
value={searchValue}
onValueChange={setSearchValue}
/>
<CommandList>
<CommandEmpty>
<div className="flex flex-col items-center gap-2 py-2">
<p className="text-sm text-muted-foreground">Aucun formateur trouvé</p>
{searchValue && (
<Button
size="sm"
variant="outline"
onClick={handleAddNew}
disabled={createMutation.isPending}
>
<Plus className="mr-2 h-4 w-4" />
Ajouter "{searchValue}"
</Button>
)}
</div>
</CommandEmpty>
<CommandGroup>
{formateurs.map((formateur) => (
<CommandItem
key={formateur.id}
value={formateur.nom}
onSelect={(currentValue) => {
onChange(currentValue === value ? "" : currentValue);
setOpen(false);
setSearchValue("");
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === formateur.nom ? "opacity-100" : "opacity-0"
)}
/>
{formateur.nom}
</CommandItem>
))}
</CommandGroup>
{searchValue && !formateurs.some(f => f.nom.toLowerCase() === searchValue.toLowerCase()) && (
<CommandGroup>
<CommandItem
onSelect={handleAddNew}
className="text-primary"
>
<Plus className="mr-2 h-4 w-4" />
Ajouter "{searchValue}"
</CommandItem>
</CommandGroup>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@@ -3,16 +3,11 @@ import * as ProgressPrimitive from "@radix-ui/react-progress";
import { cn } from "@/lib/utils";
interface ProgressProps extends React.ComponentProps<typeof ProgressPrimitive.Root> {
indicatorClassName?: string;
}
function Progress({
className,
value,
indicatorClassName,
...props
}: ProgressProps) {
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
return (
<ProgressPrimitive.Root
data-slot="progress"
@@ -24,7 +19,7 @@ function Progress({
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className={cn("bg-primary h-full w-full flex-1 transition-all", indicatorClassName)}
className="bg-primary h-full w-full flex-1 transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>