feat: implement document editing functionality with metadata and classification updates

This commit is contained in:
Yashwin 2026-04-01 15:12:31 +05:30
parent 7a368b09bc
commit 14bb57a574
7 changed files with 504 additions and 113 deletions

View File

@ -232,7 +232,7 @@ const WorkflowDefinitionsTable = ({
key: "source_module",
label: "Module",
render: (wf) => (
<span className="text-sm text-[#6b7280]">{wf.source_module}</span>
<span className="text-sm text-[#6b7280]">{wf.source_module?.join(", ")}</span>
),
},
{

View File

@ -1,5 +1,8 @@
import { useEffect, useState, type ReactElement } from "react";
import { useNavigate } from "react-router-dom";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Layout } from "@/components/layout/Layout";
import { FormField, FormSelect, FormTextArea, PrimaryButton, RichTextEditor } from "@/components/shared";
import { documentService, type FileAttachmentItem } from "@/services/document-service";
@ -9,22 +12,50 @@ import { ArrowLeft, FileText, Info, Paperclip } from "lucide-react";
import { moduleService } from "@/services/module-service";
import type { MyModule } from "@/types/module";
const documentSchema = z.object({
title: z.string().min(1, "Document title is required"),
document_number: z.string().optional(),
description: z.string().optional(),
document_type: z.string().min(1, "Document type is required"),
category_id: z.string().optional(),
department: z.string().optional(),
tags: z.string().optional(),
selectedModuleId: z.string().min(1, "Source module is required"),
content: z.string().optional(),
contentHtml: z.string().min(1, "Document content is required"),
});
type DocumentFormData = z.infer<typeof documentSchema>;
const CreateDocument = (): ReactElement => {
const navigate = useNavigate();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [documentNumber, setDocumentNumber] = useState("");
const [documentType, setDocumentType] = useState("");
const [categoryId, setCategoryId] = useState("");
const [department, setDepartment] = useState("");
const [tags, setTags] = useState("");
const [content, setContent] = useState("");
const [contentHtml, setContentHtml] = useState("");
const [isSaving, setIsSaving] = useState(false);
const [types, setTypes] = useState<Array<{ code: string; name: string }>>([]);
const [categories, setCategories] = useState<DocumentCategory[]>([]);
const [modules, setModules] = useState<MyModule[]>([]);
const [selectedModuleId, setSelectedModuleId] = useState("");
const {
control,
register,
handleSubmit,
setValue,
watch,
formState: { errors },
} = useForm<DocumentFormData>({
resolver: zodResolver(documentSchema),
defaultValues: {
title: "",
document_number: "",
description: "",
document_type: "",
category_id: "",
department: "",
tags: "",
selectedModuleId: "",
content: "",
contentHtml: "",
},
});
// File attachment fields
const [files, setFiles] = useState<FileAttachmentItem[]>([]);
@ -70,34 +101,31 @@ const CreateDocument = (): ReactElement => {
const handleFileSelect = async (fileId: string): Promise<void> => {
setSelectedFileId(fileId);
if (!fileId) {
// Clear file fields when deselected
setFileName("");
setFilePath("");
setFileSize(undefined);
setMimeType("");
setFileHash("");
setContentHtml("");
setContent("");
setValue("contentHtml", "");
setValue("content", "");
return;
}
const selected = files.find((f) => f.id === fileId);
if (!selected) return;
// Populate file metadata fields
setFileName(selected.original_name);
setFilePath(selected.file_path);
setFileSize(selected.file_size);
setMimeType(selected.mime_type);
setFileHash(selected.checksum);
// Extract actual file content from backend
try {
showToast.success(`Extracting content from "${selected.original_name}"...`);
const res = await documentService.getFileContent(fileId);
if (res.success && res.data) {
setContentHtml(res.data.html || "");
setContent(res.data.text || "");
setValue("contentHtml", res.data.html || "");
setValue("content", res.data.text || "");
showToast.success(`Content loaded from "${selected.original_name}"`);
} else {
showToast.error("Failed to extract file content");
@ -105,42 +133,37 @@ const CreateDocument = (): ReactElement => {
} catch (err: any) {
const msg = err?.response?.data?.error?.message || "Failed to extract file content";
showToast.error(msg);
// Fallback: set a placeholder
const html = `<p>Document sourced from file: <strong>${selected.original_name}</strong></p>`;
setContentHtml(html);
setContent(`Document sourced from file: ${selected.original_name}`);
setValue("contentHtml", html);
setValue("content", `Document sourced from file: ${selected.original_name}`);
}
};
const onSubmit = async (event: React.FormEvent): Promise<void> => {
event.preventDefault();
if (!title.trim() || !documentType) {
showToast.error("Title and document type are required");
return;
}
const onFormSubmit = async (data: DocumentFormData): Promise<void> => {
try {
setIsSaving(true);
const response = await documentService.create({
title: title.trim(),
description: description.trim() || undefined,
document_number: documentNumber.trim() || undefined,
document_type: documentType,
category_id: categoryId || undefined,
department: department.trim() || undefined,
tags: tags
.split(",")
.map((tag) => tag.trim())
.filter(Boolean),
content: content.trim() || undefined,
content_html: contentHtml.trim() || undefined,
title: data.title.trim(),
description: data.description?.trim() || undefined,
document_number: data.document_number?.trim() || undefined,
document_type: data.document_type,
category_id: data.category_id || undefined,
department: data.department?.trim() || undefined,
tags: data.tags
? data.tags
.split(",")
.map((tag) => tag.trim())
.filter(Boolean)
: [],
content: data.content?.trim() || undefined,
content_html: data.contentHtml.trim() || undefined,
file_name: fileName || undefined,
file_path: filePath || undefined,
file_size: fileSize,
mime_type: mimeType || undefined,
file_hash: fileHash || undefined,
source_module: selectedModuleId ? modules.find(m => m.id === selectedModuleId)?.module_id : undefined,
source_module_id: selectedModuleId || undefined,
source_module: modules.find((m) => m.id === data.selectedModuleId)!.module_id,
source_module_id: data.selectedModuleId,
});
showToast.success("Document created successfully");
navigate(`/tenant/documents/${response.data.id}`);
@ -171,7 +194,7 @@ const CreateDocument = (): ReactElement => {
],
}}
>
<form onSubmit={onSubmit} className="space-y-4">
<form onSubmit={handleSubmit(onFormSubmit)} className="space-y-4">
<div className="bg-white border border-[rgba(0,0,0,0.08)] rounded-lg shadow-[0px_4px_24px_0px_rgba(0,0,0,0.02)] p-4 md:p-5">
<div className="flex items-start justify-between gap-4 border-b border-[rgba(0,0,0,0.08)] pb-4 mb-4">
<div className="flex items-start gap-2">
@ -201,23 +224,23 @@ const CreateDocument = (): ReactElement => {
<FormField
label="Document Title"
required
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter document title"
error={errors.title?.message}
{...register("title")}
/>
<FormField
label="Document Number"
value={documentNumber}
onChange={(e) => setDocumentNumber(e.target.value)}
placeholder="Auto-generated if empty"
error={errors.document_number?.message}
{...register("document_number")}
/>
</div>
<FormTextArea
label="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Brief description of this document"
error={errors.description?.message}
{...register("description")}
/>
</div>
@ -226,48 +249,67 @@ const CreateDocument = (): ReactElement => {
Classification
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-4">
<FormSelect
label="Document Type"
required
value={documentType}
onValueChange={setDocumentType}
options={types.map((type) => ({ value: type.code, label: type.name }))}
placeholder="Select type"
<Controller
name="document_type"
control={control}
render={({ field }) => (
<FormSelect
label="Document Type"
required
value={field.value}
onValueChange={field.onChange}
options={types.map((type) => ({ value: type.code, label: type.name }))}
placeholder="Select type"
error={errors.document_type?.message}
/>
)}
/>
<FormSelect
label="Category"
value={categoryId}
onValueChange={setCategoryId}
options={categories.map((category) => ({
value: category.id,
label: `${category.name} (${category.code})`,
}))}
placeholder="Select category"
<Controller
name="category_id"
control={control}
render={({ field }) => (
<FormSelect
label="Category"
value={field.value}
onValueChange={field.onChange}
options={categories.map((category) => ({
value: category.id,
label: `${category.name} (${category.code})`,
}))}
placeholder="Select category"
error={errors.category_id?.message}
/>
)}
/>
<FormField
label="Department"
value={department}
onChange={(e) => setDepartment(e.target.value)}
placeholder="Optional"
error={errors.department?.message}
{...register("department")}
/>
<FormField
label="Tags"
value={tags}
onChange={(e) => setTags(e.target.value)}
placeholder="Comma separated tags (e.g. quality, sop)"
error={errors.tags?.message}
{...register("tags")}
/>
<FormSelect
label="Source Module"
value={selectedModuleId}
onValueChange={setSelectedModuleId}
options={[
{ value: "", label: "Platform (Default)" },
...modules.map((m) => ({
value: m.id,
label: m.name,
})),
]}
placeholder="Specify originating module (Optional)"
<Controller
name="selectedModuleId"
control={control}
render={({ field }) => (
<FormSelect
label="Source Module"
required
value={field.value}
onValueChange={field.onChange}
options={modules.map((m) => ({
value: m.id,
label: m.name,
}))}
placeholder="Select originating module"
error={errors.selectedModuleId?.message}
/>
)}
/>
</div>
</div>
@ -309,19 +351,26 @@ const CreateDocument = (): ReactElement => {
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-[#0f1724]">Initial Content</h3>
<span className="text-[11px] text-[#94a3b8]">
{content.length} characters
{watch("content")?.length || 0} characters
</span>
</div>
<RichTextEditor
label="Content"
value={contentHtml}
required
placeholder="Write the initial document content..."
minHeightClassName="min-h-[280px]"
onChange={(html, text) => {
setContentHtml(html);
setContent(text);
}}
<Controller
name="contentHtml"
control={control}
render={({ field }) => (
<RichTextEditor
label="Content"
value={field.value}
required
placeholder="Write the initial document content..."
minHeightClassName="min-h-[280px]"
onChange={(html, text) => {
field.onChange(html);
setValue("content", text);
}}
error={errors.contentHtml?.message}
/>
)}
/>
<div className="mt-1 flex items-start gap-1.5 text-[11px] text-[#6b7280]">
<Info className="w-3.5 h-3.5 mt-[1px] shrink-0" />
@ -341,13 +390,18 @@ const CreateDocument = (): ReactElement => {
Cancel
</button>
<button
type="submit"
type="button"
disabled={isSaving}
className="h-10 px-4 border border-[rgba(0,0,0,0.08)] rounded-md text-xs text-[#112868] hover:bg-[#f8fafc] disabled:opacity-60"
onClick={handleSubmit(onFormSubmit)}
>
{isSaving ? "Saving..." : "Save As Draft"}
</button>
<PrimaryButton type="submit" disabled={isSaving}>
<PrimaryButton
type="submit"
disabled={isSaving}
onClick={handleSubmit(onFormSubmit)}
>
{isSaving ? "Creating..." : "Create Document"}
</PrimaryButton>
</div>

View File

@ -156,6 +156,22 @@ const Documents = (): ReactElement => {
<span className="text-[#6b7280]">{formatDate(doc.updated_at)}</span>
),
},
{
key: "actions",
label: "Actions",
render: (doc) => (
<button
type="button"
className="text-xs text-[#084cc8] hover:underline font-medium"
onClick={(e) => {
e.stopPropagation();
navigate(`/tenant/documents/edit/${doc.id}`);
}}
>
Edit
</button>
),
},
],
[navigate],
);

View File

@ -0,0 +1,266 @@
import { useEffect, useState, type ReactElement } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Layout } from "@/components/layout/Layout";
import { FormField, FormSelect, FormTextArea, PrimaryButton } from "@/components/shared";
import { documentService } from "@/services/document-service";
import type { DocumentCategory } from "@/types/document";
import { showToast } from "@/utils/toast";
import { ArrowLeft, FileText } from "lucide-react";
import { moduleService } from "@/services/module-service";
import type { MyModule } from "@/types/module";
const documentSchema = z.object({
title: z.string().min(1, "Document title is required"),
description: z.string().optional(),
category_id: z.string().optional(),
department: z.string().optional(),
tags: z.string().optional(),
selectedModuleId: z.string().min(1, "Source module is required"),
});
type DocumentFormData = z.infer<typeof documentSchema>;
const EditDocument = (): ReactElement => {
const navigate = useNavigate();
const { id } = useParams<{ id: string }>();
const [isSaving, setIsSaving] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [categories, setCategories] = useState<DocumentCategory[]>([]);
const [modules, setModules] = useState<MyModule[]>([]);
const {
control,
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<DocumentFormData>({
resolver: zodResolver(documentSchema),
defaultValues: {
title: "",
description: "",
category_id: "",
department: "",
tags: "",
selectedModuleId: "",
},
});
useEffect(() => {
const loadData = async (): Promise<void> => {
if (!id) return;
try {
setIsLoading(true);
const [categoriesRes, modulesRes, docRes] = await Promise.all([
documentService.getCategories(),
moduleService.getMyModules(),
documentService.getById(id),
]);
setCategories(categoriesRes.data || []);
const myModules = modulesRes.data || [];
setModules(myModules);
const doc = docRes.data;
// Find matching module by id (UUID) or module_id (code)
const matchedModule = myModules.find(m =>
(doc.source_module_id && m.id === doc.source_module_id) ||
(doc.source_module && m.module_id === doc.source_module)
);
reset({
title: doc.title,
description: doc.description || "",
category_id: doc.category_id || "",
department: doc.department || "",
tags: (doc.tags || []).join(", "),
selectedModuleId: matchedModule?.id || "",
});
} catch (err: any) {
showToast.error(err?.response?.data?.error?.message || "Failed to load document data");
navigate("/tenant/documents");
} finally {
setIsLoading(false);
}
};
void loadData();
}, [id, reset, navigate]);
const onFormSubmit = async (data: DocumentFormData): Promise<void> => {
if (!id) return;
try {
setIsSaving(true);
await documentService.update(id, {
title: data.title.trim(),
description: data.description?.trim() || undefined,
category_id: data.category_id || undefined,
department: data.department?.trim() || undefined,
tags: data.tags
? data.tags
.split(",")
.map((tag) => tag.trim())
.filter(Boolean)
: [],
source_module: modules.find((m) => m.id === data.selectedModuleId)!.module_id,
source_module_id: data.selectedModuleId,
});
showToast.success("Document updated successfully");
navigate(`/tenant/documents/${id}`);
} catch (err: any) {
showToast.error(
err?.response?.data?.error?.message || "Failed to update document",
);
} finally {
setIsSaving(false);
}
};
if (isLoading) {
return (
<Layout currentPage="Document Service">
<div className="flex items-center justify-center min-h-[400px]">
<p className="text-sm text-[#6b7280]">Loading document details...</p>
</div>
</Layout>
);
}
return (
<Layout
currentPage="Document Service"
breadcrumbs={[
{ label: "Document Service", path: "/tenant/documents" },
{ label: "Edit Document" },
]}
pageHeader={{
title: "Edit Document",
description: "Modify document metadata and classification details.",
}}
>
<form onSubmit={handleSubmit(onFormSubmit)} className="space-y-4">
<div className="bg-white border border-[rgba(0,0,0,0.08)] rounded-lg shadow-[0px_4px_24px_0px_rgba(0,0,0,0.02)] p-4 md:p-5">
<div className="flex items-start justify-between gap-4 border-b border-[rgba(0,0,0,0.08)] pb-4 mb-4">
<div className="flex items-start gap-2">
<div className="mt-0.5 w-8 h-8 rounded-md bg-[#112868]/10 flex items-center justify-center">
<FileText className="w-4 h-4 text-[#112868]" />
</div>
<div>
<h3 className="text-sm font-semibold text-[#0f1724]">
Edit Document Metadata
</h3>
<p className="text-xs text-[#6b7280] mt-1">
Updates will apply to the current version of the document.
</p>
</div>
</div>
<button
type="button"
onClick={() => navigate(-1)}
className="inline-flex items-center gap-1.5 text-xs text-[#475569] hover:text-[#0f1724]"
>
<ArrowLeft className="w-3.5 h-3.5" />
Back
</button>
</div>
<div className="grid grid-cols-1 gap-x-4">
<FormField
label="Document Title"
required
placeholder="Enter document title"
error={errors.title?.message}
{...register("title")}
/>
</div>
<FormTextArea
label="Description"
placeholder="Brief description of this document"
error={errors.description?.message}
{...register("description")}
/>
</div>
<div className="bg-white border border-[rgba(0,0,0,0.08)] rounded-lg shadow-[0px_4px_24px_0px_rgba(0,0,0,0.02)] p-4 md:p-5">
<h3 className="text-sm font-semibold text-[#0f1724] mb-3">
Classification
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-4">
<Controller
name="category_id"
control={control}
render={({ field }) => (
<FormSelect
label="Category"
value={field.value}
onValueChange={field.onChange}
options={categories.map((category) => ({
value: category.id,
label: `${category.name} (${category.code})`,
}))}
placeholder="Select category"
error={errors.category_id?.message}
/>
)}
/>
<FormField
label="Department"
placeholder="Optional"
error={errors.department?.message}
{...register("department")}
/>
<FormField
label="Tags"
placeholder="Comma separated tags (e.g. quality, sop)"
error={errors.tags?.message}
{...register("tags")}
/>
<Controller
name="selectedModuleId"
control={control}
render={({ field }) => (
<FormSelect
label="Source Module"
required
value={field.value}
onValueChange={field.onChange}
options={modules.map((m) => ({
value: m.id,
label: m.name,
}))}
placeholder="Select originating module"
error={errors.selectedModuleId?.message}
/>
)}
/>
</div>
</div>
<div className="bg-white border border-[rgba(0,0,0,0.08)] rounded-lg shadow-[0px_4px_24px_0px_rgba(0,0,0,0.02)] p-4 md:p-5">
<div className="flex gap-2 mt-1">
<button
type="button"
className="h-10 px-4 border border-[rgba(0,0,0,0.08)] rounded-md text-xs text-[#0f1724] hover:bg-gray-50"
onClick={() => navigate(-1)}
>
Cancel
</button>
<PrimaryButton
type="submit"
disabled={isSaving}
onClick={handleSubmit(onFormSubmit)}
>
{isSaving ? "Updating..." : "Update Document"}
</PrimaryButton>
</div>
</div>
</form>
</Layout>
);
};
export default EditDocument;

View File

@ -75,6 +75,7 @@ const ViewDocument = (): ReactElement => {
const [newVersionChangeReason, setNewVersionChangeReason] = useState("minor_edit");
const [newVersionChangeSummary, setNewVersionChangeSummary] = useState("");
const [isMajorVersion, setIsMajorVersion] = useState(false);
const [versionErrors, setVersionErrors] = useState<Record<string, string>>({});
const [isVersionSaving, setIsVersionSaving] = useState(false);
const [showWorkflowTracker, setShowWorkflowTracker] = useState(false);
const [workflowInstance, setWorkflowInstance] = useState<WorkflowInstance | null>(null);
@ -244,6 +245,7 @@ const ViewDocument = (): ReactElement => {
const response = await workflowService.listDefinitions({
status: "active",
entity_type: "document",
source_module_id: document?.source_module_id || undefined,
limit: 100,
offset: 0,
});
@ -320,12 +322,20 @@ const ViewDocument = (): ReactElement => {
const handleCreateVersion = async (): Promise<void> => {
if (!id) return;
// Clear previous errors
setVersionErrors({});
const localErrors: Record<string, string> = {};
if (!newVersionChangeReason) {
showToast.error("Change reason is required");
return;
localErrors.change_reason = "Change reason is required";
}
if (!newVersionContent.trim()) {
showToast.error("Document content is required");
localErrors.content = "Document content is required";
}
if (Object.keys(localErrors).length > 0) {
setVersionErrors(localErrors);
return;
}
@ -462,13 +472,22 @@ const ViewDocument = (): ReactElement => {
</div>
<div className="flex items-center gap-2">
{document?.status === "draft" && (
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md bg-[#084cc8] text-white text-xs font-medium hover:bg-[#063a99]"
onClick={() => void openActionModal("submit")}
>
{ACTION_LABELS["submit"]}
</button>
<div className="flex items-center gap-2">
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md border border-[#e2e8f0] text-[#475569] bg-white text-xs font-medium hover:bg-[#f8fafc] transition-colors"
onClick={() => navigate(`/tenant/documents/edit/${id}`)}
>
Edit Metadata
</button>
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md bg-[#084cc8] text-white text-xs font-medium hover:bg-[#063a99] transition-colors"
onClick={() => void openActionModal("submit")}
>
{ACTION_LABELS["submit"]}
</button>
</div>
)}
{document?.status === "in_review" && (
<>
@ -675,7 +694,9 @@ const ViewDocument = (): ReactElement => {
onChange={(html, text) => {
setNewVersionContentHtml(html);
setNewVersionContent(text);
if (text.trim()) setVersionErrors(prev => ({ ...prev, content: "" }));
}}
error={versionErrors.content}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormSelect
@ -688,8 +709,12 @@ const ViewDocument = (): ReactElement => {
{ value: "major_rewrite", label: "major_rewrite" },
]}
value={newVersionChangeReason}
onValueChange={setNewVersionChangeReason}
onValueChange={(val) => {
setNewVersionChangeReason(val);
if (val) setVersionErrors(prev => ({ ...prev, change_reason: "" }));
}}
placeholder="Select change reason"
error={versionErrors.change_reason}
/>
<div className="flex items-center gap-2 pt-8">
<input
@ -822,14 +847,36 @@ const ViewDocument = (): ReactElement => {
>
<div className="p-5 space-y-3">
{activeAction === "submit" && (
<FormSelect
label="Workflow Definition"
required
options={workflowOptions}
value={workflowDefinitionId}
onValueChange={setWorkflowDefinitionId}
placeholder="Select active workflow definition"
/>
<div className="space-y-3">
<FormSelect
label="Workflow Definition"
required
options={workflowOptions}
value={workflowDefinitionId}
onValueChange={setWorkflowDefinitionId}
placeholder="Select active workflow definition"
/>
{/* <div className="flex flex-col gap-1.5 px-0.5">
<div className="flex items-center gap-2">
<span className="text-[11px] font-medium text-[#64748b] min-w-[80px]">Entity Type:</span>
<span className="text-[11px] font-semibold text-[#0f1724] bg-[#f1f5f9] px-2 py-0.5 rounded border border-[#e2e8f0]">
Document
</span>
</div>
<div className="flex items-center gap-2">
<span className="text-[11px] font-medium text-[#64748b] min-w-[80px]">Source Module:</span>
<span className="text-[11px] font-semibold text-[#0f1724] bg-[#f1f5f9] px-2 py-0.5 rounded border border-[#e2e8f0]">
{document?.module_name || "Platform"}
</span>
</div>
</div> */}
<div className="mt-2 p-2 bg-red-50 border border-red-100 rounded text-[10px] leading-relaxed text-[#e11d48]">
<strong>Note:</strong> Currently displaying active workflow definitions registered for
<span className="font-semibold mx-0.5">Entity Type: Document</span> and
<span className="font-semibold mx-0.5">Module: {document?.module_name || "Platform"}</span>.
If the list is empty, please go to Workflow Management and create a definition for this specific configuration.
</div>
</div>
)}
{activeAction === "approve" && (
<div>

View File

@ -16,6 +16,7 @@ const WorkflowDefination = lazy(
const Suppliers = lazy(() => import("@/pages/tenant/Suppliers"));
const Documents = lazy(() => import("@/pages/tenant/Documents"));
const CreateDocument = lazy(() => import("@/pages/tenant/CreateDocument"));
const EditDocument = lazy(() => import("@/pages/tenant/EditDocument"));
const ViewDocument = lazy(() => import("@/pages/tenant/ViewDocument"));
const DocumentCategories = lazy(() => import("@/pages/tenant/DocumentCategories"));
const Tasks = lazy(() => import("@/pages/tenant/Tasks"));
@ -93,6 +94,10 @@ export const tenantAdminRoutes: RouteConfig[] = [
path: "/tenant/documents/create",
element: <LazyRoute component={CreateDocument} />,
},
{
path: "/tenant/documents/edit/:id",
element: <LazyRoute component={EditDocument} />,
},
{
path: "/tenant/documents/:id",
element: <LazyRoute component={ViewDocument} />,

View File

@ -55,6 +55,9 @@ export interface DocumentDetail {
created_by?: string | null;
created_at?: string;
updated_at?: string;
source_module?: string | null;
source_module_id?: string | null;
category_id?: string | null;
module_name?: string | null;
}