@fayz-ai/plugin-inventory 0.12.1 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/data/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA8DpD,wBAAgB,2BAA2B,IAAI,qBAAqB,CAkrBnE"}
1
+ {"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/data/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA8DpD,wBAAgB,2BAA2B,IAAI,qBAAqB,CAmrBnE"}
@@ -1 +1 @@
1
- {"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA8YpD,wBAAgB,+BAA+B,IAAI,qBAAqB,CAq9BvE"}
1
+ {"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA8YpD,wBAAgB,+BAA+B,IAAI,qBAAqB,CA0hCvE"}
@@ -1,4 +1,4 @@
1
- import type { Product, StockMovement, StockPosition, StockLocation, Supplier, Recipe, RecipeIngredient, RecipeGroup, StockCountSession, StockCountItem, ProductCategory, CreateProductInput, CreateStockMovementInput, CreateRecipeInput, CreateRecipeIngredientInput, OpenStockCountInput, CloseStockCountResult, ProductQuery, MovementQuery, UsageAuditQuery, UsageAuditEntry, UsageAuditCorrection, UsageAuditBatchResult, PaginatedResult, InventorySummary, ConsumeRecipeForSourceInput, ReverseRecipeConsumptionInput, InventoryConsumptionResult } from '../types';
1
+ import type { Product, StockMovement, StockPosition, StockLocation, Supplier, Recipe, RecipeIngredient, RecipeGroup, StockCountSession, StockCountItem, ProductCategory, CreateProductInput, CreateStockMovementInput, CreateRecipeInput, CreateRecipeIngredientInput, OpenStockCountInput, CloseStockCountResult, ProductQuery, MovementQuery, UsageAuditQuery, UsageAuditEntry, UsageAuditCorrection, UsageAuditBatchResult, PaginatedResult, InventorySummary, RestockLine, CategoryCount, ConsumeRecipeForSourceInput, ReverseRecipeConsumptionInput, InventoryConsumptionResult } from '../types';
2
2
  export interface InventoryDataProvider {
3
3
  /**
4
4
  * Ensure the tenant has the reference rows the inventory UI depends on.
@@ -77,5 +77,13 @@ export interface InventoryDataProvider {
77
77
  /** Restores the stock consumed by the same source item, also idempotently. */
78
78
  reverseRecipeConsumption(input: ReverseRecipeConsumptionInput): Promise<InventoryConsumptionResult>;
79
79
  getSummary(): Promise<InventorySummary>;
80
+ /**
81
+ * O que precisa de compra, com o motivo em cada linha. Sem estoque e abaixo
82
+ * do mínimo são as duas perguntas do balcão, e separá-las em duas listas faria
83
+ * a pessoa somar de cabeça.
84
+ */
85
+ getRestockList?(limit?: number): Promise<RestockLine[]>;
86
+ /** Quantos produtos por categoria, incluindo o balde "Sem categoria". */
87
+ getCategoryDistribution?(): Promise<CategoryCount[]>;
80
88
  }
81
89
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import React3, { useState, useMemo, useEffect, useCallback, useRef } from 'react';
2
- import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ModulePage, Button, SubpageHeader, SearchSelect, cn, DatePicker, ListView, ConfirmDialog, Badge, DraftCommitBar, Switch, useSaveBar, DataTable, DashboardCanvas, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, ModalFooter, StockEntryForm, KpiCard, Card, CardHeader, CardTitle, CardContent } from '@fayz-ai/ui';
2
+ import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ModulePage, Button, SubpageHeader, SearchSelect, cn, DatePicker, ListView, ConfirmDialog, Badge, DraftCommitBar, Switch, useSaveBar, DataTable, DashboardCanvas, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, ModalFooter, StockEntryForm, KpiCard, Card, CardHeader, CardTitle, CardContent, Skeleton } from '@fayz-ai/ui';
3
3
  import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, createViewRouter, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, usePermissionOptional, CrudListView, TeachMeLink, useLimitGuard, invalidateRelationOptions, CrudFormSkeleton, CrudFormPage, CrudDetailPage, formatCurrency, useAgentSurface, askAI, PermissionGate, QuickActionsButton, invalidateLimit } from '@fayz-ai/admin';
4
4
  import { createSafeDataProvider, registerTranslations, countByTenant, useTranslation, getActiveUnitId, errorMessage, useDataChanged, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
- import { Trash2, ArrowRightLeft, RefreshCw, ArrowUpRight, ArrowDownRight, Package, Ruler, MapPin, Loader2, Check, ChevronRight, CircleSlash, CircleCheck, ClipboardCheck, AlertTriangle, Plus, BookOpen, Layers, Clock, Coins, Building2, Undo2 } from 'lucide-react';
6
+ import { Trash2, ArrowRightLeft, RefreshCw, ArrowUpRight, ArrowDownRight, Package, Ruler, MapPin, Loader2, Check, ChevronRight, CircleSlash, CircleCheck, ClipboardCheck, AlertTriangle, Plus, BookOpen, Layers, Clock, Coins, Building2, Undo2, PackageX } from 'lucide-react';
7
7
  import { createStore as createStore$1 } from 'zustand/vanilla';
8
8
 
9
9
  // src/index.ts
@@ -360,6 +360,16 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
360
360
  renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium bg-muted text-muted-foreground", children: typeLabel(v) })
361
361
  },
