This commit is contained in:
@@ -45,7 +45,7 @@ export default function DisciplinesPage() {
|
||||
|
||||
const contractOptions = contracts.map((c: any) => ({
|
||||
label: `${c.contractName} (${c.contractCode})`,
|
||||
value: String(c.id || c.uuid),
|
||||
value: String(c.id),
|
||||
}));
|
||||
|
||||
return (
|
||||
@@ -55,9 +55,16 @@ export default function DisciplinesPage() {
|
||||
title="Disciplines Management"
|
||||
description="Manage system disciplines (e.g., ARCH, STR, MEC)"
|
||||
queryKey={['disciplines', selectedContractId ?? 'all']}
|
||||
fetchFn={() => masterDataService.getDisciplines(selectedContractId ? selectedContractId : undefined)}
|
||||
fetchFn={async () => {
|
||||
const items = await masterDataService.getDisciplines(selectedContractId ? selectedContractId : undefined);
|
||||
// ADR-019: Map contractId INT → contract UUID for edit mode select matching
|
||||
return (items as any[]).map((item: any) => ({
|
||||
...item,
|
||||
contractId: item.contract?.id || item.contract?.uuid || String(item.contractId),
|
||||
}));
|
||||
}}
|
||||
createFn={(data: Record<string, unknown>) => masterDataService.createDiscipline(data as any)}
|
||||
updateFn={(id, data) => Promise.reject('Not implemented yet')}
|
||||
updateFn={(id, data) => Promise.reject('Not implemented yet')}
|
||||
deleteFn={(id) => masterDataService.deleteDiscipline(id)}
|
||||
columns={columns}
|
||||
filters={
|
||||
@@ -72,7 +79,7 @@ export default function DisciplinesPage() {
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Contracts</SelectItem>
|
||||
{contracts.map((c: any) => (
|
||||
<SelectItem key={c.uuid || c.id} value={String(c.id || c.uuid)}>
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.contractName} ({c.contractCode})
|
||||
</SelectItem>
|
||||
))}
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function RfaTypesPage() {
|
||||
|
||||
const contractOptions = contracts.map((c: any) => ({
|
||||
label: `${c.contractName} (${c.contractCode})`,
|
||||
value: String(c.id || c.uuid),
|
||||
value: String(c.id),
|
||||
}));
|
||||
|
||||
return (
|
||||
@@ -58,7 +58,14 @@ export default function RfaTypesPage() {
|
||||
entityName="RFA Type"
|
||||
title="RFA Types Management"
|
||||
queryKey={['rfa-types', selectedContractId ?? 'all']}
|
||||
fetchFn={() => masterDataService.getRfaTypes(selectedContractId ? selectedContractId : undefined)}
|
||||
fetchFn={async () => {
|
||||
const items = await masterDataService.getRfaTypes(selectedContractId ? selectedContractId : undefined);
|
||||
// ADR-019: Map contractId INT → contract UUID for edit mode select matching
|
||||
return (items as any[]).map((item: any) => ({
|
||||
...item,
|
||||
contractId: item.contract?.id || item.contract?.uuid || String(item.contractId),
|
||||
}));
|
||||
}}
|
||||
createFn={(data: Record<string, unknown>) => masterDataService.createRfaType(data as any)}
|
||||
updateFn={(id, data) => masterDataService.updateRfaType(id, data)}
|
||||
deleteFn={(id) => masterDataService.deleteRfaType(id)}
|
||||
@@ -75,7 +82,7 @@ export default function RfaTypesPage() {
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Contracts</SelectItem>
|
||||
{contracts.map((c: any) => (
|
||||
<SelectItem key={c.uuid || c.id} value={String(c.id || c.uuid)}>
|
||||
<SelectItem key={c.id} value={String(c.id)}>
|
||||
{c.contractName} ({c.contractCode})
|
||||
</SelectItem>
|
||||
))}
|
||||
|
||||
@@ -16,8 +16,8 @@ export default function TagsPage() {
|
||||
const projectOptions = [
|
||||
{ label: "Global (All Projects)", value: "__none__" },
|
||||
...(projectsData || []).map((p: Record<string, unknown>) => ({
|
||||
label: p.projectName || p.projectCode || p.project_name || p.project_code || `Project ${p.id}`,
|
||||
value: String(p.id),
|
||||
label: (p.projectName || p.projectCode || p.project_name || p.project_code || `Project ${p.id}`) as string,
|
||||
value: String(p.id), // p.id = UUID string via serialization
|
||||
})),
|
||||
];
|
||||
|
||||
@@ -26,10 +26,10 @@ export default function TagsPage() {
|
||||
accessorKey: "project_id",
|
||||
header: "Project",
|
||||
cell: ({ row }) => {
|
||||
const pId = row.original.project_id;
|
||||
if (!pId) return <span className="text-muted-foreground italic">Global</span>;
|
||||
const p = (projectsData || []).find((proj: Record<string, unknown>) => proj.id === pId);
|
||||
return p ? (p.projectName || p.projectCode || p.project_name || p.project_code || `Project ${pId}`) as React.ReactNode : pId as React.ReactNode;
|
||||
const item = row.original as Record<string, unknown>;
|
||||
const project = item.project as Record<string, unknown> | null;
|
||||
if (!project) return <span className="text-muted-foreground italic">Global</span>;
|
||||
return (project.projectName || project.projectCode || `Project ${project.id}`) as React.ReactNode;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -70,7 +70,14 @@ export default function TagsPage() {
|
||||
description="Manage system tags, multi-tenant capable."
|
||||
entityName="Tag"
|
||||
queryKey={["tags"]}
|
||||
fetchFn={() => masterDataService.getTags()}
|
||||
fetchFn={async () => {
|
||||
const items = await masterDataService.getTags();
|
||||
// ADR-019: Map project_id INT → project UUID for edit mode select matching
|
||||
return (items as any[]).map((item: any) => ({
|
||||
...item,
|
||||
project_id: item.project?.id || item.project?.uuid || (item.project_id ? String(item.project_id) : null),
|
||||
}));
|
||||
}}
|
||||
createFn={(data: Record<string, unknown>) => masterDataService.createTag(formatPayload(data) as unknown as CreateTagDto)}
|
||||
updateFn={(id, data) => masterDataService.updateTag(id, formatPayload(data))}
|
||||
deleteFn={(id) => masterDataService.deleteTag(id)}
|
||||
|
||||
@@ -45,9 +45,9 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
// Form validation schema
|
||||
const formSchema = z.object({
|
||||
correspondenceId: z.number(),
|
||||
correspondenceId: z.string().min(1, "Please select a document"),
|
||||
subject: z.string().min(1, "Subject is required"),
|
||||
assigneeIds: z.array(z.number()).min(1, "At least one assignee is required"),
|
||||
assigneeIds: z.array(z.string()).min(1, "At least one assignee is required"),
|
||||
remarks: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -98,18 +98,18 @@ export default function CreateCirculationPage() {
|
||||
const selectedDocId = form.watch("correspondenceId");
|
||||
|
||||
const selectedDoc = correspondences?.data?.find(
|
||||
(c: { id: number }) => c.id === selectedDocId
|
||||
(c: { uuid: string }) => c.uuid === selectedDocId
|
||||
);
|
||||
|
||||
const toggleAssignee = (userId: number) => {
|
||||
const toggleAssignee = (userUuid: string) => {
|
||||
const current = form.getValues("assigneeIds");
|
||||
if (current.includes(userId)) {
|
||||
if (current.includes(userUuid)) {
|
||||
form.setValue(
|
||||
"assigneeIds",
|
||||
current.filter((id) => id !== userId)
|
||||
current.filter((id) => id !== userUuid)
|
||||
);
|
||||
} else {
|
||||
form.setValue("assigneeIds", [...current, userId]);
|
||||
form.setValue("assigneeIds", [...current, userUuid]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -168,19 +168,19 @@ export default function CreateCirculationPage() {
|
||||
<CommandList>
|
||||
<CommandEmpty>No document found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{correspondences?.data?.map((doc: { id: number; correspondenceNumber: string }) => (
|
||||
{correspondences?.data?.map((doc: { uuid: string; correspondenceNumber: string }) => (
|
||||
<CommandItem
|
||||
key={doc.id}
|
||||
key={doc.uuid}
|
||||
value={doc.correspondenceNumber}
|
||||
onSelect={() => {
|
||||
form.setValue("correspondenceId", doc.id);
|
||||
form.setValue("correspondenceId", doc.uuid);
|
||||
setDocOpen(false);
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
doc.id === field.value
|
||||
doc.uuid === field.value
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
@@ -230,13 +230,13 @@ export default function CreateCirculationPage() {
|
||||
>
|
||||
{selectedAssignees.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{selectedAssignees.map((userId) => {
|
||||
{selectedAssignees.map((userUuid) => {
|
||||
const user = users.find(
|
||||
(u) => u.userId === userId
|
||||
(u) => u.uuid === userUuid
|
||||
);
|
||||
return user ? (
|
||||
<Badge
|
||||
key={userId}
|
||||
key={userUuid}
|
||||
variant="secondary"
|
||||
className="mr-1"
|
||||
>
|
||||
@@ -245,7 +245,7 @@ export default function CreateCirculationPage() {
|
||||
className="ml-1 h-3 w-3 cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleAssignee(userId);
|
||||
toggleAssignee(userUuid);
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
@@ -269,14 +269,14 @@ export default function CreateCirculationPage() {
|
||||
<CommandGroup>
|
||||
{users.map((user) => (
|
||||
<CommandItem
|
||||
key={user.userId ?? user.uuid}
|
||||
key={user.uuid}
|
||||
value={user.username}
|
||||
onSelect={() => user.userId && toggleAssignee(user.userId)}
|
||||
onSelect={() => toggleAssignee(user.uuid)}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
user.userId != null && selectedAssignees.includes(user.userId)
|
||||
selectedAssignees.includes(user.uuid)
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@ import { correspondenceService } from "@/lib/services/correspondence.service";
|
||||
|
||||
// Updated Zod Schema with all required fields
|
||||
const correspondenceSchema = z.object({
|
||||
projectId: z.number().min(1, "Please select a Project"),
|
||||
projectId: z.string().min(1, "Please select a Project"),
|
||||
documentTypeId: z.number().min(1, "Please select a Document Type"),
|
||||
disciplineId: z.number().optional(),
|
||||
subject: z.string().min(5, "Subject must be at least 5 characters"),
|
||||
@@ -34,8 +34,8 @@ const correspondenceSchema = z.object({
|
||||
body: z.string().optional(),
|
||||
remarks: z.string().optional(),
|
||||
dueDate: z.string().optional(), // ISO Date string
|
||||
fromOrganizationId: z.number().min(1, "Please select From Organization"),
|
||||
toOrganizationId: z.number().min(1, "Please select To Organization"),
|
||||
fromOrganizationId: z.string().min(1, "Please select From Organization"),
|
||||
toOrganizationId: z.string().min(1, "Please select To Organization"),
|
||||
importance: z.enum(["NORMAL", "HIGH", "URGENT"]),
|
||||
attachments: z.array(z.instanceof(File)).optional(),
|
||||
});
|
||||
@@ -56,7 +56,7 @@ export function CorrespondenceForm({ initialData, uuid }: { initialData?: any, u
|
||||
// Extract initial values if editing
|
||||
const currentRev = initialData?.revisions?.find((r: any) => r.isCurrent) || initialData?.revisions?.[0];
|
||||
const defaultValues: Partial<FormData> = {
|
||||
projectId: initialData?.projectId || undefined,
|
||||
projectId: initialData?.projectId ? String(initialData.projectId) : undefined,
|
||||
documentTypeId: initialData?.correspondenceTypeId || undefined,
|
||||
disciplineId: initialData?.disciplineId || undefined,
|
||||
subject: currentRev?.subject || currentRev?.title || "",
|
||||
@@ -64,9 +64,11 @@ export function CorrespondenceForm({ initialData, uuid }: { initialData?: any, u
|
||||
body: currentRev?.body || "",
|
||||
remarks: currentRev?.remarks || "",
|
||||
dueDate: currentRev?.dueDate ? new Date(currentRev.dueDate).toISOString().split('T')[0] : undefined,
|
||||
fromOrganizationId: initialData?.originatorId || undefined,
|
||||
fromOrganizationId: initialData?.originatorId ? String(initialData.originatorId) : undefined,
|
||||
// Map initial recipient (TO) - Simplified for now
|
||||
toOrganizationId: initialData?.recipients?.find((r: any) => r.recipientType === 'TO')?.recipientOrganizationId || undefined,
|
||||
toOrganizationId: initialData?.recipients?.find((r: any) => r.recipientType === 'TO')?.recipientOrganizationId
|
||||
? String(initialData.recipients.find((r: any) => r.recipientType === 'TO').recipientOrganizationId)
|
||||
: undefined,
|
||||
importance: currentRev?.details?.importance || "NORMAL",
|
||||
};
|
||||
|
||||
@@ -209,8 +211,8 @@ export function CorrespondenceForm({ initialData, uuid }: { initialData?: any, u
|
||||
<div className="space-y-2">
|
||||
<Label>Project *</Label>
|
||||
<Select
|
||||
onValueChange={(v) => setValue("projectId", parseInt(v))}
|
||||
value={projectId ? String(projectId) : undefined}
|
||||
onValueChange={(v) => setValue("projectId", v)}
|
||||
value={projectId || undefined}
|
||||
disabled={isLoadingProjects}
|
||||
>
|
||||
<SelectTrigger>
|
||||
@@ -323,8 +325,8 @@ export function CorrespondenceForm({ initialData, uuid }: { initialData?: any, u
|
||||
<div className="space-y-2">
|
||||
<Label>From Organization *</Label>
|
||||
<Select
|
||||
onValueChange={(v) => setValue("fromOrganizationId", parseInt(v))}
|
||||
value={fromOrgId ? String(fromOrgId) : undefined}
|
||||
onValueChange={(v) => setValue("fromOrganizationId", v)}
|
||||
value={fromOrgId || undefined}
|
||||
disabled={isLoadingOrgs}
|
||||
>
|
||||
<SelectTrigger>
|
||||
@@ -332,7 +334,7 @@ export function CorrespondenceForm({ initialData, uuid }: { initialData?: any, u
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(organizations || []).map((org: Organization) => (
|
||||
<SelectItem key={org.id} value={String(org.id)}>
|
||||
<SelectItem key={org.uuid} value={org.uuid}>
|
||||
{org.organizationName} ({org.organizationCode})
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -346,8 +348,8 @@ export function CorrespondenceForm({ initialData, uuid }: { initialData?: any, u
|
||||
<div className="space-y-2">
|
||||
<Label>To Organization *</Label>
|
||||
<Select
|
||||
onValueChange={(v) => setValue("toOrganizationId", parseInt(v))}
|
||||
value={toOrgId ? String(toOrgId) : undefined}
|
||||
onValueChange={(v) => setValue("toOrganizationId", v)}
|
||||
value={toOrgId || undefined}
|
||||
disabled={isLoadingOrgs}
|
||||
>
|
||||
<SelectTrigger>
|
||||
@@ -355,7 +357,7 @@ export function CorrespondenceForm({ initialData, uuid }: { initialData?: any, u
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(organizations || []).map((org: Organization) => (
|
||||
<SelectItem key={org.id} value={String(org.id)}>
|
||||
<SelectItem key={org.uuid} value={org.uuid}>
|
||||
{org.organizationName} ({org.organizationCode})
|
||||
</SelectItem>
|
||||
))}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ColumnDef } from '@tanstack/react-table';
|
||||
import { Drawing } from '@/types/drawing';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowUpDown, MoreHorizontal } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -66,7 +67,9 @@ export const columns: ColumnDef<Drawing>[] = [
|
||||
Copy Drawing No.
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>View Details</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href={`/drawings/${drawing.uuid}`}>View Details</Link>
|
||||
</DropdownMenuItem>
|
||||
{/* Add download/view functionality later */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -68,8 +68,8 @@ export function TemplateTester({ open, onOpenChange, template }: TemplateTesterP
|
||||
try {
|
||||
const result = await numberingApi.previewNumber({
|
||||
projectId: projectId,
|
||||
originatorOrganizationId: parseInt(testData.originatorId || "0"),
|
||||
recipientOrganizationId: parseInt(testData.recipientId || "0"),
|
||||
originatorOrganizationId: testData.originatorId || "0",
|
||||
recipientOrganizationId: testData.recipientId || "0",
|
||||
correspondenceTypeId: parseInt(testData.correspondenceTypeId || "0"),
|
||||
disciplineId: parseInt(testData.disciplineId || "0"),
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ export function useDrawings(type: DrawingType, params: DrawingSearchParams) {
|
||||
if (response && response.data) {
|
||||
const mappedData = response.data.map((d: ContractDrawing) => ({
|
||||
...d,
|
||||
uuid: d.uuid,
|
||||
uuid: d.uuid || (d as unknown as { id: string }).id,
|
||||
drawingNumber: d.contractDrawingNo,
|
||||
type: 'CONTRACT',
|
||||
}));
|
||||
@@ -46,7 +46,7 @@ export function useDrawings(type: DrawingType, params: DrawingSearchParams) {
|
||||
if (response && response.data) {
|
||||
const mappedData = response.data.map((d: ShopDrawing) => ({
|
||||
...d,
|
||||
uuid: d.uuid,
|
||||
uuid: d.uuid || (d as unknown as { id: string }).id,
|
||||
type: 'SHOP',
|
||||
title: d.currentRevision?.title || 'Untitled',
|
||||
revision: d.currentRevision?.revisionNumber,
|
||||
@@ -61,7 +61,7 @@ export function useDrawings(type: DrawingType, params: DrawingSearchParams) {
|
||||
if (response && response.data) {
|
||||
const mappedData = response.data.map((d: AsBuiltDrawing) => ({
|
||||
...d,
|
||||
uuid: d.uuid,
|
||||
uuid: d.uuid || (d as unknown as { id: string }).id,
|
||||
type: 'AS_BUILT',
|
||||
title: d.currentRevision?.title || 'Untitled',
|
||||
revision: d.currentRevision?.revisionNumber,
|
||||
|
||||
@@ -34,7 +34,7 @@ export interface NumberingTemplate {
|
||||
*/
|
||||
export interface SaveTemplateDto {
|
||||
id?: number; // If present, update; otherwise create
|
||||
projectId: number;
|
||||
projectId: number | string;
|
||||
correspondenceTypeId: number | null;
|
||||
formatTemplate: string;
|
||||
description?: string;
|
||||
@@ -273,13 +273,13 @@ export const numberingApi = {
|
||||
* Preview what a document number would look like (without generating)
|
||||
*/
|
||||
previewNumber: async (ctx: {
|
||||
projectId: number;
|
||||
originatorOrganizationId: number;
|
||||
projectId: number | string;
|
||||
originatorOrganizationId: number | string;
|
||||
correspondenceTypeId: number;
|
||||
disciplineId?: number;
|
||||
subTypeId?: number;
|
||||
rfaTypeId?: number;
|
||||
recipientOrganizationId?: number;
|
||||
recipientOrganizationId?: number | string;
|
||||
}): Promise<{ previewNumber: string; nextSequence: number }> => {
|
||||
const res = await apiClient.post<{ data: { previewNumber: string; nextSequence: number } }>(
|
||||
'/document-numbering/preview',
|
||||
|
||||
@@ -89,10 +89,10 @@ export interface CirculationListResponse {
|
||||
* DTO for creating a circulation
|
||||
*/
|
||||
export interface CreateCirculationDto {
|
||||
correspondenceId: number;
|
||||
projectId?: number;
|
||||
correspondenceId: number | string;
|
||||
projectId?: number | string;
|
||||
subject: string;
|
||||
assigneeIds: number[];
|
||||
assigneeIds: (number | string)[];
|
||||
remarks?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// File: src/types/dto/circulation/create-circulation.dto.ts
|
||||
|
||||
export interface CreateCirculationDto {
|
||||
/** เอกสารต้นเรื่องที่จะเวียน (Correspondence ID) */
|
||||
correspondenceId: number;
|
||||
/** เอกสารต้นเรื่องที่จะเวียน (Correspondence ID or UUID) */
|
||||
correspondenceId: number | string;
|
||||
|
||||
/** หัวข้อเรื่อง (Subject) */
|
||||
subject: string;
|
||||
subject: string;
|
||||
|
||||
/** รายชื่อ User ID ที่ต้องการส่งให้ (ผู้รับผิดชอบ) */
|
||||
assigneeIds: number[];
|
||||
/** รายชื่อ User ID/UUID ที่ต้องการส่งให้ (ผู้รับผิดชอบ) */
|
||||
assigneeIds: (number | string)[];
|
||||
|
||||
/** หมายเหตุเพิ่มเติม (ถ้ามี) */
|
||||
remarks?: string;
|
||||
}
|
||||
remarks?: string;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// File: src/types/dto/correspondence/create-correspondence.dto.ts
|
||||
|
||||
export interface CreateCorrespondenceDto {
|
||||
/** ID ของโครงการ */
|
||||
projectId: number;
|
||||
/** ID or UUID ของโครงการ */
|
||||
projectId: number | string;
|
||||
|
||||
/** ID ของประเภทเอกสาร (เช่น RFA, LETTER) */
|
||||
typeId: number;
|
||||
@@ -37,8 +37,8 @@ export interface CreateCorrespondenceDto {
|
||||
/** * ✅ Field สำหรับ Impersonation (เลือกองค์กรผู้ส่ง)
|
||||
* ใช้กรณี Admin สร้างเอกสารแทนผู้อื่น
|
||||
*/
|
||||
originatorId?: number;
|
||||
originatorId?: number | string;
|
||||
|
||||
/** รายชื่อผู้รับ */
|
||||
recipients?: { organizationId: number; type: 'TO' | 'CC' }[];
|
||||
recipients?: { organizationId: number | string; type: 'TO' | 'CC' }[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user