Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4239361
docs: add CHANGELOG.md with project history
lexmarcos Jun 9, 2026
ecd53f6
feat(barcode-scanner): add error modal with copy content functionality
lexmarcos Jun 9, 2026
f8d183c
feat(product-form): add existing product modal and batch overlay types
lexmarcos Jun 9, 2026
89ae90e
feat(stock-movements): replace missing product toast with modal dialog
lexmarcos Jun 9, 2026
434fd2a
feat(new-product-inline): check API for existing product on barcode s…
lexmarcos Jun 9, 2026
16cd0ee
refactor(transfers): extract subcomponents, add batch drawer, product…
lexmarcos Jun 9, 2026
15dc3b2
feat(barcode-scanner): use barcode-detector stream
lexmarcos Jun 10, 2026
4aaa23c
fix(warehouse): hydrate selected warehouse synchronously
lexmarcos Jun 10, 2026
935b364
feat(stock-movements): harden draft and barcode flows
lexmarcos Jun 10, 2026
a402988
fix(product-form): make scannedExistingProduct optional and fix all t…
lexmarcos Jun 10, 2026
53dff8e
fix(api): preserve client config on post-refresh retries
lexmarcos Jun 10, 2026
30db4b5
refactor(stock-movements): store draft images as blobs
lexmarcos Jun 10, 2026
d6dadb7
refactor(stock-movements): extract payload builders from model
lexmarcos Jun 10, 2026
2fd4e69
refactor(stock-movements): extract product options and footer helpers…
lexmarcos Jun 10, 2026
ad11fd7
refactor(stock-movements): extract draft persistence hook from model
lexmarcos Jun 10, 2026
949c74a
refactor(stock-movements): extract batch form and scanner hooks from …
lexmarcos Jun 10, 2026
ce4fe4d
fix(stock-movement): recognize inline product barcode in scanner
lexmarcos Jun 11, 2026
9006ec7
fix(i18n): translate toast messages and use correct severity levels
lexmarcos Jun 11, 2026
be5ddc2
feat(toast): style sonner toasts with brutalist design system
lexmarcos Jun 11, 2026
a66809d
feat(nuqs): persist product search query in URL across navigations
lexmarcos Jun 17, 2026
7c15ad5
feat(products): add clear button to search input
lexmarcos Jun 17, 2026
ceaeafe
feat(stock-movement): replace inline duplicate toast with warning drawer
lexmarcos Jun 17, 2026
5ff2ba9
feat(sidebar): add exploratory tests navigation link
lexmarcos Jun 17, 2026
07355c8
refactor(footer): extract useFooterVisibility hook from duplicate scr…
lexmarcos Jun 18, 2026
5ddd552
feat(new-product): extract custom attribute validation and scroll-to-…
lexmarcos Jun 18, 2026
2ef059b
feat(sidebar): hide dev-only nav items in production
lexmarcos Jun 18, 2026
ac46c4b
refactor: extract shared BRL currency formatter to lib/currency
lexmarcos Jun 18, 2026
616c193
feat: add deterministic category color util
lexmarcos Jun 18, 2026
e3d736d
feat(products): add mobile product card with image, category badge an…
lexmarcos Jun 18, 2026
6ecbec5
feat(exploratory-tests): add IndexedDB-backed test checklist
lexmarcos Jun 18, 2026
195dead
fix(ui): increase toast CSS specificity to beat sonner injected styles
lexmarcos Jun 18, 2026
c98c976
feat(category-color): generate light base colors and extract badge st…
lexmarcos Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ jobs:
- name: Lint
run: pnpm lint

- name: Typecheck
run: pnpm typecheck

- name: Unit tests
run: pnpm exec vitest run

Expand Down
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Changelog

### Added

- Added production-only Microsoft Clarity tracking.
- Added AI product image prompt generation, including prompt management, price overlay controls, image sharing support, and a dedicated prompt generation page.
- Added a stock movement detail page with model, view, types, and focused tests.
- Added IndexedDB-backed stock movement draft persistence and runtime recovery tracking.
- Added stock movement submit progress feedback.
- Added product form image processing locks to prevent submitting while images are still being processed.

### Changed

- Redesigned the main operational views for batches, products, catalog setup, sales, stock movements, system administration, transfers, and warehouses.
- Modernized the product detail page with a restructured layout and fixed bottom action bar.
- Moved product prompt generation out of the product prompt list flow and into its own page.
- Split product image dropzone UI states for clearer current, selected, removed, and processing behavior.
- Centralized dynamic chart component loading for the sales chart.
- Improved batch model state handling and authentication submit loading state clarity.