362
362
  // — Stock column (list only) — placed before Cost for the desired column order
363
+ //
364
+ // A célula carrega o saldo E o mínimo, porque a pergunta do balcão é a
365
+ // COMPARAÇÃO entre os dois — no V1 são duas colunas e o olho faz a conta.
366
+ // Uma coluna a mais custaria a sétima numa tabela que já é larga.
367
+ //
368
+ // E o alarme obedece à mesma regra do resumo (`stock > 0 && stock <= min`).
369
+ // Antes era `currentQuantity <= minQuantity`, que com mínimo 0 — o caso de
370
+ // 169 dos 169 produtos deste tenant — pinta o catálogo INTEIRO de vermelho.
371
+ // Alarme que toca sempre é alarme que ninguém ouve: é literalmente o defeito
372
+ // do V1 que a Visão Geral documenta (203 de 218 "em reposição urgente").
363
373
  {
364
374
  key: "currentQuantity",
365
375
  label: t2("inventory.productList.stock"),
@@ -367,7 +377,23 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
367
377
  showInForm: false,
368
378
  showInTable: true,
369
379
  sortable: true,
370
- renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: `text-right block ${row.currentQuantity <= row.minQuantity ? "text-destructive font-medium" : ""}`, children: row.currentQuantity })
380
+ renderCell: (_v, row) => {
381
+ const qty2 = Number(row.currentQuantity) || 0;
382
+ const min = Number(row.minQuantity) || 0;
383
+ const unit = row.measurementUnitName;
384
+ const below = min > 0 && qty2 > 0 && qty2 <= min;
385
+ const empty = qty2 <= 0;
386
+ return /* @__PURE__ */ jsxs("span", { className: "block text-right", children: [
387
+ /* @__PURE__ */ jsxs("span", { className: below ? "font-medium text-destructive" : empty ? "text-muted-foreground" : "", children: [
388
+ qty2.toLocaleString("pt-BR", { maximumFractionDigits: 3 }),
389
+ unit ? /* @__PURE__ */ jsx("span", { className: "ml-1 text-xs text-muted-foreground", children: unit }) : null
390
+ ] }),
391
+ min > 0 && /* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
392
+ "m\xEDn. ",
393
+ min.toLocaleString("pt-BR", { maximumFractionDigits: 3 })
394
+ ] })
395
+ ] });
396
+ }
371
397
  },
372
398
  // — Pricing (form) + Cost column (table) —
373
399
  {
@@ -380,7 +406,12 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
380
406
  currencyLocale: currency.locale,
381
407
  showInTable: true,
382
408
  sortable: false,
383
- renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "text-right block text-muted-foreground", children: formatCurrency(Number(v) || 0, currency) })
409
+ // Mesma regra do preço de venda: custo zero é "não cadastrado", não
410
+ // "custa nada". E é ele que faz o Valor abaixo mentir.
411
+ renderCell: (v) => {
412
+ const n = Number(v);
413
+ return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: v == null || v === "" || !Number.isFinite(n) || n === 0 ? "\u2014" : formatCurrency(n, currency) });
414
+ }
384
415
  },
385
416
  // — Value column (list only): stock × cost —
386
417
  {
@@ -390,7 +421,19 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
390
421
  showInForm: false,
391
422
  showInTable: true,
392
423
  sortable: false,
393
- renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-right block font-medium", children: formatCurrency((row.currentQuantity || 0) * (row.costPrice || 0), currency) })
424
+ // Saldo em mãos SEM custo cadastrado não vale R$ 0,00 vale um número
425
+ // que esta casa não sabe. É o beco que o V1 documenta nas movimentações:
426
+ // "o Valor Total se descola da realidade sem ninguém errar nada". Zero
427
+ // com saldo zero é verdade; zero com 84 unidades na prateleira é uma
428
+ // afirmação que o dado não sustenta.
429
+ renderCell: (_v, row) => {
430
+ const qty2 = Number(row.currentQuantity) || 0;
431
+ const cost = Number(row.costPrice) || 0;
432
+ if (qty2 > 0 && cost === 0) {
433
+ return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", title: "Sem custo cadastrado, o valor deste saldo n\xE3o pode ser calculado", children: "\u2014" });
434
+ }
435
+ return /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: formatCurrency(qty2 * cost, currency) });
436
+ }
394
437
  },
395
438
  {
396
439
  key: "salePrice",
@@ -400,7 +443,16 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
400
443
  currency: currency.code,
401
444
  currencySymbol: currency.symbol,
402
445
  currencyLocale: currency.locale,
403
- showInTable: false,
446
+ // O V1 mostra Custo E Venda lado a lado. Custo sozinho não responde
447
+ // "quanto isto rende", e o preço estava só dentro da ficha.
448
+ showInTable: true,
449
+ // Zero aqui é "não informado", não "de graça" — 100 dos 169 produtos deste
450
+ // tenant estão assim. "R$ 0,00" afirmaria um preço; o travessão diz que
451
+ // falta cadastrar, que é a verdade e é o que se pode agir sobre.
452
+ renderCell: (v) => {
453
+ const n = Number(v);
454
+ return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: v == null || v === "" || !Number.isFinite(n) || n === 0 ? "\u2014" : formatCurrency(n, currency) });
455
+ },
404
456
  // Ingredients and intermediate preparations are inputs, not things sold
405
457
  // directly to the customer. If it is sold, it belongs to the menu-item
406
458
  // classification and gets the commercial fields there.
@@ -1138,7 +1190,7 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
1138
1190
  /* @__PURE__ */ jsxs("div", { children: [
1139
1191
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: needsDest ? t2("inventory.stock.fromLocation") : t2("inventory.stock.locationLabel") }),
1140
1192
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-1.5", children: sortedLocations.map((l) => {
1141
- const qty = positionByLocation[l.id] ?? 0;
1193
+ const qty2 = positionByLocation[l.id] ?? 0;
1142
1194
  return /* @__PURE__ */ jsxs(
1143
1195
  "button",
1144
1196
  {
@@ -1152,8 +1204,8 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
1152
1204
  /* @__PURE__ */ jsx("span", { children: l.name }),
1153
1205
  productId && /* @__PURE__ */ jsx("span", { className: cn(
1154
1206
  "inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-semibold tabular-nums",
1155
- qty > 0 ? "bg-success/10 text-success" : "bg-muted text-muted-foreground"
1156
- ), children: qty })
1207
+ qty2 > 0 ? "bg-success/10 text-success" : "bg-muted text-muted-foreground"
1208
+ ), children: qty2 })
1157
1209
  ]
1158
1210
  },
1159
1211
  l.id
@@ -1163,7 +1215,7 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
1163
1215
  needsDest && /* @__PURE__ */ jsxs("div", { children: [
1164
1216
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("inventory.stock.toLocation") }),
1165
1217
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-1.5", children: sortedLocations.filter((l) => l.id !== locationId).map((l) => {
1166
- const qty = positionByLocation[l.id] ?? 0;
1218
+ const qty2 = positionByLocation[l.id] ?? 0;
1167
1219
  return /* @__PURE__ */ jsxs(
1168
1220
  "button",
1169
1221
  {
@@ -1177,8 +1229,8 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
1177
1229
  /* @__PURE__ */ jsx("span", { children: l.name }),
1178
1230
  productId && /* @__PURE__ */ jsx("span", { className: cn(
1179
1231
  "inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-semibold tabular-nums",
1180
- qty > 0 ? "bg-success/10 text-success" : "bg-muted text-muted-foreground"
1181
- ), children: qty })
1232
+ qty2 > 0 ? "bg-success/10 text-success" : "bg-muted text-muted-foreground"
1233
+ ), children: qty2 })
1182
1234
  ]
1183
1235
  },
