Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 19 additions & 3 deletions src/__tests__/components/uom-filters.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { describe, it, expect, vi } from "vitest"
import { render, screen, fireEvent } from "../utils"
import { UOMFilters } from "@/components/finance/uom/uom-filters"
import { UOMCategory, ActiveFilter } from "@/types/finance/uom"
import { ActiveFilter } from "@/types/finance/uom"

// Mock the UOM hooks
vi.mock("@/hooks/finance/use-uom", () => ({
Expand All @@ -12,12 +12,23 @@ vi.mock("@/hooks/finance/use-uom", () => ({
}),
}))

// Mock the UOM Category hooks
vi.mock("@/hooks/finance/use-uom-category", () => ({
useUOMCategories: () => ({
data: {
data: [
{ uomCategoryId: "cat-1", categoryCode: "WEIGHT", categoryName: "Weight" },
{ uomCategoryId: "cat-2", categoryCode: "LENGTH", categoryName: "Length" },
],
},
}),
}))

describe("UOMFilters Component", () => {
const defaultFilters = {
page: 1,
pageSize: 10,
search: "",
category: UOMCategory.UOM_CATEGORY_UNSPECIFIED,
activeFilter: ActiveFilter.ACTIVE_FILTER_UNSPECIFIED,
sortBy: "code",
sortOrder: "asc" as const,
Expand All @@ -35,19 +46,24 @@ describe("UOMFilters Component", () => {
expect(searchInput).toBeInTheDocument()
})

it("should call onFiltersChange when typing in search", () => {
it("should call onFiltersChange when typing in search", async () => {
vi.useFakeTimers()
const onFiltersChange = vi.fn()
render(<UOMFilters {...defaultProps} onFiltersChange={onFiltersChange} />)

const searchInput = screen.getByPlaceholderText(/search/i)
fireEvent.change(searchInput, { target: { value: "KG" } })

// Advance past debounce timer (300ms)
vi.advanceTimersByTime(350)

expect(onFiltersChange).toHaveBeenCalledWith(
expect.objectContaining({
search: "KG",
page: 1,
})
)
vi.useRealTimers()
})

it("should display current search value", () => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/hooks/use-uom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe("UOM Hooks", () => {

it("should return list keys with params", () => {
const params = { page: 1, pageSize: 10 }
expect(uomKeys.list(params)).toEqual(["finance", "uom", "list", params])
expect(uomKeys.list(params)).toEqual(["finance", "uom", "list", JSON.stringify(params)])
})

it("should return detail keys", () => {
Expand Down
14 changes: 14 additions & 0 deletions src/app/(dashboard)/finance/master/uom-category/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TableSkeleton } from "@/components/loading"

export default function UOMCategoryLoading() {
return (
<div className="space-y-6">
<div className="space-y-2">
<div className="h-4 w-48 bg-muted animate-pulse rounded" />
<div className="h-8 w-64 bg-muted animate-pulse rounded" />
<div className="h-4 w-96 bg-muted animate-pulse rounded" />
</div>
<TableSkeleton rows={8} />
</div>
)
}
8 changes: 8 additions & 0 deletions src/app/(dashboard)/finance/master/uom-category/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { generateMetadata as genMeta } from "@/config/site"
import UOMCategoryPageClient from "./uom-category-page-client"

export const metadata = genMeta("UOM Category")

export default function UOMCategoryPage() {
return <UOMCategoryPageClient />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
"use client"

import { useState, Suspense } from "react"
import { Plus, Download, Upload, Loader2 } from "lucide-react"

import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { PageHeader } from "@/components/common/page-header"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

import {
UOMCategoryFormDialog,
UOMCategoryDeleteDialog,
UOMCategoryImportDialog,
UOMCategoryFilters,
UOMCategoryTable,
UOMCategoryPagination,
} from "@/components/finance/uom-category"

import { useUOMCategories, useExportUOMCategories } from "@/hooks/finance/use-uom-category"
import { useUrlState } from "@/lib/hooks"
import {
type UOMCategory,
type ListUOMCategoriesParams,
ActiveFilter,
} from "@/types/finance/uom-category"

const defaultFilters: ListUOMCategoriesParams = {
page: 1,
pageSize: 10,
search: "",
activeFilter: ActiveFilter.ACTIVE_FILTER_UNSPECIFIED,
sortBy: "code",
sortOrder: "asc",
}

function UOMCategoryPageContent() {
const [filters, setFilters] = useUrlState<ListUOMCategoriesParams>({
defaultValues: defaultFilters,
})

const [isFormOpen, setIsFormOpen] = useState(false)
const [isDeleteOpen, setIsDeleteOpen] = useState(false)
const [isImportOpen, setIsImportOpen] = useState(false)
const [selectedUOMCategory, setSelectedUOMCategory] = useState<UOMCategory | null>(null)

const { data, isLoading, isError, error } = useUOMCategories(filters)
const exportMutation = useExportUOMCategories()

const handleAddNew = () => {
setSelectedUOMCategory(null)
setIsFormOpen(true)
}

const handleEdit = (uomCategory: UOMCategory) => {
setSelectedUOMCategory(uomCategory)
setIsFormOpen(true)
}

const handleDelete = (uomCategory: UOMCategory) => {
setSelectedUOMCategory(uomCategory)
setIsDeleteOpen(true)
}

const handleExport = async () => {
await exportMutation.mutateAsync({
activeFilter: filters.activeFilter,
})
}

const handlePageChange = (page: number) => {
setFilters((prev) => ({ ...prev, page }))
}

const handlePageSizeChange = (pageSize: number) => {
setFilters((prev) => ({ ...prev, pageSize, page: 1 }))
}

const totalItems = data?.pagination?.totalItems ?? 0

return (
<div>
<PageHeader
title="UOM Categories"
subtitle="Manage unit of measure categories"
>
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
{exportMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<Download className="mr-2 h-4 w-4" />
)}
Export/Import
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={handleExport}
disabled={exportMutation.isPending}
>
<Download className="mr-2 h-4 w-4" />
Export to Excel
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setIsImportOpen(true)}>
<Upload className="mr-2 h-4 w-4" />
Import from Excel
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

<Button onClick={handleAddNew}>
<Plus className="mr-2 h-4 w-4" />
Add UOM Category
</Button>
</div>
</PageHeader>

<Card>
<CardHeader>
<CardTitle>UOM Category List</CardTitle>
<CardDescription>
{isLoading
? "Loading..."
: `${totalItems} total UOM categories`}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<UOMCategoryFilters filters={filters} onFiltersChange={setFilters} />

{isError && (
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-center text-destructive">
{error instanceof Error
? error.message
: "Failed to load UOM categories"}
</div>
)}

<UOMCategoryTable
data={data?.data || []}
isLoading={isLoading}
onEdit={handleEdit}
onDelete={handleDelete}
/>

<UOMCategoryPagination
pagination={data?.pagination}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
/>
</CardContent>
</Card>

<UOMCategoryFormDialog
open={isFormOpen}
onOpenChange={setIsFormOpen}
uomCategory={selectedUOMCategory}
/>

<UOMCategoryDeleteDialog
open={isDeleteOpen}
onOpenChange={setIsDeleteOpen}
uomCategory={selectedUOMCategory}
/>

<UOMCategoryImportDialog open={isImportOpen} onOpenChange={setIsImportOpen} />
</div>
)
}

function UOMCategoryPageSkeleton() {
return (
<div>
<PageHeader
title="UOM Categories"
subtitle="Manage unit of measure categories"
>
<div className="flex items-center gap-2">
<Button variant="outline" disabled>
<Download className="mr-2 h-4 w-4" />
Export/Import
</Button>
<Button disabled>
<Plus className="mr-2 h-4 w-4" />
Add UOM Category
</Button>
</div>
</PageHeader>
<Card>
<CardHeader>
<CardTitle>UOM Category List</CardTitle>
<CardDescription>Loading...</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center py-8">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
</CardContent>
</Card>
</div>
)
}

export default function UOMCategoryPageClient() {
return (
<Suspense fallback={<UOMCategoryPageSkeleton />}>
<UOMCategoryPageContent />
</Suspense>
)
}
4 changes: 1 addition & 3 deletions src/app/(dashboard)/finance/master/uom/uom-page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import { useUrlState } from "@/lib/hooks"
import {
type UOM,
type ListUOMsParams,
UOMCategory,
ActiveFilter,
} from "@/types/finance/uom"

Expand All @@ -42,7 +41,6 @@ const defaultFilters: ListUOMsParams = {
page: 1,
pageSize: 10,
search: "",
category: UOMCategory.UOM_CATEGORY_UNSPECIFIED,
activeFilter: ActiveFilter.ACTIVE_FILTER_UNSPECIFIED,
sortBy: "code",
sortOrder: "asc",
Expand Down Expand Up @@ -82,7 +80,7 @@ function UOMPageContent() {

const handleExport = async () => {
await exportMutation.mutateAsync({
category: filters.category,
uomCategoryId: filters.uomCategoryId,
activeFilter: filters.activeFilter,
})
}
Expand Down
Loading
Loading