mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-23 15:08:12 +02:00
feat: restrict gamme/export/exports-page to admin; fix create user button style
- Non-admin users see gamme as read-only badge (no select) - Export button hidden for non-admin in grid - Save drafts button hidden for non-admin - Exports page hidden in sidebar for non-admin - Create user button: explicit blue style (was unreadable with apple-btn-primary) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
f52532343f
commit
ead53db496
4 files changed
+29
-7
No files matched your search
@@ -14,7 +14,7 @@ const NAV_ITEMS = [
|
|||||||
{ icon: TrendingUp, label: "Analytics", href: "/analytics" },
|
{ icon: TrendingUp, label: "Analytics", href: "/analytics" },
|
||||||
{ icon: Trophy, label: "Hit Parade", href: "/hit-parade" },
|
{ icon: Trophy, label: "Hit Parade", href: "/hit-parade" },
|
||||||
{ icon: Camera, label: "Snapshots", href: "/snapshots" },
|
{ icon: Camera, label: "Snapshots", href: "/snapshots" },
|
||||||
{ icon: FileDown, label: "Exports", href: "/exports" },
|
{ icon: FileDown, label: "Exports", href: "/exports", adminOnly: true },
|
||||||
{ icon: Settings, label: "Paramètres", href: "/settings", adminOnly: true },
|
{ icon: Settings, label: "Paramètres", href: "/settings", adminOnly: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export function UserManagement() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isCreating}
|
disabled={isCreating}
|
||||||
className="apple-btn-primary w-full justify-center"
|
className="flex items-center gap-2 justify-center w-full px-4 py-2.5 rounded-xl bg-blue-600 hover:bg-blue-500 disabled:opacity-50 text-white text-sm font-semibold transition-colors"
|
||||||
>
|
>
|
||||||
{isCreating ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
|
{isCreating ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
|
||||||
Créer le compte
|
Créer le compte
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useGridStore } from "@/features/grid/store/use-grid-store";
|
|||||||
import { useSaveDrafts } from "@/features/grid/hooks/use-save-drafts";
|
import { useSaveDrafts } from "@/features/grid/hooks/use-save-drafts";
|
||||||
import type { ProductRow } from "@/types/grid";
|
import type { ProductRow } from "@/types/grid";
|
||||||
import { CheckCircle, AlertCircle, Loader2 } from "lucide-react";
|
import { CheckCircle, AlertCircle, Loader2 } from "lucide-react";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { computeProductScores } from "@/lib/score-engine";
|
import { computeProductScores } from "@/lib/score-engine";
|
||||||
@@ -24,6 +25,8 @@ interface GridClientProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function GridClient({ initialRows, codeFournisseur, nomFournisseur, fournisseurs, magasins, magasin }: GridClientProps) {
|
export function GridClient({ initialRows, codeFournisseur, nomFournisseur, fournisseurs, magasins, magasin }: GridClientProps) {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const isAdmin = (session?.user as any)?.role === "admin";
|
||||||
const setRows = useGridStore((s) => s.setRows);
|
const setRows = useGridStore((s) => s.setRows);
|
||||||
const setActiveGridQuery = useGridStore((s) => s.setActiveGridQuery);
|
const setActiveGridQuery = useGridStore((s) => s.setActiveGridQuery);
|
||||||
const setFilter = useGridStore((s) => s.setFilter);
|
const setFilter = useGridStore((s) => s.setFilter);
|
||||||
@@ -98,8 +101,8 @@ export function GridClient({ initialRows, codeFournisseur, nomFournisseur, fourn
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div id="grid-toolbar-actions"></div>
|
<div id="grid-toolbar-actions"></div>
|
||||||
<ExportDropdown nomFournisseur={nomFournisseur} />
|
{isAdmin && <ExportDropdown nomFournisseur={nomFournisseur} />}
|
||||||
{hasDrafts && (
|
{isAdmin && hasDrafts && (
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
@@ -141,6 +144,7 @@ export function GridClient({ initialRows, codeFournisseur, nomFournisseur, fourn
|
|||||||
<div className="flex-1 min-h-0 min-w-0">
|
<div className="flex-1 min-h-0 min-w-0">
|
||||||
<HeatmapGrid
|
<HeatmapGrid
|
||||||
onSelectionChange={setSelectedCodeins}
|
onSelectionChange={setSelectedCodeins}
|
||||||
|
isAdmin={isAdmin}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ function getStoreConfig(name: string) {
|
|||||||
|
|
||||||
interface HeatmapGridProps {
|
interface HeatmapGridProps {
|
||||||
onSelectionChange?: (codeins: string[]) => void;
|
onSelectionChange?: (codeins: string[]) => void;
|
||||||
|
isAdmin?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -90,7 +91,7 @@ interface HeatmapGridProps {
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
// 1. Composant isolé pour la Cellule Gamme (évite le re-render des colonnes)
|
// 1. Composant isolé pour la Cellule Gamme (évite le re-render des colonnes)
|
||||||
const GammeCell = React.memo(({ row }: { row: ProductRow }) => {
|
const GammeCell = React.memo(({ row, isAdmin }: { row: ProductRow; isAdmin?: boolean }) => {
|
||||||
// Abonnement ultra-ciblé : la cellule ne re-render que si SA valeur change
|
// Abonnement ultra-ciblé : la cellule ne re-render que si SA valeur change
|
||||||
const codein = row.codein;
|
const codein = row.codein;
|
||||||
const isDraft = useGridStore((s) => s.draftChanges[codein] !== undefined);
|
const isDraft = useGridStore((s) => s.draftChanges[codein] !== undefined);
|
||||||
@@ -98,6 +99,23 @@ const GammeCell = React.memo(({ row }: { row: ProductRow }) => {
|
|||||||
const setDraftGamme = useGridStore((s) => s.setDraftGamme);
|
const setDraftGamme = useGridStore((s) => s.setDraftGamme);
|
||||||
|
|
||||||
const isModified = row.codeGamme !== row.codeGammeInit && row.codeGammeInit !== null;
|
const isModified = row.codeGamme !== row.codeGammeInit && row.codeGammeInit !== null;
|
||||||
|
const displayValue = (!effectiveGamme || effectiveGamme.trim() === "") ? "Aucune" : effectiveGamme;
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
return (
|
||||||
|
<div className={[
|
||||||
|
"w-full text-xs font-bold rounded-lg py-1.5 px-2 text-center",
|
||||||
|
displayValue === "A" ? "bg-emerald-500/10 text-emerald-700" :
|
||||||
|
displayValue === "B" ? "bg-blue-500/10 text-blue-700" :
|
||||||
|
displayValue === "C" ? "bg-amber-500/10 text-amber-700" :
|
||||||
|
displayValue === "Y" ? "bg-violet-500/10 text-violet-700" :
|
||||||
|
displayValue === "Z" ? "bg-rose-500/10 text-rose-700" :
|
||||||
|
"bg-slate-500/10 text-slate-500"
|
||||||
|
].join(" ")}>
|
||||||
|
{displayValue === "Aucune" ? "—" : displayValue}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative group/gamme">
|
<div className="relative group/gamme">
|
||||||
@@ -249,7 +267,7 @@ function CellDetailModal({ d, activeMagasin, onClose }: { d: CellDetailData; act
|
|||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
export function HeatmapGrid({ onSelectionChange, isAdmin }: HeatmapGridProps) {
|
||||||
// L'abonnement doit être minimal ici ! PAS de draftChanges ni de setDraftGamme.
|
// L'abonnement doit être minimal ici ! PAS de draftChanges ni de setDraftGamme.
|
||||||
const { rows, filters, displayDensity, draftChanges, activeMagasin } = useGridStore();
|
const { rows, filters, displayDensity, draftChanges, activeMagasin } = useGridStore();
|
||||||
|
|
||||||
@@ -626,7 +644,7 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
id: "gamme",
|
id: "gamme",
|
||||||
header: () => <div className="text-center w-full">Gamme</div>,
|
header: () => <div className="text-center w-full">Gamme</div>,
|
||||||
size: 110,
|
size: 110,
|
||||||
cell: ({ row }) => <GammeCell row={row.original} />,
|
cell: ({ row }) => <GammeCell row={row.original} isAdmin={isAdmin} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "ai",
|
id: "ai",
|
||||||
|
|||||||
Reference in new issue
Block a user