1184
1236
  l.id
@@ -1336,9 +1388,30 @@ function MovementHistoryView({ onViewDetail, onNew, onNewExit } = {}) {
1336
1388
  cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: getValue() })
1337
1389
  },
1338
1390
  {
1391
+ // O MOTIVO entra debaixo do produto, não como sétima coluna.
1392
+ //
1393
+ // É o campo central do razão do V1 — "nenhum movimento existe sem motivo"
1394
+ // — e estava gravado e invisível: as entradas deste tenant carregam
1395
+ // "Entrada via DANFE NF-e 10467692" desde a migração, que é a resposta
1396
+ // exata para a pergunta nº 1 do usuário no V1 ("de onde vieram essas
1397
+ // entradas que eu não lancei?"). Como legenda ele fica onde o olho já
1398
+ // está, em vez de numa coluna a mais numa tabela que já tem seis.
1339
1399
  accessorKey: "productName",
1340
1400
  header: t2("inventory.history.columnProduct"),
1341
- cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "font-medium", children: getValue() ?? "\u2014" })
1401
+ cell: ({ row }) => {
1402
+ const reason = row.original.reason || void 0;
1403
+ const doc = row.original.documentNumber || void 0;
1404
+ const printableDoc = doc && !/^[0-9a-f-]{32,36}$/i.test(doc) ? doc : void 0;
1405
+ const caption = reason ?? (printableDoc ? `Documento ${printableDoc}` : void 0);
1406
+ return /* @__PURE__ */ jsxs("span", { className: "block", children: [
1407
+ /* @__PURE__ */ jsx("span", { className: "block font-medium", children: row.original.productName ?? "\u2014" }),
1408
+ caption ? /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", title: caption, children: caption }) : (
1409
+ // Movimento sem motivo é um buraco no razão, e o V1 não deixa
1410
+ // criar um pela tela. Dizê-lo é o que faz alguém parar de criar.
1411
+ /* @__PURE__ */ jsx("span", { className: "block text-xs italic text-muted-foreground", children: "Sem motivo registrado" })
1412
+ )
1413
+ ] });
1414
+ }
1342
1415
  },
