978 lines
36 KiB
TypeScript
978 lines
36 KiB
TypeScript
"use client";
|
|
import React, { useState, useEffect } from "react";
|
|
import { useRouter, useParams } from "next/navigation";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import {
|
|
ChevronLeftIcon,
|
|
SaveIcon,
|
|
SettingsIcon,
|
|
UsersIcon,
|
|
WorkflowIcon,
|
|
} from "@/components/icons";
|
|
import {
|
|
Tenant,
|
|
TenantUpdateRequest,
|
|
getTenantById,
|
|
updateTenant,
|
|
} from "@/service/tenant";
|
|
import { getTenantList } from "@/service/tenant";
|
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
import Swal from "sweetalert2";
|
|
import { FormField } from "@/components/form/common/FormField";
|
|
import { getCookiesDecrypt } from "@/lib/utils";
|
|
import { ApprovalWorkflowForm } from "@/components/form/ApprovalWorkflowForm";
|
|
import { UserLevelsForm } from "@/components/form/UserLevelsForm";
|
|
import {
|
|
CreateApprovalWorkflowWithClientSettingsRequest,
|
|
UserLevelsCreateRequest,
|
|
UserLevel,
|
|
getUserLevels,
|
|
getApprovalWorkflowComprehensiveDetails,
|
|
ComprehensiveWorkflowResponse,
|
|
createUserLevel,
|
|
} from "@/service/approval-workflows";
|
|
import TenantCompanyUpdateForm from "@/components/form/tenant/tenant-detail-update-form";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import {
|
|
flexRender,
|
|
getCoreRowModel,
|
|
getFilteredRowModel,
|
|
getPaginationRowModel,
|
|
getSortedRowModel,
|
|
PaginationState,
|
|
useReactTable,
|
|
} from "@tanstack/react-table";
|
|
import TablePagination from "@/components/table/table-pagination";
|
|
import useTableColumns from "@/app/[locale]/(admin)/admin/settings/tenant/component/columns";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { PlusIcon, EditIcon, DeleteIcon } from "@/components/icons";
|
|
import { errorAutoClose, successAutoClose } from "@/lib/swal";
|
|
import { close, loading } from "@/config/swal";
|
|
|
|
export default function EditTenantPage() {
|
|
const router = useRouter();
|
|
const params = useParams();
|
|
const tenantId = params?.id as string;
|
|
const [totalData, setTotalData] = useState<number>(0);
|
|
const [totalPage, setTotalPage] = useState<number>(1);
|
|
|
|
const [tenant, setTenant] = useState<Tenant | null>(null);
|
|
const [parentTenants, setParentTenants] = useState<Tenant[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [activeTab, setActiveTab] = useState("profile");
|
|
|
|
// Workflow state
|
|
const [workflow, setWorkflow] =
|
|
useState<ComprehensiveWorkflowResponse | null>(null);
|
|
const [isEditingWorkflow, setIsEditingWorkflow] = useState(false);
|
|
|
|
// User Levels state
|
|
const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
|
|
const [isUserLevelDialogOpen, setIsUserLevelDialogOpen] = useState(false);
|
|
const [editingUserLevel, setEditingUserLevel] = useState<UserLevel | null>(
|
|
null,
|
|
);
|
|
|
|
// Tenant form data
|
|
const [formData, setFormData] = useState<TenantUpdateRequest>({
|
|
name: "",
|
|
description: "",
|
|
clientType: "standalone",
|
|
parentClientId: undefined,
|
|
maxUsers: undefined,
|
|
maxStorage: undefined,
|
|
address: "",
|
|
phoneNumber: "",
|
|
website: "",
|
|
isActive: true,
|
|
});
|
|
|
|
useEffect(() => {
|
|
// Check if user has roleId = 1
|
|
const roleId = getCookiesDecrypt("urie");
|
|
if (Number(roleId) !== 1) {
|
|
Swal.fire({
|
|
title: "Access Denied",
|
|
text: "You don't have permission to access this page",
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: "swal-z-index-9999",
|
|
},
|
|
}).then(() => {
|
|
router.push("/admin/dashboard");
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (tenantId) {
|
|
loadData();
|
|
}
|
|
}, [tenantId, router]);
|
|
|
|
// Load workflow and user levels when switching to those tabs
|
|
useEffect(() => {
|
|
if (tenant && (activeTab === "workflows" || activeTab === "user-levels")) {
|
|
loadWorkflowAndUserLevels();
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [activeTab]);
|
|
|
|
const loadData = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const [tenantRes, parentRes] = await Promise.all([
|
|
getTenantById(tenantId),
|
|
getTenantList({ clientType: "parent_client", limit: 100 }),
|
|
]);
|
|
|
|
if (tenantRes?.error || !tenantRes?.data?.data) {
|
|
Swal.fire({
|
|
title: "Error",
|
|
text: tenantRes?.message || "Failed to load tenant data",
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: "swal-z-index-9999",
|
|
},
|
|
}).then(() => {
|
|
router.push("/admin/tenants");
|
|
});
|
|
return;
|
|
}
|
|
|
|
const tenantData = tenantRes.data.data;
|
|
setTenant(tenantData);
|
|
|
|
// Set form data with all available fields
|
|
setFormData({
|
|
name: tenantData.name || "",
|
|
description: tenantData.description || "",
|
|
clientType: tenantData.clientType || "standalone",
|
|
parentClientId: tenantData.parentClientId || undefined,
|
|
maxUsers: tenantData.maxUsers || undefined,
|
|
maxStorage: tenantData.maxStorage || undefined,
|
|
address: tenantData.address || "",
|
|
phoneNumber: tenantData.phoneNumber || "",
|
|
website: tenantData.website || "",
|
|
isActive:
|
|
tenantData.isActive !== undefined ? tenantData.isActive : true,
|
|
logoUrl: tenantData.logoUrl || undefined,
|
|
logoImagePath: tenantData.logoImagePath || undefined,
|
|
});
|
|
|
|
if (!parentRes?.error) {
|
|
setParentTenants(parentRes?.data?.data || []);
|
|
}
|
|
|
|
// Load workflow and user levels if on those tabs
|
|
// Note: This will be loaded when tab changes via useEffect
|
|
} catch (error) {
|
|
console.error("Error loading tenant:", error);
|
|
Swal.fire({
|
|
title: "Error",
|
|
text: "An unexpected error occurred while loading tenant data",
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: "swal-z-index-9999",
|
|
},
|
|
}).then(() => {
|
|
router.push("/admin/tenants");
|
|
});
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadWorkflowAndUserLevels = async () => {
|
|
try {
|
|
const [comprehensiveWorkflowRes, userLevelsRes] = await Promise.all([
|
|
getApprovalWorkflowComprehensiveDetails(),
|
|
getUserLevels(),
|
|
]);
|
|
|
|
if (!comprehensiveWorkflowRes?.error) {
|
|
setWorkflow(comprehensiveWorkflowRes?.data?.data || null);
|
|
} else {
|
|
setWorkflow(null);
|
|
}
|
|
if (!userLevelsRes?.error) {
|
|
const data = userLevelsRes?.data?.data || [];
|
|
|
|
setUserLevels(data);
|
|
setTotalData(data.length);
|
|
|
|
const pageSize = pagination.pageSize;
|
|
setTotalPage(Math.max(1, Math.ceil(data.length / pageSize)));
|
|
}
|
|
|
|
if (!userLevelsRes?.error) {
|
|
setUserLevels(userLevelsRes?.data?.data || []);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error loading workflow and user levels:", error);
|
|
}
|
|
};
|
|
|
|
const handleSaveTenantInfo = async () => {
|
|
if (!tenantId) return;
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const updateData: TenantUpdateRequest = {
|
|
name: formData.name,
|
|
description: formData.description || undefined,
|
|
clientType: formData.clientType,
|
|
parentClientId: formData.parentClientId || undefined,
|
|
maxUsers: formData.maxUsers,
|
|
maxStorage: formData.maxStorage,
|
|
address: formData.address || undefined,
|
|
phoneNumber: formData.phoneNumber || undefined,
|
|
website: formData.website || undefined,
|
|
isActive: formData.isActive,
|
|
};
|
|
|
|
const res = await updateTenant(tenantId, updateData);
|
|
if (res?.error) {
|
|
Swal.fire({
|
|
title: "Error",
|
|
text: res?.message || "Failed to update tenant",
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: "swal-z-index-9999",
|
|
},
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
title: "Success",
|
|
text: "Tenant updated successfully",
|
|
icon: "success",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: "swal-z-index-9999",
|
|
},
|
|
});
|
|
await loadData();
|
|
}
|
|
} catch (error) {
|
|
console.error("Error saving tenant:", error);
|
|
Swal.fire({
|
|
title: "Error",
|
|
text: "An unexpected error occurred",
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: "swal-z-index-9999",
|
|
},
|
|
});
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleWorkflowSave = async (
|
|
data: CreateApprovalWorkflowWithClientSettingsRequest,
|
|
) => {
|
|
setIsEditingWorkflow(false);
|
|
await loadWorkflowAndUserLevels();
|
|
};
|
|
|
|
const handleUserLevelSave = async (data: UserLevelsCreateRequest) => {
|
|
try {
|
|
loading();
|
|
const response = await createUserLevel(data);
|
|
close();
|
|
|
|
if (response?.error) {
|
|
errorAutoClose(response.message || "Failed to create user level.");
|
|
return;
|
|
}
|
|
|
|
successAutoClose("User level created successfully.");
|
|
setIsUserLevelDialogOpen(false);
|
|
setEditingUserLevel(null);
|
|
setTimeout(async () => {
|
|
await loadWorkflowAndUserLevels();
|
|
}, 1000);
|
|
} catch (error) {
|
|
close();
|
|
errorAutoClose("An error occurred while creating user level.");
|
|
console.error("Error creating user level:", error);
|
|
}
|
|
};
|
|
|
|
const handleEditUserLevel = (userLevel: UserLevel) => {
|
|
setEditingUserLevel(userLevel);
|
|
setIsUserLevelDialogOpen(true);
|
|
};
|
|
|
|
const handleDeleteUserLevel = async (userLevel: UserLevel) => {
|
|
const result = await Swal.fire({
|
|
title: "Delete User Level?",
|
|
text: `Are you sure you want to delete "${userLevel.name}"? This action cannot be undone.`,
|
|
icon: "warning",
|
|
showCancelButton: true,
|
|
confirmButtonText: "Yes, delete it",
|
|
cancelButtonText: "Cancel",
|
|
customClass: {
|
|
popup: "swal-z-index-9999",
|
|
},
|
|
});
|
|
|
|
if (result.isConfirmed) {
|
|
try {
|
|
// TODO: Implement delete API call
|
|
console.log("Delete user level:", userLevel.id);
|
|
await loadWorkflowAndUserLevels();
|
|
} catch (error) {
|
|
console.error("Error deleting user level:", error);
|
|
}
|
|
}
|
|
};
|
|
|
|
const columns = React.useMemo(
|
|
() => useTableColumns((data) => handleEditUserLevel(data)),
|
|
[],
|
|
);
|
|
|
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
pageIndex: 0,
|
|
pageSize: 10,
|
|
});
|
|
|
|
const table = useReactTable({
|
|
data: userLevels,
|
|
columns,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getPaginationRowModel: getPaginationRowModel(),
|
|
getSortedRowModel: getSortedRowModel(),
|
|
getFilteredRowModel: getFilteredRowModel(),
|
|
onPaginationChange: setPagination,
|
|
state: {
|
|
pagination,
|
|
},
|
|
});
|
|
|
|
const roleId = getCookiesDecrypt("urie");
|
|
if (Number(roleId) !== 1) {
|
|
return null;
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<>
|
|
<SiteBreadcrumb />
|
|
<div className="container mx-auto p-6">
|
|
<div className="text-center py-12">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
|
|
<p className="mt-4 text-gray-600">Loading tenant data...</p>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
if (!tenant) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<SiteBreadcrumb />
|
|
<div className="container mx-auto p-6 space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => router.push("/admin/tenants")}
|
|
>
|
|
<ChevronLeftIcon className="h-4 w-4 mr-2" />
|
|
Back
|
|
</Button>
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-gray-900">
|
|
Edit Tenant: {tenant.name}
|
|
</h1>
|
|
<p className="text-gray-600 mt-2">
|
|
Manage tenant information, workflows, and user levels
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
|
<TabsList className="grid w-full grid-cols-3">
|
|
<TabsTrigger value="profile" className="flex items-center gap-2">
|
|
<SettingsIcon className="h-4 w-4" />
|
|
Tenant Information
|
|
</TabsTrigger>
|
|
<TabsTrigger value="workflows" className="flex items-center gap-2">
|
|
<WorkflowIcon className="h-4 w-4" />
|
|
Approval Workflows
|
|
</TabsTrigger>
|
|
<TabsTrigger
|
|
value="user-levels"
|
|
className="flex items-center gap-2"
|
|
>
|
|
<UsersIcon className="h-4 w-4" />
|
|
User Levels
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Tenant Information Tab */}
|
|
<TabsContent value="profile" className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Tenant Information</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<FormField
|
|
label="Tenant Name"
|
|
name="name"
|
|
type="text"
|
|
placeholder="e.g., Company ABC, Organization XYZ"
|
|
value={formData.name}
|
|
onChange={(value) =>
|
|
setFormData({ ...formData, name: value })
|
|
}
|
|
required
|
|
/>
|
|
<FormField
|
|
label="Description"
|
|
name="description"
|
|
type="textarea"
|
|
placeholder="Brief description of the tenant"
|
|
value={formData.description || ""}
|
|
onChange={(value) =>
|
|
setFormData({
|
|
...formData,
|
|
description: value || undefined,
|
|
})
|
|
}
|
|
/>
|
|
<FormField
|
|
label="Client Type"
|
|
name="clientType"
|
|
type="select"
|
|
placeholder="Select client type"
|
|
value={formData.clientType}
|
|
onChange={(value) =>
|
|
setFormData({ ...formData, clientType: value as any })
|
|
}
|
|
options={[
|
|
{ value: "standalone", label: "Standalone" },
|
|
{ value: "parent_client", label: "Parent Client" },
|
|
{ value: "sub_client", label: "Sub Client" },
|
|
]}
|
|
required
|
|
/>
|
|
{formData.clientType === "sub_client" && (
|
|
<FormField
|
|
label="Parent Tenant"
|
|
name="parentClientId"
|
|
type="select"
|
|
placeholder="Select parent tenant"
|
|
value={formData.parentClientId || "none"}
|
|
onChange={(value) =>
|
|
setFormData({
|
|
...formData,
|
|
parentClientId: value === "none" ? undefined : value,
|
|
})
|
|
}
|
|
options={[
|
|
{ value: "none", label: "No Parent Tenant" },
|
|
...parentTenants
|
|
.filter((t) => t.id !== tenantId)
|
|
.map((t) => ({
|
|
value: t.id,
|
|
label: t.name,
|
|
})),
|
|
]}
|
|
required
|
|
/>
|
|
)}
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
label="Max Users"
|
|
name="maxUsers"
|
|
type="number"
|
|
placeholder="e.g., 100"
|
|
value={formData.maxUsers?.toString() || ""}
|
|
onChange={(value) =>
|
|
setFormData({
|
|
...formData,
|
|
maxUsers: value ? Number(value) : undefined,
|
|
})
|
|
}
|
|
helpText="Maximum number of users allowed"
|
|
/>
|
|
<FormField
|
|
label="Max Storage (MB)"
|
|
name="maxStorage"
|
|
type="number"
|
|
placeholder="e.g., 10000"
|
|
value={formData.maxStorage?.toString() || ""}
|
|
onChange={(value) =>
|
|
setFormData({
|
|
...formData,
|
|
maxStorage: value ? Number(value) : undefined,
|
|
})
|
|
}
|
|
helpText="Maximum storage in MB"
|
|
/>
|
|
</div>
|
|
<FormField
|
|
label="Address"
|
|
name="address"
|
|
type="textarea"
|
|
placeholder="Tenant address"
|
|
value={formData.address || ""}
|
|
onChange={(value) =>
|
|
setFormData({ ...formData, address: value || undefined })
|
|
}
|
|
/>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
label="Phone Number"
|
|
name="phoneNumber"
|
|
type="tel"
|
|
placeholder="e.g., +62 123 456 7890"
|
|
value={formData.phoneNumber || ""}
|
|
onChange={(value) =>
|
|
setFormData({
|
|
...formData,
|
|
phoneNumber: value || undefined,
|
|
})
|
|
}
|
|
/>
|
|
<FormField
|
|
label="Website"
|
|
name="website"
|
|
type="url"
|
|
placeholder="e.g., https://example.com"
|
|
value={formData.website || ""}
|
|
onChange={(value) =>
|
|
setFormData({ ...formData, website: value || undefined })
|
|
}
|
|
/>
|
|
</div>
|
|
<FormField
|
|
label="Status"
|
|
name="isActive"
|
|
type="select"
|
|
placeholder="Select status"
|
|
value={formData.isActive ? "active" : "inactive"}
|
|
onChange={(value) =>
|
|
setFormData({ ...formData, isActive: value === "active" })
|
|
}
|
|
options={[
|
|
{ value: "active", label: "Active" },
|
|
{ value: "inactive", label: "Inactive" },
|
|
]}
|
|
required
|
|
/>
|
|
<div className="flex justify-end pt-4 border-t">
|
|
<Button
|
|
onClick={handleSaveTenantInfo}
|
|
disabled={isSaving}
|
|
className="flex items-center gap-2"
|
|
>
|
|
<SaveIcon className="h-4 w-4" />
|
|
{isSaving ? "Saving..." : "Save Tenant Information"}
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* Approval Workflows Tab */}
|
|
<TabsContent value="workflows" className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<h2 className="text-2xl font-semibold">
|
|
Approval Workflow Setup
|
|
</h2>
|
|
{workflow && !isEditingWorkflow && (
|
|
<Button
|
|
onClick={() => setIsEditingWorkflow(true)}
|
|
className="flex items-center gap-2"
|
|
>
|
|
<SettingsIcon className="h-4 w-4" />
|
|
Edit Workflow
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{isEditingWorkflow ? (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
<span>Setup Approval Workflow</span>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setIsEditingWorkflow(false)}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ApprovalWorkflowForm
|
|
initialData={
|
|
workflow
|
|
? {
|
|
name: workflow.workflow.name,
|
|
description: workflow.workflow.description,
|
|
isDefault: workflow.workflow.isDefault,
|
|
isActive: workflow.workflow.isActive,
|
|
requiresApproval:
|
|
workflow.workflow.requiresApproval,
|
|
autoPublish: workflow.workflow.autoPublish,
|
|
steps:
|
|
workflow.steps?.map((step) => ({
|
|
stepOrder: step.stepOrder,
|
|
stepName: step.stepName,
|
|
requiredUserLevelId: step.requiredUserLevelId,
|
|
canSkip: step.canSkip,
|
|
autoApproveAfterHours:
|
|
step.autoApproveAfterHours,
|
|
isActive: step.isActive,
|
|
conditionType: step.conditionType,
|
|
conditionValue: step.conditionValue,
|
|
})) || [],
|
|
clientApprovalSettings: {
|
|
approvalExemptCategories:
|
|
workflow.clientSettings
|
|
.exemptCategoriesDetails || [],
|
|
approvalExemptRoles:
|
|
workflow.clientSettings.exemptRolesDetails ||
|
|
[],
|
|
approvalExemptUsers:
|
|
workflow.clientSettings.exemptUsersDetails ||
|
|
[],
|
|
autoPublishArticles:
|
|
workflow.clientSettings.autoPublishArticles,
|
|
isActive: workflow.clientSettings.isActive,
|
|
requireApprovalFor:
|
|
workflow.clientSettings.requireApprovalFor ||
|
|
[],
|
|
requiresApproval:
|
|
workflow.clientSettings.requiresApproval,
|
|
skipApprovalFor:
|
|
workflow.clientSettings.skipApprovalFor || [],
|
|
},
|
|
}
|
|
: undefined
|
|
}
|
|
workflowId={workflow?.workflow.id}
|
|
onSave={handleWorkflowSave}
|
|
onCancel={() => setIsEditingWorkflow(false)}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
) : workflow ? (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
<span>{workflow.workflow.name}</span>
|
|
<div className="flex items-center gap-2">
|
|
{workflow.workflow.isDefault && (
|
|
<span className="px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded-full">
|
|
Default
|
|
</span>
|
|
)}
|
|
{workflow.workflow.isActive ? (
|
|
<span className="px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">
|
|
Active
|
|
</span>
|
|
) : (
|
|
<span className="px-2 py-1 text-xs bg-gray-100 text-gray-800 rounded-full">
|
|
Inactive
|
|
</span>
|
|
)}
|
|
</div>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-gray-600 text-sm mb-4">
|
|
{workflow.workflow.description}
|
|
</p>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<div className="text-2xl font-bold text-blue-600">
|
|
{workflow.workflow.totalSteps}
|
|
</div>
|
|
<div className="text-sm text-gray-600">Total Steps</div>
|
|
</div>
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<div className="text-2xl font-bold text-green-600">
|
|
{workflow.workflow.activeSteps}
|
|
</div>
|
|
<div className="text-sm text-gray-600">Active Steps</div>
|
|
</div>
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<div
|
|
className={`text-2xl font-bold ${workflow.workflow.requiresApproval ? "text-green-600" : "text-red-600"}`}
|
|
>
|
|
{workflow.workflow.requiresApproval ? "Yes" : "No"}
|
|
</div>
|
|
<div className="text-sm text-gray-600">
|
|
Requires Approval
|
|
</div>
|
|
</div>
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<div
|
|
className={`text-2xl font-bold ${workflow.workflow.autoPublish ? "text-green-600" : "text-red-600"}`}
|
|
>
|
|
{workflow.workflow.autoPublish ? "Yes" : "No"}
|
|
</div>
|
|
<div className="text-sm text-gray-600">Auto Publish</div>
|
|
</div>
|
|
</div>
|
|
{workflow.steps && workflow.steps.length > 0 && (
|
|
<div>
|
|
<h4 className="text-lg font-medium mb-3">
|
|
Workflow Steps
|
|
</h4>
|
|
<div className="space-y-2">
|
|
{workflow.steps.map((step: any, index: number) => (
|
|
<div
|
|
key={index}
|
|
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-sm font-medium">
|
|
{step.stepOrder}
|
|
</div>
|
|
<div>
|
|
<div className="font-medium">
|
|
{step.stepName}
|
|
</div>
|
|
<div className="text-sm text-gray-600">
|
|
Required Level: {step.requiredUserLevelName}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{step.isActive ? (
|
|
<span className="px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">
|
|
Active
|
|
</span>
|
|
) : (
|
|
<span className="px-2 py-1 text-xs bg-gray-100 text-gray-800 rounded-full">
|
|
Inactive
|
|
</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<Card>
|
|
<CardContent className="flex items-center justify-center py-12">
|
|
<div className="text-center">
|
|
<WorkflowIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
No Workflow Found
|
|
</h3>
|
|
<p className="text-gray-500 mb-4">
|
|
No approval workflow has been set up for this tenant yet.
|
|
</p>
|
|
<Button onClick={() => setIsEditingWorkflow(true)}>
|
|
<PlusIcon className="h-4 w-4 mr-2" />
|
|
Create Workflow
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</TabsContent>
|
|
|
|
{/* User Levels Tab */}
|
|
<TabsContent value="user-levels" className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<h2 className="text-2xl font-semibold">User Levels Management</h2>
|
|
<Dialog
|
|
open={isUserLevelDialogOpen}
|
|
onOpenChange={setIsUserLevelDialogOpen}
|
|
>
|
|
<DialogTrigger asChild>
|
|
<Button
|
|
className="flex items-center gap-2"
|
|
onClick={() => {
|
|
setEditingUserLevel(null);
|
|
setIsUserLevelDialogOpen(true);
|
|
}}
|
|
>
|
|
<PlusIcon className="h-4 w-4" />
|
|
Create User Level
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{editingUserLevel
|
|
? `Edit User Level: ${editingUserLevel.name}`
|
|
: "Create New User Level"}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<UserLevelsForm
|
|
mode="single"
|
|
initialData={
|
|
editingUserLevel
|
|
? {
|
|
// id: editingUserLevel.id,
|
|
name: editingUserLevel.name,
|
|
aliasName: editingUserLevel.aliasName,
|
|
levelNumber: editingUserLevel.levelNumber,
|
|
parentLevelId: editingUserLevel.parentLevelId || 0,
|
|
provinceId: editingUserLevel.provinceId,
|
|
group: editingUserLevel.group || "",
|
|
isApprovalActive: editingUserLevel.isApprovalActive,
|
|
isActive: editingUserLevel.isActive,
|
|
}
|
|
: undefined
|
|
}
|
|
onSave={handleUserLevelSave}
|
|
onCancel={() => {
|
|
setIsUserLevelDialogOpen(false);
|
|
setEditingUserLevel(null);
|
|
}}
|
|
/>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
|
|
{userLevels.length > 0 ? (
|
|
<Card>
|
|
<CardContent className="p-0">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
{table
|
|
.getHeaderGroups()
|
|
.map((headerGroup) =>
|
|
headerGroup.headers.map((header) => (
|
|
<TableHead key={header.id}>
|
|
{header.isPlaceholder
|
|
? null
|
|
: flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext(),
|
|
)}
|
|
</TableHead>
|
|
)),
|
|
)}
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{table.getRowModel().rows?.length ? (
|
|
table.getRowModel().rows.map((row) => (
|
|
<TableRow key={row.id}>
|
|
{row.getVisibleCells().map((cell) => (
|
|
<TableCell key={cell.id}>
|
|
{flexRender(
|
|
cell.column.columnDef.cell,
|
|
cell.getContext(),
|
|
)}
|
|
</TableCell>
|
|
))}
|
|
<TableCell className="text-right">
|
|
<div className="flex items-center justify-end gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() =>
|
|
handleEditUserLevel(row.original)
|
|
}
|
|
>
|
|
<EditIcon className="h-4 w-4 mr-2" />
|
|
Edit
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="text-red-600 hover:text-red-700 hover:bg-red-50"
|
|
onClick={() =>
|
|
handleDeleteUserLevel(row.original)
|
|
}
|
|
>
|
|
<DeleteIcon className="h-4 w-4 mr-2" />
|
|
Delete
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
) : (
|
|
<TableRow>
|
|
<TableCell
|
|
colSpan={columns.length + 1}
|
|
className="h-24 text-center"
|
|
>
|
|
No results.
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
<div className="p-4">
|
|
<TablePagination
|
|
table={table}
|
|
totalData={totalData}
|
|
totalPage={totalPage}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<Card>
|
|
<CardContent className="flex items-center justify-center py-12">
|
|
<div className="text-center">
|
|
<UsersIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
No User Levels Found
|
|
</h3>
|
|
<p className="text-gray-500 mb-4">
|
|
Create your first user level to define user hierarchy
|
|
</p>
|
|
<Button
|
|
onClick={() => {
|
|
setEditingUserLevel(null);
|
|
setIsUserLevelDialogOpen(true);
|
|
}}
|
|
>
|
|
<PlusIcon className="h-4 w-4 mr-2" />
|
|
Create User Level
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|