251225:1703 On going update to 1.7.0: Refoctory drawing Module not finish
This commit is contained in:
282
frontend/app/(admin)/admin/drawings/contract/categories/page.tsx
Normal file
282
frontend/app/(admin)/admin/drawings/contract/categories/page.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { GenericCrudTable } from "@/components/admin/reference/generic-crud-table";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useProjects } from "@/hooks/use-master-data";
|
||||
import { drawingMasterDataService, ContractCategory, ContractSubCategory } from "@/lib/services/drawing-master-data.service";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface Category {
|
||||
id: number;
|
||||
catCode: string;
|
||||
catName: string;
|
||||
description?: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export default function ContractCategoriesPage() {
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<number | undefined>(undefined);
|
||||
const { data: projects = [], isLoading: isLoadingProjects } = useProjects();
|
||||
|
||||
const columns: ColumnDef<Category>[] = [
|
||||
{
|
||||
accessorKey: "catCode",
|
||||
header: "Code",
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{row.getValue("catCode")}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "catName",
|
||||
header: "Category Name",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
cell: ({ row }) => (
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{row.getValue("description") || "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "sortOrder",
|
||||
header: "Order",
|
||||
cell: ({ row }) => (
|
||||
<span className="font-mono">{row.getValue("sortOrder")}</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const projectFilter = (
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm font-medium">Project:</span>
|
||||
<Select
|
||||
value={selectedProjectId?.toString() ?? ""}
|
||||
onValueChange={(v) => setSelectedProjectId(v ? parseInt(v) : undefined)}
|
||||
>
|
||||
<SelectTrigger className="w-[300px]">
|
||||
{isLoadingProjects ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<SelectValue placeholder="Select Project" />
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{projects.map((project: { id: number; projectName: string; projectCode: string }) => (
|
||||
<SelectItem key={project.id} value={String(project.id)}>
|
||||
{project.projectCode} - {project.projectName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!selectedProjectId) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Contract Drawing Categories</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Manage main categories (หมวดหมู่หลัก) for contract drawings
|
||||
</p>
|
||||
</div>
|
||||
{projectFilter}
|
||||
<div className="text-center py-12 text-muted-foreground border rounded-lg border-dashed">
|
||||
Please select a project to manage categories.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<GenericCrudTable
|
||||
entityName="Category"
|
||||
title="Contract Drawing Categories"
|
||||
description="Manage main categories (หมวดหมู่หลัก) for contract drawings"
|
||||
queryKey={["contract-drawing-categories", String(selectedProjectId)]}
|
||||
fetchFn={() => drawingMasterDataService.getContractCategories(selectedProjectId)}
|
||||
createFn={(data) => drawingMasterDataService.createContractCategory({ ...data, projectId: selectedProjectId })}
|
||||
updateFn={(id, data) => drawingMasterDataService.updateContractCategory(id, data)}
|
||||
deleteFn={(id) => drawingMasterDataService.deleteContractCategory(id)}
|
||||
columns={columns}
|
||||
fields={[
|
||||
{ name: "catCode", label: "Category Code", type: "text", required: true },
|
||||
{ name: "catName", label: "Category Name", type: "text", required: true },
|
||||
{ name: "description", label: "Description", type: "textarea" },
|
||||
{ name: "sortOrder", label: "Sort Order", type: "text", required: true },
|
||||
]}
|
||||
filters={projectFilter}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Note: For mapping, we should ideally have a separate "Mappings" column or action button.
|
||||
Since GenericCrudTable might not support custom action columns easily without modification,
|
||||
we are currently just listing categories. To add mapping functionality, we might need
|
||||
to either extend GenericCrudTable or create a dedicated page for mappings.
|
||||
|
||||
Given the constraints, I will add a "Mapped Sub-categories" management section
|
||||
that opens when clicking a category ROW or adding a custom action if GenericCrudTable supports it.
|
||||
For now, let's assume we need to extend GenericCrudTable or replace it to support this specific requirement.
|
||||
|
||||
However, to keep it simple and consistent:
|
||||
Let's add a separate section below the table or a dialog triggered by a custom cell.
|
||||
*/}
|
||||
<div className="mt-8 border-t pt-8">
|
||||
<CategoryMappingSection projectId={selectedProjectId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CategoryMappingSection({ projectId }: { projectId: number }) {
|
||||
|
||||
// ... logic to manage mappings would go here ...
|
||||
// But to properly implement this, we need a full mapping UI.
|
||||
// Let's defer this implementation pattern to a separate component to keep this file clean
|
||||
// and just mount it here.
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xl font-semibold">Category Mappings (Map Sub-categories to Categories)</h2>
|
||||
<div className="bg-muted/30 p-4 rounded-lg border-dashed border">
|
||||
<p className="text-sm text-muted-foreground">Select a category to view and manage its sub-categories.</p>
|
||||
{/*
|
||||
Real implementation would be complex here.
|
||||
Better approach: Add a "Manage Sub-categories" button to the Categories table if possible.
|
||||
Or simpler: A separate "Mapping" page.
|
||||
*/}
|
||||
<ManageMappings projectId={projectId} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner"; // Use sonner instead of use-toast
|
||||
|
||||
function ManageMappings({ projectId }: { projectId: number }) {
|
||||
const queryClient = useQueryClient();
|
||||
const [selectedCat, setSelectedCat] = useState<string>("");
|
||||
const [selectedSubCat, setSelectedSubCat] = useState<string>("");
|
||||
|
||||
const { data: categories = [] } = useQuery({
|
||||
queryKey: ["contract-categories", String(projectId)],
|
||||
queryFn: () => drawingMasterDataService.getContractCategories(projectId),
|
||||
});
|
||||
|
||||
const { data: subCategories = [] } = useQuery({
|
||||
queryKey: ["contract-sub-categories", String(projectId)],
|
||||
queryFn: () => drawingMasterDataService.getContractSubCategories(projectId),
|
||||
});
|
||||
|
||||
const { data: mappings = [] } = useQuery({
|
||||
queryKey: ["contract-mappings", String(projectId), selectedCat],
|
||||
queryFn: () => drawingMasterDataService.getContractMappings(projectId, selectedCat ? parseInt(selectedCat) : undefined),
|
||||
enabled: !!selectedCat,
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: drawingMasterDataService.createContractMapping,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["contract-mappings"] });
|
||||
toast.success("Mapping created");
|
||||
setSelectedSubCat("");
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: drawingMasterDataService.deleteContractMapping,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["contract-mappings"] });
|
||||
toast.success("Mapping removed");
|
||||
}
|
||||
});
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!selectedCat || !selectedSubCat) return;
|
||||
createMutation.mutate({
|
||||
projectId,
|
||||
categoryId: parseInt(selectedCat),
|
||||
subCategoryId: parseInt(selectedSubCat),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Select Category</label>
|
||||
<Select value={selectedCat} onValueChange={setSelectedCat}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select Category..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((c: ContractCategory) => (
|
||||
<SelectItem key={c.id} value={String(c.id)}>{c.catCode} - {c.catName}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{selectedCat && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2 items-end">
|
||||
<div className="flex-1 space-y-2">
|
||||
<label className="text-sm font-medium">Add Sub-Category</label>
|
||||
<Select value={selectedSubCat} onValueChange={setSelectedSubCat}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select Sub-Category to add..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{subCategories
|
||||
.filter((s: ContractSubCategory) => !mappings.find((m: { subCategory: { id: number } }) => m.subCategory.id === s.id))
|
||||
.map((s: ContractSubCategory) => (
|
||||
<SelectItem key={s.id} value={String(s.id)}>{s.subCatCode} - {s.subCatName}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Button onClick={handleAdd} disabled={!selectedSubCat || createMutation.isPending}>
|
||||
<Plus className="h-4 w-4 mr-2" /> Add
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-md">
|
||||
<div className="p-2 bg-muted/50 font-medium text-sm grid grid-cols-[1fr,auto] gap-2">
|
||||
<span>Mapped Sub-Categories</span>
|
||||
<span>Action</span>
|
||||
</div>
|
||||
{mappings.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">No sub-categories mapped yet.</div>
|
||||
) : (
|
||||
<div className="divide-y">
|
||||
{mappings.map((m: { id: number; subCategory: ContractSubCategory }) => (
|
||||
<div key={m.id} className="p-2 grid grid-cols-[1fr,auto] gap-2 items-center">
|
||||
<span className="text-sm">{m.subCategory.subCatCode} - {m.subCategory.subCatName}</span>
|
||||
<Button variant="ghost" size="sm" onClick={() => deleteMutation.mutate(m.id)} disabled={deleteMutation.isPending}>
|
||||
<Trash2 className="h-4 w-4 text-red-500" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { GenericCrudTable } from "@/components/admin/reference/generic-crud-table";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useProjects } from "@/hooks/use-master-data";
|
||||
import { drawingMasterDataService } from "@/lib/services/drawing-master-data.service";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface SubCategory {
|
||||
id: number;
|
||||
subCatCode: string;
|
||||
subCatName: string;
|
||||
description?: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export default function ContractSubCategoriesPage() {
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<number | undefined>(undefined);
|
||||
const { data: projects = [], isLoading: isLoadingProjects } = useProjects();
|
||||
|
||||
const columns: ColumnDef<SubCategory>[] = [
|
||||
{
|
||||
accessorKey: "subCatCode",
|
||||
header: "Code",
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{row.getValue("subCatCode")}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "subCatName",
|
||||
header: "Sub-category Name",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
cell: ({ row }) => (
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{row.getValue("description") || "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "sortOrder",
|
||||
header: "Order",
|
||||
cell: ({ row }) => (
|
||||
<span className="font-mono">{row.getValue("sortOrder")}</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const projectFilter = (
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm font-medium">Project:</span>
|
||||
<Select
|
||||
value={selectedProjectId?.toString() ?? ""}
|
||||
onValueChange={(v) => setSelectedProjectId(v ? parseInt(v) : undefined)}
|
||||
>
|
||||
<SelectTrigger className="w-[300px]">
|
||||
{isLoadingProjects ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<SelectValue placeholder="Select Project" />
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{projects.map((project: { id: number; projectName: string; projectCode: string }) => (
|
||||
<SelectItem key={project.id} value={String(project.id)}>
|
||||
{project.projectCode} - {project.projectName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!selectedProjectId) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Contract Drawing Sub-categories</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Manage sub-categories (หมวดหมู่ย่อย) for contract drawings
|
||||
</p>
|
||||
</div>
|
||||
{projectFilter}
|
||||
<div className="text-center py-12 text-muted-foreground border rounded-lg border-dashed">
|
||||
Please select a project to manage sub-categories.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<GenericCrudTable
|
||||
entityName="Sub-category"
|
||||
title="Contract Drawing Sub-categories"
|
||||
description="Manage sub-categories (หมวดหมู่ย่อย) for contract drawings"
|
||||
queryKey={["contract-drawing-sub-categories", String(selectedProjectId)]}
|
||||
fetchFn={() => drawingMasterDataService.getContractSubCategories(selectedProjectId)}
|
||||
createFn={(data) => drawingMasterDataService.createContractSubCategory({ ...data, projectId: selectedProjectId })}
|
||||
updateFn={(id, data) => drawingMasterDataService.updateContractSubCategory(id, data)}
|
||||
deleteFn={(id) => drawingMasterDataService.deleteContractSubCategory(id)}
|
||||
columns={columns}
|
||||
fields={[
|
||||
{ name: "subCatCode", label: "Sub-category Code", type: "text", required: true },
|
||||
{ name: "subCatName", label: "Sub-category Name", type: "text", required: true },
|
||||
{ name: "description", label: "Description", type: "textarea" },
|
||||
{ name: "sortOrder", label: "Sort Order", type: "text", required: true },
|
||||
]}
|
||||
filters={projectFilter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
126
frontend/app/(admin)/admin/drawings/contract/volumes/page.tsx
Normal file
126
frontend/app/(admin)/admin/drawings/contract/volumes/page.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { GenericCrudTable } from "@/components/admin/reference/generic-crud-table";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useProjects } from "@/hooks/use-master-data";
|
||||
import { drawingMasterDataService } from "@/lib/services/drawing-master-data.service";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface Volume {
|
||||
id: number;
|
||||
volumeCode: string;
|
||||
volumeName: string;
|
||||
description?: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export default function ContractVolumesPage() {
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<number | undefined>(undefined);
|
||||
const { data: projects = [], isLoading: isLoadingProjects } = useProjects();
|
||||
|
||||
const columns: ColumnDef<Volume>[] = [
|
||||
{
|
||||
accessorKey: "volumeCode",
|
||||
header: "Code",
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{row.getValue("volumeCode")}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "volumeName",
|
||||
header: "Volume Name",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
cell: ({ row }) => (
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{row.getValue("description") || "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "sortOrder",
|
||||
header: "Order",
|
||||
cell: ({ row }) => (
|
||||
<span className="font-mono">{row.getValue("sortOrder")}</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const projectFilter = (
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm font-medium">Project:</span>
|
||||
<Select
|
||||
value={selectedProjectId?.toString() ?? ""}
|
||||
onValueChange={(v) => setSelectedProjectId(v ? parseInt(v) : undefined)}
|
||||
>
|
||||
<SelectTrigger className="w-[300px]">
|
||||
{isLoadingProjects ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<SelectValue placeholder="Select Project" />
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{projects.map((project: { id: number; projectName: string; projectCode: string }) => (
|
||||
<SelectItem key={project.id} value={String(project.id)}>
|
||||
{project.projectCode} - {project.projectName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!selectedProjectId) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Contract Drawing Volumes</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Manage drawing volumes (เล่ม) for contract drawings
|
||||
</p>
|
||||
</div>
|
||||
{projectFilter}
|
||||
<div className="text-center py-12 text-muted-foreground border rounded-lg border-dashed">
|
||||
Please select a project to manage volumes.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<GenericCrudTable
|
||||
entityName="Volume"
|
||||
title="Contract Drawing Volumes"
|
||||
description="Manage drawing volumes (เล่ม) for contract drawings"
|
||||
queryKey={["contract-drawing-volumes", String(selectedProjectId)]}
|
||||
fetchFn={() => drawingMasterDataService.getContractVolumes(selectedProjectId)}
|
||||
createFn={(data) => drawingMasterDataService.createContractVolume({ ...data, projectId: selectedProjectId })}
|
||||
updateFn={(id, data) => drawingMasterDataService.updateContractVolume(id, data)}
|
||||
deleteFn={(id) => drawingMasterDataService.deleteContractVolume(id)}
|
||||
columns={columns}
|
||||
fields={[
|
||||
{ name: "volumeCode", label: "Volume Code", type: "text", required: true },
|
||||
{ name: "volumeName", label: "Volume Name", type: "text", required: true },
|
||||
{ name: "description", label: "Description", type: "textarea" },
|
||||
{ name: "sortOrder", label: "Sort Order", type: "text", required: true },
|
||||
]}
|
||||
filters={projectFilter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
114
frontend/app/(admin)/admin/drawings/page.tsx
Normal file
114
frontend/app/(admin)/admin/drawings/page.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
FileStack,
|
||||
FolderTree,
|
||||
Layers,
|
||||
BookOpen,
|
||||
FileBox
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
const contractDrawingMenu = [
|
||||
{
|
||||
title: "Volumes",
|
||||
description: "Manage contract drawing volumes (เล่ม)",
|
||||
href: "/admin/drawings/contract/volumes",
|
||||
icon: BookOpen,
|
||||
},
|
||||
{
|
||||
title: "Categories",
|
||||
description: "Manage main categories (หมวดหมู่หลัก)",
|
||||
href: "/admin/drawings/contract/categories",
|
||||
icon: FolderTree,
|
||||
},
|
||||
{
|
||||
title: "Sub-categories",
|
||||
description: "Manage sub-categories (หมวดหมู่ย่อย)",
|
||||
href: "/admin/drawings/contract/sub-categories",
|
||||
icon: Layers,
|
||||
},
|
||||
];
|
||||
|
||||
const shopDrawingMenu = [
|
||||
{
|
||||
title: "Main Categories",
|
||||
description: "Manage main categories (หมวดหมู่หลัก)",
|
||||
href: "/admin/drawings/shop/main-categories",
|
||||
icon: FolderTree,
|
||||
},
|
||||
{
|
||||
title: "Sub-categories",
|
||||
description: "Manage sub-categories (หมวดหมู่ย่อย)",
|
||||
href: "/admin/drawings/shop/sub-categories",
|
||||
icon: Layers,
|
||||
},
|
||||
];
|
||||
|
||||
export default function DrawingsAdminPage() {
|
||||
return (
|
||||
<div className="p-6 space-y-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Drawing Master Data</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Manage categories and volumes for Contract and Shop Drawings
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Contract Drawings Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileStack className="h-5 w-5 text-blue-600" />
|
||||
<h2 className="text-lg font-semibold">Contract Drawings</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{contractDrawingMenu.map((item) => (
|
||||
<Link key={item.href} href={item.href}>
|
||||
<Card className="hover:shadow-md transition-shadow cursor-pointer h-full border-blue-200 hover:border-blue-400">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
{item.title}
|
||||
</CardTitle>
|
||||
<item.icon className="h-4 w-4 text-blue-600" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{item.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Shop Drawings Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileBox className="h-5 w-5 text-green-600" />
|
||||
<h2 className="text-lg font-semibold">Shop Drawings / As Built</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{shopDrawingMenu.map((item) => (
|
||||
<Link key={item.href} href={item.href}>
|
||||
<Card className="hover:shadow-md transition-shadow cursor-pointer h-full border-green-200 hover:border-green-400">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
{item.title}
|
||||
</CardTitle>
|
||||
<item.icon className="h-4 w-4 text-green-600" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{item.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { GenericCrudTable } from "@/components/admin/reference/generic-crud-table";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Loader2, CheckCircle, XCircle } from "lucide-react";
|
||||
import { useProjects } from "@/hooks/use-master-data";
|
||||
import { drawingMasterDataService } from "@/lib/services/drawing-master-data.service";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface MainCategory {
|
||||
id: number;
|
||||
mainCategoryCode: string;
|
||||
mainCategoryName: string;
|
||||
description?: string;
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export default function ShopMainCategoriesPage() {
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<number | undefined>(undefined);
|
||||
const { data: projects = [], isLoading: isLoadingProjects } = useProjects();
|
||||
|
||||
const columns: ColumnDef<MainCategory>[] = [
|
||||
{
|
||||
accessorKey: "mainCategoryCode",
|
||||
header: "Code",
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{row.getValue("mainCategoryCode")}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "mainCategoryName",
|
||||
header: "Category Name",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
cell: ({ row }) => (
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{row.getValue("description") || "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "isActive",
|
||||
header: "Active",
|
||||
cell: ({ row }) => (
|
||||
row.getValue("isActive") ? (
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
) : (
|
||||
<XCircle className="h-4 w-4 text-red-600" />
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "sortOrder",
|
||||
header: "Order",
|
||||
cell: ({ row }) => (
|
||||
<span className="font-mono">{row.getValue("sortOrder")}</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const projectFilter = (
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm font-medium">Project:</span>
|
||||
<Select
|
||||
value={selectedProjectId?.toString() ?? ""}
|
||||
onValueChange={(v) => setSelectedProjectId(v ? parseInt(v) : undefined)}
|
||||
>
|
||||
<SelectTrigger className="w-[300px]">
|
||||
{isLoadingProjects ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<SelectValue placeholder="Select Project" />
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{projects.map((project: { id: number; projectName: string; projectCode: string }) => (
|
||||
<SelectItem key={project.id} value={String(project.id)}>
|
||||
{project.projectCode} - {project.projectName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!selectedProjectId) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Shop Drawing Main Categories</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Manage main categories (หมวดหมู่หลัก) for shop drawings
|
||||
</p>
|
||||
</div>
|
||||
{projectFilter}
|
||||
<div className="text-center py-12 text-muted-foreground border rounded-lg border-dashed">
|
||||
Please select a project to manage main categories.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<GenericCrudTable
|
||||
entityName="Main Category"
|
||||
title="Shop Drawing Main Categories"
|
||||
description="Manage main categories (หมวดหมู่หลัก) for shop drawings"
|
||||
queryKey={["shop-drawing-main-categories", String(selectedProjectId)]}
|
||||
fetchFn={() => drawingMasterDataService.getShopMainCategories(selectedProjectId)}
|
||||
createFn={(data) => drawingMasterDataService.createShopMainCategory({
|
||||
...data,
|
||||
projectId: selectedProjectId,
|
||||
isActive: data.isActive === "true" || data.isActive === true
|
||||
})}
|
||||
updateFn={(id, data) => drawingMasterDataService.updateShopMainCategory(id, {
|
||||
...data,
|
||||
isActive: data.isActive === "true" || data.isActive === true
|
||||
})}
|
||||
deleteFn={(id) => drawingMasterDataService.deleteShopMainCategory(id)}
|
||||
columns={columns}
|
||||
fields={[
|
||||
{ name: "mainCategoryCode", label: "Category Code", type: "text", required: true },
|
||||
{ name: "mainCategoryName", label: "Category Name", type: "text", required: true },
|
||||
{ name: "description", label: "Description", type: "textarea" },
|
||||
{ name: "isActive", label: "Active", type: "checkbox" },
|
||||
{ name: "sortOrder", label: "Sort Order", type: "text", required: true },
|
||||
]}
|
||||
filters={projectFilter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
146
frontend/app/(admin)/admin/drawings/shop/sub-categories/page.tsx
Normal file
146
frontend/app/(admin)/admin/drawings/shop/sub-categories/page.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { GenericCrudTable } from "@/components/admin/reference/generic-crud-table";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Loader2, CheckCircle, XCircle } from "lucide-react";
|
||||
import { useProjects } from "@/hooks/use-master-data";
|
||||
import { drawingMasterDataService } from "@/lib/services/drawing-master-data.service";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface SubCategory {
|
||||
id: number;
|
||||
subCategoryCode: string;
|
||||
subCategoryName: string;
|
||||
description?: string;
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export default function ShopSubCategoriesPage() {
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<number | undefined>(undefined);
|
||||
const { data: projects = [], isLoading: isLoadingProjects } = useProjects();
|
||||
|
||||
const columns: ColumnDef<SubCategory>[] = [
|
||||
{
|
||||
accessorKey: "subCategoryCode",
|
||||
header: "Code",
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{row.getValue("subCategoryCode")}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "subCategoryName",
|
||||
header: "Sub-category Name",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
cell: ({ row }) => (
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{row.getValue("description") || "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "isActive",
|
||||
header: "Active",
|
||||
cell: ({ row }) => (
|
||||
row.getValue("isActive") ? (
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
) : (
|
||||
<XCircle className="h-4 w-4 text-red-600" />
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "sortOrder",
|
||||
header: "Order",
|
||||
cell: ({ row }) => (
|
||||
<span className="font-mono">{row.getValue("sortOrder")}</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const projectFilter = (
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm font-medium">Project:</span>
|
||||
<Select
|
||||
value={selectedProjectId?.toString() ?? ""}
|
||||
onValueChange={(v) => setSelectedProjectId(v ? parseInt(v) : undefined)}
|
||||
>
|
||||
<SelectTrigger className="w-[300px]">
|
||||
{isLoadingProjects ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<SelectValue placeholder="Select Project" />
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{projects.map((project: { id: number; projectName: string; projectCode: string }) => (
|
||||
<SelectItem key={project.id} value={String(project.id)}>
|
||||
{project.projectCode} - {project.projectName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!selectedProjectId) {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Shop Drawing Sub-categories</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Manage sub-categories (หมวดหมู่ย่อย) for shop drawings
|
||||
</p>
|
||||
</div>
|
||||
{projectFilter}
|
||||
<div className="text-center py-12 text-muted-foreground border rounded-lg border-dashed">
|
||||
Please select a project to manage sub-categories.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<GenericCrudTable
|
||||
entityName="Sub-category"
|
||||
title="Shop Drawing Sub-categories"
|
||||
description="Manage sub-categories (หมวดหมู่ย่อย) for shop drawings"
|
||||
queryKey={["shop-drawing-sub-categories", String(selectedProjectId)]}
|
||||
fetchFn={() => drawingMasterDataService.getShopSubCategories(selectedProjectId)}
|
||||
createFn={(data) => drawingMasterDataService.createShopSubCategory({
|
||||
...data,
|
||||
projectId: selectedProjectId,
|
||||
isActive: data.isActive === "true" || data.isActive === true
|
||||
})}
|
||||
updateFn={(id, data) => drawingMasterDataService.updateShopSubCategory(id, {
|
||||
...data,
|
||||
isActive: data.isActive === "true" || data.isActive === true
|
||||
})}
|
||||
deleteFn={(id) => drawingMasterDataService.deleteShopSubCategory(id)}
|
||||
columns={columns}
|
||||
fields={[
|
||||
{ name: "subCategoryCode", label: "Sub-category Code", type: "text", required: true },
|
||||
{ name: "subCategoryName", label: "Sub-category Name", type: "text", required: true },
|
||||
{ name: "description", label: "Description", type: "textarea" },
|
||||
{ name: "isActive", label: "Active", type: "checkbox" },
|
||||
{ name: "sortOrder", label: "Sort Order", type: "text", required: true },
|
||||
]}
|
||||
filters={projectFilter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Shield,
|
||||
Activity,
|
||||
ArrowRight,
|
||||
FileStack,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
@@ -78,6 +79,12 @@ export default function AdminPage() {
|
||||
href: "/admin/numbering",
|
||||
icon: Settings,
|
||||
},
|
||||
{
|
||||
title: "Drawing Master Data",
|
||||
description: "Manage drawing categories, volumes, and classifications",
|
||||
href: "/admin/drawings",
|
||||
icon: FileStack,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user