1343
1416
  {
1344
1417
  accessorKey: "movementType",
@@ -1372,7 +1445,25 @@ function MovementHistoryView({ onViewDetail, onNew, onNewExit } = {}) {
1372
1445
  {
1373
1446
  accessorKey: "totalCost",
1374
1447
  header: t2("inventory.history.columnTotal"),
1375
- cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-right block text-muted-foreground", children: formatCurrency(getValue(), currency) })
1448
+ // Custo é OPCIONAL na entrada, e o V1 documenta o beco que isso abre: o
1449
+ // saldo de quantidade sobe, o valor do estoque não, e o "Valor Total" da
1450
+ // visão geral se descola da realidade sem ninguém errar nada. "R$ 0,00"
1451
+ // esconde isso atrás de um número; o travessão com a explicação no hover
1452
+ // diz que o movimento entrou sem valor — que é o fato.
1453
+ cell: ({ getValue }) => {
1454
+ const n = Number(getValue());
1455
+ if (!Number.isFinite(n) || n === 0) {
1456
+ return /* @__PURE__ */ jsx(
1457
+ "span",
1458
+ {
1459
+ className: "block text-right text-muted-foreground",
1460
+ title: "Movimento registrado sem custo: o saldo mudou, o valor do estoque n\xE3o",
1461
+ children: "\u2014"
1462
+ }
1463
+ );
1464
+ }
1465
+ return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: formatCurrency(n, currency) });
1466
+ }
1376
1467
  }
1377
1468
  ], [currency, t2]);
1378
1469
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
@@ -3159,22 +3250,36 @@ function buildNav(config, view, navigate, t2) {
3159
3250
  icon: "Package",
3160
3251
  active: view.startsWith("products"),
3161
3252
  children: [
3162
- { id: "products-new", label: t2("inventory.nav.new"), active: view === "products-new", onClick: () => navigate("products-new") },
3253
+ { id: "products-new", kind: "action", label: t2("inventory.nav.new"), active: view === "products-new", onClick: () => navigate("products-new") },
3163
3254
  { id: "products-list", label: t2("inventory.nav.list"), active: view === "products-list" || view.startsWith("products-detail:") || view.startsWith("products-stock:"), onClick: () => navigate("products-list") }
3164
3255
  ]
3165
3256
  }] : [],
