feat: Ajout de filtres avancés dans gestion productions

- Filtre par Déclarée FTS (Toutes/Oui/Non)
- Filtre par Type de prod (Tous/Spectacle vivant/Audiovisuelle/Phonographique/Administratif)
- Filtre par Année (années disponibles dans les données)
- Filtre par Mois (janvier à décembre)
- Compteur de filtres actifs dans le badge
- Réinitialisation de tous les filtres en un clic
- Layout responsive en grille 4 colonnes pour les filtres
This commit is contained in:
odentas 2025-10-22 19:17:38 +02:00
parent c87c11eb10
commit d81a12de6e

View file

@ -456,6 +456,12 @@ export default function GestionProductionsPage() {
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [sortColumn, setSortColumn] = useState<"name" | "reference" | "declaration_date" | null>("declaration_date");
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");
// Nouveaux filtres
const [filterDeclaredFts, setFilterDeclaredFts] = useState<"all" | "yes" | "no">("all");
const [filterProdType, setFilterProdType] = useState<ProductionType | "all">("all");
const [filterYear, setFilterYear] = useState<string>("all");
const [filterMonth, setFilterMonth] = useState<string>("all");
const { data: staffCheck, isLoading: isLoadingStaff } = useStaffCheck();
const { data: organizations = [], isLoading: isLoadingOrgs } = useOrganizations();
@ -476,10 +482,22 @@ export default function GestionProductionsPage() {
}
};
// Générer les années disponibles
const availableYears = useMemo(() => {
const years = new Set<string>();
productions.forEach((p) => {
if (p.declaration_date) {
years.add(new Date(p.declaration_date).getFullYear().toString());
}
});
return Array.from(years).sort((a, b) => b.localeCompare(a)); // Plus récent en premier
}, [productions]);
// Filtrage et tri
const filteredProductions = useMemo(() => {
let result = productions;
// Filtre par recherche
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
result = result.filter(
@ -489,6 +507,41 @@ export default function GestionProductionsPage() {
);
}
// Filtre par organisation
// (déjà géré par l'API dans useProductions)
// Filtre par Déclarée FTS
if (filterDeclaredFts !== "all") {
result = result.filter((p) => {
if (filterDeclaredFts === "yes") return p.declared_fts === true;
if (filterDeclaredFts === "no") return p.declared_fts === false || p.declared_fts === null;
return true;
});
}
// Filtre par Type de prod
if (filterProdType !== "all") {
result = result.filter((p) => p.prod_type === filterProdType);
}
// Filtre par année
if (filterYear !== "all") {
result = result.filter((p) => {
if (!p.declaration_date) return false;
const year = new Date(p.declaration_date).getFullYear().toString();
return year === filterYear;
});
}
// Filtre par mois
if (filterMonth !== "all") {
result = result.filter((p) => {
if (!p.declaration_date) return false;
const month = (new Date(p.declaration_date).getMonth() + 1).toString().padStart(2, "0");
return month === filterMonth;
});
}
// Tri
if (sortColumn) {
result = [...result].sort((a, b) => {
@ -513,7 +566,7 @@ export default function GestionProductionsPage() {
}
return result;
}, [productions, searchQuery, sortColumn, sortDirection]);
}, [productions, searchQuery, sortColumn, sortDirection, filterDeclaredFts, filterProdType, filterYear, filterMonth]);
// Vérification staff
if (isLoadingStaff) {
@ -533,7 +586,14 @@ export default function GestionProductionsPage() {
);
}
const hasActiveFilters = selectedOrgId || searchQuery;
const hasActiveFilters = selectedOrgId || searchQuery || filterDeclaredFts !== "all" || filterProdType !== "all" || filterYear !== "all" || filterMonth !== "all";
const activeFiltersCount = [
selectedOrgId,
filterDeclaredFts !== "all",
filterProdType !== "all",
filterYear !== "all",
filterMonth !== "all"
].filter(Boolean).length;
return (
<div className="space-y-5 p-6">
@ -587,9 +647,9 @@ export default function GestionProductionsPage() {
>
<Filter className="w-4 h-4" />
Filtres
{hasActiveFilters && (
{activeFiltersCount > 0 && (
<span className="px-2 py-0.5 bg-indigo-600 text-white text-xs rounded-full">
{selectedOrgId ? 1 : 0}
{activeFiltersCount}
</span>
)}
</button>
@ -600,6 +660,10 @@ export default function GestionProductionsPage() {
onClick={() => {
setSelectedOrgId("");
setSearchQuery("");
setFilterDeclaredFts("all");
setFilterProdType("all");
setFilterYear("all");
setFilterMonth("all");
}}
className="inline-flex items-center gap-1 text-sm text-slate-600 hover:text-slate-900 transition-colors"
>
@ -611,7 +675,8 @@ export default function GestionProductionsPage() {
{showFilters && (
<div className="mt-4 pt-4 border-t">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Organisation */}
<div className="space-y-2">
<label className="text-sm font-medium text-slate-700">
Organisation
@ -621,7 +686,7 @@ export default function GestionProductionsPage() {
onChange={(e) => setSelectedOrgId(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
>
<option value="">Toutes les organisations</option>
<option value="">Toutes</option>
{organizations.map((org) => (
<option key={org.id} value={org.id}>
{org.name}
@ -629,6 +694,85 @@ export default function GestionProductionsPage() {
))}
</select>
</div>
{/* Déclarée FTS */}
<div className="space-y-2">
<label className="text-sm font-medium text-slate-700">
Déclarée FTS
</label>
<select
value={filterDeclaredFts}
onChange={(e) => setFilterDeclaredFts(e.target.value as "all" | "yes" | "no")}
className="w-full px-3 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
>
<option value="all">Toutes</option>
<option value="yes">Oui</option>
<option value="no">Non</option>
</select>
</div>
{/* Type de prod */}
<div className="space-y-2">
<label className="text-sm font-medium text-slate-700">
Type de prod
</label>
<select
value={filterProdType}
onChange={(e) => setFilterProdType(e.target.value as ProductionType | "all")}
className="w-full px-3 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
>
<option value="all">Tous</option>
<option value="Spectacle vivant">Spectacle vivant</option>
<option value="Production audiovisuelle">Production audiovisuelle</option>
<option value="Production phonographique">Production phonographique</option>
<option value="Administratif">Administratif</option>
</select>
</div>
{/* Année */}
<div className="space-y-2">
<label className="text-sm font-medium text-slate-700">
Année
</label>
<select
value={filterYear}
onChange={(e) => setFilterYear(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
>
<option value="all">Toutes</option>
{availableYears.map((year) => (
<option key={year} value={year}>
{year}
</option>
))}
</select>
</div>
{/* Mois */}
<div className="space-y-2">
<label className="text-sm font-medium text-slate-700">
Mois
</label>
<select
value={filterMonth}
onChange={(e) => setFilterMonth(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
>
<option value="all">Tous</option>
<option value="01">Janvier</option>
<option value="02">Février</option>
<option value="03">Mars</option>
<option value="04">Avril</option>
<option value="05">Mai</option>
<option value="06">Juin</option>
<option value="07">Juillet</option>
<option value="08">Août</option>
<option value="09">Septembre</option>
<option value="10">Octobre</option>
<option value="11">Novembre</option>
<option value="12">Décembre</option>
</select>
</div>
</div>
</div>
)}