### Fixed

- Fixed Recharts client-only loading issues.
- Stabilized warehouse client hydration.
- Refined stock movement draft routing and cleaned up lingering debounce timers in tests.
- Fixed uploaded company logo preview rendering.
- Fixed iOS PWA product prompt sharing flows by restoring touch interaction, returning to the prompt list, and blocking unsupported file sharing when needed.
- Preserved product image removal state when a replacement file picker is cancelled.
- Autofilled prompt pricing from the latest batch after async batch data loads.
- Fixed prompt share cleanup callback types so production builds pass type checking.

### Maintenance

- Added a React Doctor report.
- Updated project agent instructions.
- Expanded model and browser tests around product prompts, stock movement details, draft storage, and image handling.
10 changes: 2 additions & 8 deletions app/(pages)/batches/[id]/batches-detail.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
parseISO,
} from "date-fns";
import { ptBR } from "date-fns/locale";
import { formatCentsToBRL } from "@/lib/currency";
import { useBreadcrumb } from "@/components/breadcrumb";
import type {
BatchDetail,
Expand Down Expand Up @@ -64,10 +65,6 @@ const STATUS_MAP: Record<BatchStatusKind, BatchStatusView> = {
},
};

const BATCH_DETAIL_CURRENCY_FORMATTER = new Intl.NumberFormat("pt-BR", {
style: "currency",
currency: "BRL",
});

/* ─── Pure Functions ─── */

Expand Down Expand Up @@ -96,10 +93,7 @@ export const computeBatchStatus = (
return STATUS_MAP.ok;
};

export const formatCentsToBRL = (cents: number | null | undefined): string => {
if (cents === null || cents === undefined) return "-";
return BATCH_DETAIL_CURRENCY_FORMATTER.format(cents / 100);
};
export { formatCentsToBRL };

export const formatCentsTotal = (
unitCents: number | null | undefined,
Expand Down
8 changes: 4 additions & 4 deletions app/(pages)/batches/[id]/edit/batches-edit.model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ describe("useBatchEditModel", () => {
expect(fakeApi.put).toHaveBeenCalledWith("batches/batch-1", {
json: values,
});
expect(fakeToast.success).toHaveBeenCalledWith("Batch atualizado");
expect(fakeToast.success).toHaveBeenCalledWith("Lote atualizado");
expect(fakeRouter.push).toHaveBeenCalledWith("/batches/batch-1");
});

Expand All @@ -258,11 +258,11 @@ describe("useBatchEditModel", () => {
await result.current.onSubmit(baseFormValues);
});

expect(fakeToast.error).toHaveBeenCalledWith("Erro ao atualizar batch");
expect(fakeToast.error).toHaveBeenCalledWith("Erro ao atualizar lote");
});