3257
+ // Movimentações é UMA tela: o que entrou e o que saiu. A pílula "Histórico"
3258
+ // ao lado da aba repetia o nome da própria aba (e o título da página já diz
3259
+ // "Histórico de Estoque"), então a aba passa a SER o destino — filha única
3260
+ // não desenha controle.
3166
3261
  {
3167
3262
  id: "stock",
3168
3263
  label: t2("inventory.nav.stock"),
3169
3264
  icon: "ArrowUpCircle",
3170
3265
  children: [
3171
- { id: "stock-entry", label: t2("inventory.nav.entry"), active: view === "stock-entry", onClick: () => navigate("stock-entry") },
3172
- { id: "stock-exit", label: t2("inventory.nav.exit"), active: view === "stock-exit", onClick: () => navigate("stock-exit") },
3173
- // Before History on purpose: the parent tab flattens to the LAST child,
3174
- // and "Stock" has always landed on History.
3175
- { id: "stock-counts", label: t2("inventory.nav.counts"), active: view === "stock-counts" || view.startsWith("stock-counts"), onClick: () => navigate("stock-counts") },
3176
- { id: "stock-usage-audit", label: t2("inventory.nav.usageAudit"), active: view === "stock-usage-audit", onClick: () => navigate("stock-usage-audit") },
3177
- { id: "stock-history", label: t2("inventory.nav.history"), active: view === "stock-history", onClick: () => navigate("stock-history") }
3266
+ { id: "stock-entry", kind: "action", label: t2("inventory.nav.entry"), active: view === "stock-entry", onClick: () => navigate("stock-entry") },
3267
+ { id: "stock-exit", kind: "action", label: t2("inventory.nav.exit"), active: view === "stock-exit", onClick: () => navigate("stock-exit") },
3268
+ { id: "stock-history", label: t2("inventory.nav.history"), defaultChild: true, active: view === "stock-history", onClick: () => navigate("stock-history") }
3269
+ ]
3270
+ },
3271
+ // Inventário conferir o que o sistema diz contra o que existe na
3272
+ // prateleira. Contagem e auditoria de uso respondem à mesma pergunta por
3273
+ // dois caminhos (contar e comparar consumo), e nenhuma das duas é uma
3274
+ // movimentação: viviam como pílula de Movimentações e ninguém as achava.
3275
+ {
3276
+ id: "inventory-check",
3277
+ label: t2("inventory.nav.inventory"),
3278
+ icon: "ClipboardCheck",
3279
+ children: [
3280
+ { id: "stock-counts-new", kind: "action", label: t2("inventory.nav.new"), active: view === "stock-counts-new", onClick: () => navigate("stock-counts-new") },
3281
+ { id: "stock-counts", label: t2("inventory.nav.counts"), defaultChild: true, active: view === "stock-counts" || view.startsWith("stock-counts"), onClick: () => navigate("stock-counts") },
3282
+ { id: "stock-usage-audit", label: t2("inventory.nav.usageAudit"), active: view === "stock-usage-audit", onClick: () => navigate("stock-usage-audit") }
3178
3283
  ]
3179
3284
  }
3180
3285
  ];
@@ -3185,7 +3290,7 @@ function buildNav(config, view, navigate, t2) {
3185
3290
  icon: "BookOpen",
3186
3291
  children: [
3187
3292
  { id: "recipes-list", label: config.labels.recipesList, active: view === "recipes-list" || view.startsWith("recipes-detail:"), onClick: () => navigate("recipes-list") },
3188
- { id: "recipes-new", label: config.labels.recipesNew, active: view === "recipes-new", onClick: () => navigate("recipes-new") }
3293
+ { id: "recipes-new", kind: "action", label: config.labels.recipesNew, active: view === "recipes-new", onClick: () => navigate("recipes-new") }
3189
3294
  ]
3190
3295
  });
3191
3296
  }
@@ -3389,6 +3494,120 @@ function RecentActivityPanel() {
3389
3494
  ] })
3390
3495
  ] });
3391
3496
  }
