mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
feat(grid): preserve initial gamme reference and add modification icon
This commit is contained in:
1 parent
a0f8d9c373
commit
9493a39d12
5 files changed
+42
-37
No files matched your search
@@ -1,30 +0,0 @@
|
||||
|
||||
import { db } from "./src/db";
|
||||
import { ventesProduits } from "./src/db/schema";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
async function diagnose() {
|
||||
console.log("Checking DB connection...");
|
||||
try {
|
||||
await db.execute(sql`SELECT 1`);
|
||||
console.log("DB Connection OK.");
|
||||
|
||||
console.log("Checking table 'ventes_produits'...");
|
||||
const tableCheck = await db.execute(sql`SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'ventes_produits'
|
||||
)`);
|
||||
console.log("Table exists:", tableCheck.rows[0].exists);
|
||||
|
||||
if (tableCheck.rows[0].exists) {
|
||||
const count = await db.select({ count: sql`count(*)` }).from(ventesProduits);
|
||||
console.log("Total rows in 'ventes_produits':", count[0].count);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Diagnosis failed:", err);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
diagnose();
|
||||
@@ -14,6 +14,7 @@ export const ventesProduits = pgTable("ventes_produits", {
|
||||
|
||||
// Gamme & Nomenclature
|
||||
codeGamme: varchar("code_gamme", { length: 20 }),
|
||||
codeGammeInit: varchar("code_gamme_init", { length: 20 }),
|
||||
code3: varchar("code3", { length: 20 }),
|
||||
libelle3: varchar("libelle3", { length: 500 }),
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
import { db } from "@/db";
|
||||
import { ventesProduits } from "@/db/schema";
|
||||
import { eq, and, ne, like } from "drizzle-orm";
|
||||
import { eq, and, ne, like, sql } from "drizzle-orm";
|
||||
import type { ProductRow, GammeCode, GridFilters } from "@/types/grid";
|
||||
import { computeProductScores } from "@/lib/score-engine";
|
||||
|
||||
let isSchemaInitialized = false;
|
||||
|
||||
interface GetProductRowsInput {
|
||||
codeFournisseur: string;
|
||||
magasin?: string;
|
||||
@@ -15,6 +17,26 @@ interface GetProductRowsInput {
|
||||
export async function getProductRows(input: GetProductRowsInput): Promise<ProductRow[]> {
|
||||
const { codeFournisseur, magasin = "TOTAL", filters } = input;
|
||||
|
||||
// Self-healing: Ensure codeGammeInit column exists and is populated
|
||||
if (!isSchemaInitialized) {
|
||||
try {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE ventes_produits
|
||||
ADD COLUMN IF NOT EXISTS code_gamme_init VARCHAR(20)
|
||||
`);
|
||||
await db.execute(sql`
|
||||
UPDATE ventes_produits
|
||||
SET code_gamme_init = code_gamme
|
||||
WHERE code_gamme_init IS NULL
|
||||
`);
|
||||
isSchemaInitialized = true;
|
||||
console.log("[Schema] codeGammeInit initialized successfully.");
|
||||
} catch (err) {
|
||||
console.error("[Schema] Failed to initialize codeGammeInit:", err);
|
||||
// We continue anyway, the mapping fallback will handle it for the UI
|
||||
}
|
||||
}
|
||||
|
||||
// ... (allowedPeriods calculation remains the same)
|
||||
const now = new Date();
|
||||
const allowedPeriods = new Set<string>();
|
||||
@@ -106,6 +128,7 @@ export async function getProductRows(input: GetProductRowsInput): Promise<Produc
|
||||
code3: c3,
|
||||
libelle3: row.libelle3 ?? "",
|
||||
codeGamme: (row.codeGamme as GammeCode | null),
|
||||
codeGammeInit: (row.codeGammeInit as GammeCode | null) ?? (row.codeGamme as GammeCode | null),
|
||||
codeGammeDraft: null,
|
||||
sales12m: { [periode]: qty },
|
||||
totalQuantite: qty,
|
||||
|
||||
@@ -285,7 +285,7 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
||||
size: 56,
|
||||
cell: ({ row }) => (
|
||||
<div className="text-center font-bold text-[12px]" style={{ color: "var(--text-secondary)" }}>
|
||||
{row.original.codeGamme || "-"}
|
||||
{row.original.codeGammeInit || "-"}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -295,12 +295,21 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
||||
size: 110,
|
||||
cell: ({ row }) => {
|
||||
const effectiveGamme = (draftChanges[row.original.codein] ?? row.original.codeGamme) as GammeCode | null;
|
||||
const isModified = row.original.codeGamme !== row.original.codeGammeInit && row.original.codeGammeInit !== null;
|
||||
|
||||
return (
|
||||
<GammeSelect
|
||||
value={effectiveGamme}
|
||||
isDraft={!!draftChanges[row.original.codein]}
|
||||
onChange={(g: GammeCode) => setDraftGamme(row.original.codein, g)}
|
||||
/>
|
||||
<div className="relative group/gamme">
|
||||
<GammeSelect
|
||||
value={effectiveGamme}
|
||||
isDraft={!!draftChanges[row.original.codein]}
|
||||
onChange={(g: GammeCode) => setDraftGamme(row.original.codein, g)}
|
||||
/>
|
||||
{isModified && !draftChanges[row.original.codein] && (
|
||||
<div className="absolute -right-2 -top-2 bg-emerald-500 text-white rounded-full p-0.5 shadow-sm z-10 animate-in zoom-in-50" title="Modifié et validé">
|
||||
<Check className="w-2.5 h-2.5" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface VenteProduit {
|
||||
gtin: string | null;
|
||||
reference: string | null;
|
||||
codeGamme: GammeCode | null;
|
||||
codeGammeInit: GammeCode | null;
|
||||
code3: string | null;
|
||||
libelle3: string | null;
|
||||
magasin: string;
|
||||
@@ -43,6 +44,7 @@ export interface ProductRow {
|
||||
code3: string;
|
||||
libelle3: string;
|
||||
codeGamme: GammeCode | null;
|
||||
codeGammeInit: GammeCode | null;
|
||||
/** Draft gamme value before saving */
|
||||
codeGammeDraft: GammeCode | null;
|
||||
/** 12-month sales quantities, indexed by YYYYMM key */
|
||||
|
||||
Reference in new issue
Block a user