it("exibe mensagem de erro retornada pela API na atualização", async () => {
const updateError = new Error("Falha ao atualizar batch");
const updateError = new Error("Falha ao atualizar lote");
fakeApi.put.mockReturnValueOnce({
json: vi.fn(async () => {
throw updateError;
Expand All @@ -274,6 +274,6 @@ describe("useBatchEditModel", () => {
await result.current.onSubmit(baseFormValues);
});

expect(fakeToast.error).toHaveBeenCalledWith("Falha ao atualizar batch");
expect(fakeToast.error).toHaveBeenCalledWith("Falha ao atualizar lote");
});
});
4 changes: 2 additions & 2 deletions app/(pages)/batches/[id]/edit/batches-edit.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ export const useBatchEditModel = (batchId: string) => {
try {
const { api } = await import("@/lib/api");
await api.put(`batches/${batchId}`, { json: values }).json();
toast.success("Batch atualizado");
toast.success("Lote atualizado");
router.push(`/batches/${batchId}`);
} catch (err) {
const message = err instanceof Error ? err.message : "Erro ao atualizar batch";
const message = err instanceof Error ? err.message : "Erro ao atualizar lote";
toast.error(message);
}
};
Expand Down
2 changes: 1 addition & 1 deletion app/(pages)/batches/batches.view.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const baseProps = {
status: "all" as const,
lowStockThreshold: 10,
},
sortConfig: { key: "createdAt", direction: "desc" as const },
sortConfig: { key: "createdAt" as const, direction: "desc" as const },
isGroupedByProduct: false,
isMobileFiltersOpen: false,
mobileFiltersDraft: {
Expand Down
9 changes: 5 additions & 4 deletions app/(pages)/batches/create/batches-create.model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const fakeToast = vi.hoisted(() => {
class FakeBatchCreateToast {
public readonly success = vi.fn<(message: string) => void>();
public readonly error = vi.fn<(message: string) => void>();
public readonly warning = vi.fn<(message: string) => void>();
}

return new FakeBatchCreateToast();
Expand Down Expand Up @@ -543,8 +544,8 @@ describe("useBatchCreateModel", () => {
await result.current.onSubmit(validSubmitData);
});

expect(fakeToast.error).toHaveBeenCalledWith(
"Selecione um warehouse ativo para criar o batch",
expect(fakeToast.warning).toHaveBeenCalledWith(
"Selecione um estoque ativo para criar o lote",
);
expect(fakeApi.post).not.toHaveBeenCalled();
});
Expand Down Expand Up @@ -596,7 +597,7 @@ describe("useBatchCreateModel", () => {
expect(fakeApi.post).toHaveBeenCalledWith("batches", {
json: buildBatchPayload(validSubmitData, "wh-1"),
});
expect(fakeToast.success).toHaveBeenCalledWith("Batch criado com sucesso");
expect(fakeToast.success).toHaveBeenCalledWith("Lote criado com sucesso");
expect(fakeRouter.push).toHaveBeenCalledWith("/batches/batch-2026");
});

Expand Down Expand Up @@ -627,6 +628,6 @@ describe("useBatchCreateModel", () => {
await result.current.onSubmit(validSubmitData);
});

expect(fakeToast.error).toHaveBeenCalledWith("Erro ao criar batch");
expect(fakeToast.error).toHaveBeenCalledWith("Erro ao criar lote");
});
});
6 changes: 3 additions & 3 deletions app/(pages)/batches/create/batches-create.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export const useBatchCreateModel = () => {

const onSubmit = async (data: BatchCreateFormData) => {
if (!warehouseId) {
toast.error("Selecione um warehouse ativo para criar o batch");
toast.warning("Selecione um estoque ativo para criar o lote");
return;
}

Expand All @@ -320,11 +320,11 @@ export const useBatchCreateModel = () => {
.json<BatchCreateResponse>();

if (response.success) {
toast.success("Batch criado com sucesso");
toast.success("Lote criado com sucesso");
router.push(`/batches/${response.data.id}`);
}
} catch (err) {
const message = err instanceof Error ? err.message : "Erro ao criar batch";
const message = err instanceof Error ? err.message : "Erro ao criar lote";
toast.error(message);
}
};
Expand Down
4 changes: 3 additions & 1 deletion app/(pages)/batches/create/batches-create.schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const baseData = {
};

const expectErrorPath = (
result: { success: false; error: { issues: { path: (string | number)[]; message: string }[] } },
result: { success: false; error: { issues: { path: PropertyKey[]; message: string }[] } },
path: string,
): void => {
if (result.success) {
Expand Down Expand Up @@ -52,6 +52,7 @@ describe("batchCreateSchema", () => {
productId: "",
});
expect(result.success).toBe(false);
if (result.success) throw new Error("expected parse failure");
expectErrorPath(result, "productId");
});

Expand All @@ -61,6 +62,7 @@ describe("batchCreateSchema", () => {
quantity: 0,
});
expect(result.success).toBe(false);
if (result.success) throw new Error("expected parse failure");
expectErrorPath(result, "quantity");
});

Expand Down
6 changes: 5 additions & 1 deletion app/(pages)/batches/create/batches-create.view.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ const baseProps = {
selectedWarehouseId: "wh-1",
onQuantityIncrement: vi.fn(),
onQuantityDecrement: vi.fn(),
selectedProduct: { hasExpiration: false },
selectedProduct: { id: "prod-1", name: "Produto A", hasExpiration: false },
latestBatchPriceSuggestion: null,
isLatestBatchPriceLoading: false,
onApplyLatestCostPrice: vi.fn(),
onApplyLatestSellingPrice: vi.fn(),
};

const Wrapper = ({
Expand Down
2 changes: 1 addition & 1 deletion app/(pages)/categories/categories.model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const fakeSWR = vi.hoisted(() => {

public reset(): void {
this.responses.clear();
this.defaultState.mutate.mockClear();
vi.mocked(this.defaultState.mutate).mockClear();
this.hook.mockClear();
}
}
Expand Down
Loading
Loading