3497
+ function NoMinimumKpi() {
3498
+ const summary = useInventoryStore((s) => s.summary);
3499
+ useEnsureSummary();
3500
+ const n = summary?.noMinimumCount ?? 0;
3501
+ return /* @__PURE__ */ jsx(
3502
+ KpiCard,
3503
+ {
3504
+ label: "Sem m\xEDnimo definido",
3505
+ icon: "AlertTriangle",
3506
+ value: String(n),
3507
+ sub: n > 0 ? "N\xE3o entram no alerta de estoque baixo" : "Todo produto tem m\xEDnimo"
3508
+ }
3509
+ );
3510
+ }
3511
+ function RestockRowSkeleton() {
3512
+ return /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3", children: [
3513
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-40" }),
3514
+ /* @__PURE__ */ jsx("span", { className: "flex-1" }),
3515
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16" })
3516
+ ] }, i)) });
3517
+ }
3518
+ function qty(value, unit) {
3519
+ const n = value.toLocaleString("pt-BR", { maximumFractionDigits: 3 });
3520
+ return unit ? `${n} ${unit}` : n;
3521
+ }
3522
+ function RestockPanel() {
3523
+ const rows = useInventoryStore((s) => s.restock);
3524
+ const summary = useInventoryStore((s) => s.summary);
3525
+ const fetchRestock = useInventoryStore((s) => s.fetchRestock);
3526
+ useEnsureSummary();
3527
+ useEffect(() => {
3528
+ void fetchRestock();
3529
+ }, []);
3530
+ const belowMinimum = (rows ?? []).filter((r) => r.reason === "below-minimum");
3531
+ const outOfStock = (rows ?? []).filter((r) => r.reason === "out-of-stock");
3532
+ const noMinimum = summary?.noMinimumCount ?? 0;
3533
+ const total = summary?.totalProducts ?? 0;
3534
+ const nobodyHasMinimum = total > 0 && noMinimum === total;
3535
+ return /* @__PURE__ */ jsxs(Card, { children: [
3536
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: "Precisa de reposi\xE7\xE3o" }) }),
3537
+ /* @__PURE__ */ jsx(CardContent, { children: rows === null ? /* @__PURE__ */ jsx(RestockRowSkeleton, {}) : nobodyHasMinimum ? (
3538
+ // Sem nenhum mínimo cadastrado esta lista não é um alerta: é o
3539
+ // catálogo inteiro. Dizer o que falta vale mais do que listá-lo.
3540
+ /* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
3541
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Nenhum produto tem estoque m\xEDnimo definido." }),
3542
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-muted-foreground", children: [
3543
+ "Sem o m\xEDnimo na ficha do produto n\xE3o h\xE1 reposi\xE7\xE3o a calcular \u2014 o alerta de estoque baixo fica em zero mesmo com o dep\xF3sito vazio.",
3544
+ (summary?.outOfStockCount ?? 0) > 0 && ` Hoje ${summary.outOfStockCount === 1 ? "h\xE1 1 produto" : `h\xE1 ${summary.outOfStockCount} produtos`} com saldo zerado.`
3545
+ ] })
3546
+ ] })
3547
+ ) : rows.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nada a repor: todo produto est\xE1 acima do m\xEDnimo." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: [...belowMinimum, ...outOfStock].slice(0, 8).map((row) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 text-sm", children: [
3548
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
3549
+ /* @__PURE__ */ jsx("span", { className: "block truncate", children: row.name }),
3550
+ row.categoryName && /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", children: row.categoryName })
3551
+ ] }),
3552
+ row.reason === "below-minimum" ? /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "gap-1 shrink-0", children: [
3553
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3 w-3", "aria-hidden": "true" }),
3554
+ qty(row.currentQuantity, row.unit),
3555
+ " de ",
3556
+ qty(row.minQuantity, row.unit)
3557
+ ] }) : /* @__PURE__ */ jsxs(Badge, { variant: "outline", className: "gap-1 shrink-0", children: [
3558
+ /* @__PURE__ */ jsx(PackageX, { className: "h-3 w-3", "aria-hidden": "true" }),
3559
+ "Sem estoque"
3560
+ ] })
3561
+ ] }, row.productId)) }) })
3562
+ ] });
3563
+ }
3564
+ function CategoryMixPanel() {
3565
+ const mix = useInventoryStore((s) => s.categoryMix);
3566
+ const fetchMix = useInventoryStore((s) => s.fetchCategoryMix);
3567
+ useEffect(() => {
3568
+ void fetchMix();
3569
+ }, []);
3570
+ const total = (mix ?? []).reduce((sum, c) => sum + c.count, 0);
3571
+ const uncategorized = (mix ?? []).find((c) => c.categoryId === null);
3572
+ return /* @__PURE__ */ jsxs(Card, { children: [
3573
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: "Produtos por categoria" }) }),
3574
+ /* @__PURE__ */ jsx(CardContent, { children: mix === null ? /* @__PURE__ */ jsx(RestockRowSkeleton, {}) : total === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nenhum produto ativo para distribuir." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3575
+ /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: mix.slice(0, 8).map((row) => {
3576
+ const pct = Math.round(row.count / total * 100);
3577
+ const isGap = row.categoryId === null;
3578
+ return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 text-sm", children: [
3579
+ /* @__PURE__ */ jsx("span", { className: "w-40 shrink-0 truncate", title: row.name, children: row.name }),
3580
+ /* @__PURE__ */ jsx(
3581
+ "span",
3582
+ {
3583
+ className: "relative h-2 flex-1 overflow-hidden rounded-full bg-muted",
3584
+ "aria-hidden": "true",
3585
+ children: /* @__PURE__ */ jsx(
3586
+ "span",
3587
+ {
3588
+ className: isGap ? "absolute inset-y-0 left-0 rounded-full bg-muted-foreground/60" : "absolute inset-y-0 left-0 rounded-full bg-primary",
3589
+ style: { width: `${pct}%` }
3590
+ }
3591
+ )
3592
+ }
3593
+ ),
3594
+ /* @__PURE__ */ jsxs("span", { className: "w-20 shrink-0 text-right tabular-nums text-muted-foreground", children: [
3595
+ row.count,
3596
+ " \xB7 ",
3597
+ pct,
3598
+ "%"
3599
+ ] })
3600
+ ] }, row.categoryId ?? "none");
3601
+ }) }),
3602
+ uncategorized && uncategorized.count > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-3 text-xs text-muted-foreground", children: [
3603
+ uncategorized.count,
3604
+ " de ",
3605
+ total,
3606
+ " produtos ainda n\xE3o t\xEAm categoria \u2014 \xE9 o que impede este painel, os filtros e as isen\xE7\xF5es de taxa de dizerem algo \xFAtil."
3607
+ ] })
3608
+ ] }) })
3609
+ ] });
3610
+ }
3392
3611
  function createInventoryDashboardWidgets(ctx2) {
3393
3612
  const withCtx = (Inner) => {
3394
3613
  const Wrapped = () => /* @__PURE__ */ jsx(InventoryContextProvider, { config: ctx2.config, provider: ctx2.provider, store: ctx2.store, children: /* @__PURE__ */ jsx(Inner, {}) });
@@ -3402,7 +3621,12 @@ function createInventoryDashboardWidgets(ctx2) {
3402
3621
  defineKpiWidget({ id: "inventory.kpi.total-products", title: "inventory.dashboard.totalProducts", domain: "inventory", defaultOrder: 1, defaultVisible: false, component: withCtx(TotalProductsKpi) }),
3403
3622
  defineKpiWidget({ id: "inventory.kpi.low-stock", title: "inventory.dashboard.lowStock", domain: "inventory", defaultOrder: 2, defaultVisible: false, component: withCtx(LowStockKpi) }),
3404
3623
  defineKpiWidget({ id: "inventory.kpi.out-of-stock", title: "inventory.dashboard.outOfStock", domain: "inventory", defaultOrder: 3, defaultVisible: false, component: withCtx(OutOfStockKpi) }),
3405
- defineCustomWidget({ id: "inventory.panel.recent-activity", title: "inventory.dashboard.recentActivity", domain: "inventory", span: 2, defaultOrder: 10, surfaces: ["plugin-home"], component: withCtx(RecentActivityPanel) })
3624
+ // Escondido na home global diagnóstico de cadastro, não manchete do
3625
+ // negócio) e visível no painel do estoque, que é onde se conserta.
3626
+ defineKpiWidget({ id: "inventory.kpi.no-minimum", title: "Sem m\xEDnimo definido", domain: "inventory", defaultOrder: 4, defaultVisible: false, component: withCtx(NoMinimumKpi) }),
3627
+ defineCustomWidget({ id: "inventory.panel.recent-activity", title: "inventory.dashboard.recentActivity", domain: "inventory", span: 2, defaultOrder: 10, surfaces: ["plugin-home"], component: withCtx(RecentActivityPanel) }),
3628
+ defineCustomWidget({ id: "inventory.panel.restock", title: "Precisa de reposi\xE7\xE3o", domain: "inventory", span: 2, defaultOrder: 11, surfaces: ["plugin-home"], component: withCtx(RestockPanel) }),
3629
+ defineCustomWidget({ id: "inventory.panel.category-mix", title: "Produtos por categoria", domain: "inventory", span: 2, defaultOrder: 12, surfaces: ["plugin-home"], component: withCtx(CategoryMixPanel) })
3406
3630
  ];
3407
3631
  }
3408
3632
 
@@ -4024,7 +4248,8 @@ function createMockInventoryProvider() {
4024
4248
  outOfStockCount: outOfStock.length,
4025
4249
  totalStockValue: totalValue,
4026
4250
  recentMovementCount: recent.length,
4027
- movementsByType
4251
+ movementsByType,
4252
+ noMinimumCount: active.filter((p) => !(p.minQuantity > 0)).length
4028
4253
  };
4029
4254
  }
