mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
feat: implement new DropdownMenu UI component and create tsc-errors.txt to log existing TypeScript compilation issues.
This commit is contained in:
1 parent
02a382e815
commit
baebf50d7a
4 files changed
+258
-83
No files matched your search
@@ -0,0 +1,257 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function DropdownMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Trigger
|
||||
data-slot="dropdown-menu-trigger"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Label
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
data-slot="dropdown-menu-separator"
|
||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
}
|
||||
@@ -493,7 +493,7 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
||||
className="capitalize text-xs cursor-pointer"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||
onSelect={(e: React.SyntheticEvent) => e.preventDefault()}
|
||||
onSelect={(e: Event) => e.preventDefault()}
|
||||
>
|
||||
{typeof column.columnDef.header === 'string'
|
||||
? column.columnDef.header
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
import { ScoringEngine } from "../src/features/ai-copilot/business/scoring-engine";
|
||||
import { ProductAnalysisInput } from "../src/features/ai-copilot/models/ai-analysis.types";
|
||||
|
||||
const mockProducts: ProductAnalysisInput[] = [
|
||||
{ codein: "PROD1", libelle1: "Nouveau Récent", regularityScore: 1, inactivityMonths: 0, totalCa: 100, totalQuantite: 10, tauxMarge: 30, score: 50, storeCount: 1, sales12m: {} },
|
||||
{ codein: "PROD2", libelle1: "Nouveau Ancien", regularityScore: 1, inactivityMonths: 6, totalCa: 100, totalQuantite: 10, tauxMarge: 30, score: 50, storeCount: 1, sales12m: {} },
|
||||
{ codein: "PROD3", libelle1: "Ancien Actif", regularityScore: 12, inactivityMonths: 0, totalCa: 1000, totalQuantite: 100, tauxMarge: 30, score: 50, storeCount: 1, sales12m: {} },
|
||||
{ codein: "PROD4", libelle1: "Nouveau Limite", regularityScore: 2, inactivityMonths: 1, totalCa: 100, totalQuantite: 10, tauxMarge: 30, score: 50, storeCount: 1, sales12m: {} },
|
||||
{ codein: "PROD5", libelle1: "Nouveau Hors Limite", regularityScore: 2, inactivityMonths: 2, totalCa: 100, totalQuantite: 10, tauxMarge: 30, score: 50, storeCount: 1, sales12m: {} },
|
||||
];
|
||||
|
||||
console.log("--- Testing isRecent Logic ---");
|
||||
|
||||
mockProducts.forEach(p => {
|
||||
const result = ScoringEngine.analyzeRayon(p, mockProducts);
|
||||
console.log(`Product: ${p.libelle1} (reg: ${p.regularityScore}, inact: ${p.inactivityMonths})`);
|
||||
console.log(` isRecent: ${result.decision.isRecent}`);
|
||||
console.log(` Recommendation: ${result.decision.recommendation}`);
|
||||
});
|
||||
|
||||
// Expectations:
|
||||
// PROD1: true (1 < 3 && 0 <= 1)
|
||||
// PROD2: false (1 < 3 && 6 > 1) -> Recommendation should NOT be protected (unless score high)
|
||||
// PROD3: false (12 >= 3)
|
||||
// PROD4: true (2 < 3 && 1 <= 1)
|
||||
// PROD5: false (2 < 3 && 2 > 1)
|
||||
@@ -1,55 +0,0 @@
|
||||
|
||||
import { AnalysisEngine } from "./src/features/ai-copilot/business/analysis-engine";
|
||||
|
||||
console.log("--- Testing AI Parser Logic for B/C ---");
|
||||
|
||||
function simulateParse(content: string, simulateRuleResponse = false) {
|
||||
let reco: "A" | "B" | "C" | "Z" | null = null;
|
||||
let ruleApplies = simulateRuleResponse;
|
||||
|
||||
try {
|
||||
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
||||
const jsonText = jsonMatch ? jsonMatch[0] : content;
|
||||
const parsed = JSON.parse(jsonText);
|
||||
|
||||
reco = parsed.recommendation as "A" | "B" | "C" | "Z";
|
||||
ruleApplies = parsed.rule_applies === true;
|
||||
|
||||
if (!ruleApplies && (reco === "B" || reco === "C")) reco = "A";
|
||||
|
||||
if (!reco || !["A", "B", "C", "Z"].includes(reco)) {
|
||||
reco = AnalysisEngine.extractRecommendation(parsed.justification || "") || "A";
|
||||
if (!ruleApplies && (reco === "B" || reco === "C")) reco = "A";
|
||||
}
|
||||
} catch (e) {
|
||||
reco = AnalysisEngine.extractRecommendation(content);
|
||||
if (!ruleApplies && (reco === "B" || reco === "C")) reco = "A";
|
||||
}
|
||||
return reco;
|
||||
}
|
||||
|
||||
const tests = [
|
||||
{ name: "Valid Rule B", json: '{"rule_applies": true, "recommendation": "B", "justification": "blabla"}', expected: "B" },
|
||||
{ name: "Valid Rule C", json: '{"rule_applies": true, "recommendation": "C", "justification": "blabla"}', expected: "C" },
|
||||
{ name: "Invalid Rule B (hallucinated)", json: '{"rule_applies": false, "recommendation": "B", "justification": "blabla"}', expected: "A" },
|
||||
{ name: "Invalid Rule C (hallucinated)", json: '{"rule_applies": false, "recommendation": "C", "justification": "blabla"}', expected: "A" },
|
||||
{ name: "Valid Rule A", json: '{"rule_applies": true, "recommendation": "A", "justification": "blabla"}', expected: "A" },
|
||||
{ name: "Valid Extraction B", json: '{"rule_applies": true, "recommendation": "INVALID", "justification": "Je choisis B."}', expected: "B" },
|
||||
{ name: "Invalid Extraction B", json: '{"rule_applies": false, "recommendation": "INVALID", "justification": "Je choisis B."}', expected: "A" }
|
||||
];
|
||||
|
||||
let allPassed = true;
|
||||
tests.forEach(t => {
|
||||
const res = simulateParse(t.json);
|
||||
const pass = res === t.expected;
|
||||
console.log(`${pass ? "✅" : "❌"} ${t.name}: expected ${t.expected}, got ${res}`);
|
||||
if (!pass) allPassed = false;
|
||||
});
|
||||
|
||||
if (allPassed) {
|
||||
console.log("All parsing tests passed.");
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log("Some tests failed.");
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in new issue
Block a user