4030
4255
  };
@@ -5060,8 +5285,58 @@ function createSupabaseInventoryProvider() {
5060
5285
  outOfStockCount: outOfStock.length,
5061
5286
  totalStockValue: totalValue,
5062
5287
  recentMovementCount: movs.length,
5063
- movementsByType
5288
+ movementsByType,
5289
+ // `> 0`, não `!= null`: mínimo zero é o mesmo que não ter mínimo — é
5290
+ // dele que sai o alerta que nunca dispara.
5291
+ noMinimumCount: items.filter((p) => !(Number(p.min_stock) > 0)).length
5064
5292
  };
5293
+ },
5294
+ async getRestockList(limit = 50) {
5295
+ const { core, pub } = getClients();
5296
+ const { data } = await core.from(PRODUCTS_READ).select("id, name, category_id, stock, min_stock, unit").eq("is_active", true);
5297
+ const rows = data ?? [];
5298
+ const needs = rows.map((r) => ({
5299
+ productId: r.id,
5300
+ name: r.name,
5301
+ categoryId: r.category_id,
5302
+ currentQuantity: Number(r.stock) || 0,
5303
+ minQuantity: Number(r.min_stock) || 0,
5304
+ unit: r.unit ?? void 0
5305
+ })).filter((r) => r.currentQuantity <= 0 || r.minQuantity > 0 && r.currentQuantity <= r.minQuantity).sort((a, b) => {
5306
+ const rank = (x) => x.minQuantity > 0 && x.currentQuantity > 0 ? 0 : 1;
5307
+ return rank(a) - rank(b) || a.name.localeCompare(b.name, "pt-BR");
5308
+ }).slice(0, limit);
5309
+ const catIds = [...new Set(needs.map((n) => n.categoryId).filter(Boolean))];
5310
+ const names = /* @__PURE__ */ new Map();
5311
+ if (catIds.length) {
5312
+ const { data: cats } = await pub.from("categories").select("id, name").in("id", catIds);
5313
+ for (const c of cats ?? []) names.set(c.id, c.name);
5314
+ }
5315
+ return needs.map(({ categoryId, ...rest }) => ({
5316
+ ...rest,
5317
+ categoryName: categoryId ? names.get(categoryId) : void 0,
5318
+ reason: rest.currentQuantity <= 0 ? "out-of-stock" : "below-minimum"
5319
+ }));
5320
+ },
5321
+ async getCategoryDistribution() {
5322
+ const { core, pub } = getClients();
5323
+ const { data } = await core.from(PRODUCTS_READ).select("category_id").eq("is_active", true);
5324
+ const rows = data ?? [];
5325
+ const tally = /* @__PURE__ */ new Map();
5326
+ for (const r of rows) tally.set(r.category_id ?? null, (tally.get(r.category_id ?? null) ?? 0) + 1);
5327
+ const ids = [...tally.keys()].filter(Boolean);
5328
+ const names = /* @__PURE__ */ new Map();
5329
+ if (ids.length) {
5330
+ const { data: cats } = await pub.from("categories").select("id, name").in("id", ids);
5331
+ for (const c of cats ?? []) names.set(c.id, c.name);
5332
+ }
5333
+ return [...tally.entries()].map(([categoryId, count]) => ({
5334
+ categoryId,
5335
+ // "Sem categoria" é uma resposta, não um buraco: é o que diz ao dono
5336
+ // que o cadastro está incompleto. Esconder o balde esconderia isso.
5337
+ name: categoryId ? names.get(categoryId) ?? "Categoria removida" : "Sem categoria",
5338
+ count
5339
+ })).sort((a, b) => b.count - a.count);
5065
5340
  }
5066
5341
  };
5067
5342
  return provider;
@@ -5085,6 +5360,28 @@ function createInventoryStore(provider) {
5085
5360
  recipesLoading: false,
5086
5361
  summary: null,
5087
5362
  summaryLoading: false,
5363
+ restock: null,
5364
+ categoryMix: null,
5365
+ async fetchRestock() {
5366
+ if (!provider.getRestockList) return;
5367
+ return dedup("inv:restock", async () => {
5368
+ try {
5369
+ set({ restock: await provider.getRestockList() });
5370
+ } catch {
5371
+ set({ restock: [] });
5372
+ }
5373
+ });
5374
+ },
5375
+ async fetchCategoryMix() {
5376
+ if (!provider.getCategoryDistribution) return;
5377
+ return dedup("inv:category-mix", async () => {
5378
+ try {
5379
+ set({ categoryMix: await provider.getCategoryDistribution() });
5380
+ } catch {
5381
+ set({ categoryMix: [] });
5382
+ }
5383
+ });
5384
+ },
5088
5385
  async fetchSummary() {
5089
5386
  return dedup("inv:summary", async () => {
5090
5387
  set({ summaryLoading: true });
@@ -5609,10 +5906,11 @@ var en = {
5609
5906
  "inventory.nav.entry": "Entry",
5610
5907
  "inventory.nav.exit": "Exit",
5611
5908
  "inventory.nav.history": "History",
5909
+ "inventory.nav.inventory": "Inventory",
5612
5910
  "inventory.nav.list": "List",
5613
5911
  "inventory.nav.new": "New",
5614
5912
  "inventory.nav.products": "Products",
5615
- "inventory.nav.stock": "Stock",
5913
+ "inventory.nav.stock": "Movements",
5616
5914
  "inventory.page.newProduct": "New Product",
5617
5915
  "inventory.page.newProductDesc": "Add a product to your catalog",
5618
5916
  "inventory.page.settingsSubtitle": "Preferences, suppliers, categories, and units",
@@ -6057,10 +6355,11 @@ var ptBR = {
6057
6355
  "inventory.nav.entry": "Entrada",
6058
6356
  "inventory.nav.exit": "Sa\xEDda",
6059
6357
  "inventory.nav.history": "Hist\xF3rico",
6358
+ "inventory.nav.inventory": "Invent\xE1rio",
6060
6359
  "inventory.nav.list": "Lista",
6061
6360
  "inventory.nav.new": "Novo",
6062
6361
  "inventory.nav.products": "Produtos",
6063
- "inventory.nav.stock": "Estoque",
6362
+ "inventory.nav.stock": "Movimenta\xE7\xF5es",
6064
6363
  "inventory.page.newProduct": "Novo Produto",
6065
6364
  "inventory.page.newProductDesc": "Adicionar um produto ao seu cat\xE1logo",
6066
6365
  "inventory.page.settingsSubtitle": "Prefer\xEAncias, fornecedores, categorias e unidades",