897 lines
39 KiB
TypeScript
897 lines
39 KiB
TypeScript
"use client";
|
|
import React, { useState } from "react";
|
|
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 {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import {
|
|
PlusIcon,
|
|
SettingsIcon,
|
|
UsersIcon,
|
|
WorkflowIcon,
|
|
DotsIcon,
|
|
DeleteIcon,
|
|
} from "@/components/icons";
|
|
import { ApprovalWorkflowForm } from "@/components/form/ApprovalWorkflowForm";
|
|
import { UserLevelsForm } from "@/components/form/UserLevelsForm";
|
|
import { useWorkflowModal } from "@/components/modals/WorkflowModalProvider";
|
|
import { useWorkflowStatusCheck } from "@/hooks/useWorkflowStatusCheck";
|
|
import {
|
|
CreateApprovalWorkflowWithClientSettingsRequest,
|
|
UserLevelsCreateRequest,
|
|
UserLevel,
|
|
getUserLevels,
|
|
getApprovalWorkflowComprehensiveDetails,
|
|
ComprehensiveWorkflowResponse,
|
|
createUserLevel,
|
|
} from "@/service/approval-workflows";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import {
|
|
flexRender,
|
|
getCoreRowModel,
|
|
getFilteredRowModel,
|
|
getPaginationRowModel,
|
|
getSortedRowModel,
|
|
PaginationState,
|
|
useReactTable,
|
|
} from "@tanstack/react-table";
|
|
import useTableColumns from "./component/columns";
|
|
import TablePagination from "@/components/table/table-pagination";
|
|
import TenantSettingsPageTable from "./component/tenant-settings-content-table";
|
|
|
|
function TenantSettingsContent() {
|
|
const [activeTab, setActiveTab] = useState("workflows");
|
|
const [isUserLevelDialogOpen, setIsUserLevelDialogOpen] = useState(false);
|
|
const [workflow, setWorkflow] =
|
|
useState<ComprehensiveWorkflowResponse | null>(null);
|
|
const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [isEditingWorkflow, setIsEditingWorkflow] = useState(false);
|
|
const [editingUserLevel, setEditingUserLevel] = useState<UserLevel | null>(
|
|
null,
|
|
);
|
|
const { checkWorkflowStatus } = useWorkflowStatusCheck();
|
|
const { showWorkflowModal } = useWorkflowModal();
|
|
|
|
// Load data on component mount
|
|
React.useEffect(() => {
|
|
loadData();
|
|
}, []);
|
|
|
|
const loadData = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const [comprehensiveWorkflowRes, userLevelsRes] = await Promise.all([
|
|
getApprovalWorkflowComprehensiveDetails(),
|
|
getUserLevels(),
|
|
]);
|
|
|
|
if (!comprehensiveWorkflowRes?.error) {
|
|
setWorkflow(comprehensiveWorkflowRes?.data?.data || null);
|
|
} else {
|
|
setWorkflow(null);
|
|
}
|
|
|
|
if (!userLevelsRes?.error) {
|
|
setUserLevels(userLevelsRes?.data?.data || []);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error loading data:", error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleWorkflowSave = async (
|
|
data: CreateApprovalWorkflowWithClientSettingsRequest,
|
|
) => {
|
|
setIsEditingWorkflow(false);
|
|
await loadData(); // Reload data after saving
|
|
};
|
|
|
|
const handleUserLevelSave = async (data: UserLevelsCreateRequest) => {
|
|
try {
|
|
const response = await createUserLevel(data);
|
|
|
|
if (response?.error) {
|
|
console.error("Error creating user level:", response?.message);
|
|
// You can add error handling here (e.g., show error message)
|
|
} else {
|
|
console.log("User level created successfully:", response);
|
|
// You can add success handling here (e.g., show success message)
|
|
}
|
|
} catch (error) {
|
|
console.error("Error creating user level:", error);
|
|
}
|
|
|
|
setIsUserLevelDialogOpen(false);
|
|
setEditingUserLevel(null);
|
|
await loadData(); // Reload data after saving
|
|
};
|
|
|
|
const handleEditUserLevel = (userLevel: UserLevel) => {
|
|
setEditingUserLevel(userLevel);
|
|
setIsUserLevelDialogOpen(true);
|
|
};
|
|
|
|
const handleDeleteUserLevel = async (userLevel: UserLevel) => {
|
|
if (
|
|
window.confirm(
|
|
`Are you sure you want to delete "${userLevel.name}"? This action cannot be undone.`,
|
|
)
|
|
) {
|
|
try {
|
|
// TODO: Implement delete API call
|
|
console.log("Delete user level:", userLevel.id);
|
|
await loadData(); // Reload data after deletion
|
|
} catch (error) {
|
|
console.error("Error deleting user level:", error);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleBulkUserLevelSave = async (data: UserLevelsCreateRequest[]) => {
|
|
setIsUserLevelDialogOpen(false);
|
|
await loadData(); // Reload data after saving
|
|
};
|
|
|
|
return (
|
|
<div className="container mx-auto p-6 space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-gray-900">Tenant Settings</h1>
|
|
<p className="text-gray-600 mt-2">
|
|
Manage approval workflows and user levels for your tenant
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<SettingsIcon className="h-6 w-6 text-gray-500" />
|
|
<Button variant="outline" size="sm" onClick={checkWorkflowStatus}>
|
|
Check Workflow Status
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => showWorkflowModal({ hasWorkflowSetup: false })}
|
|
className="bg-red-50 text-red-600 border-red-200 hover:bg-red-100"
|
|
>
|
|
Test Modal
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
|
<TabsList className="grid w-full grid-cols-2">
|
|
<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>
|
|
|
|
{/* 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
|
|
variant="outline"
|
|
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 Overview */}
|
|
{workflow.steps && workflow.steps.length > 0 && (
|
|
<div className="mb-6">
|
|
<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-500">
|
|
{step.conditionType &&
|
|
`Condition: ${step.conditionType}`}
|
|
{step.autoApproveAfterHours &&
|
|
` • Auto-approve after ${step.autoApproveAfterHours}h`}
|
|
{step.requiredUserLevelName &&
|
|
` • Required Level: ${step.requiredUserLevelName}`}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{step.canSkip && (
|
|
<span className="px-2 py-1 text-xs bg-yellow-100 text-yellow-800 rounded-full">
|
|
Can Skip
|
|
</span>
|
|
)}
|
|
{step.isParallel && (
|
|
<span className="px-2 py-1 text-xs bg-purple-100 text-purple-800 rounded-full">
|
|
Parallel
|
|
</span>
|
|
)}
|
|
{step.isActive && (
|
|
<span className="px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">
|
|
Active
|
|
</span>
|
|
)}
|
|
{step.isFirstStep && (
|
|
<span className="px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded-full">
|
|
First Step
|
|
</span>
|
|
)}
|
|
{step.isLastStep && (
|
|
<span className="px-2 py-1 text-xs bg-orange-100 text-orange-800 rounded-full">
|
|
Last Step
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Client Settings */}
|
|
<div className="mb-6">
|
|
<h4 className="text-lg font-medium mb-3">Client Settings</h4>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Default Workflow
|
|
</div>
|
|
<div className="text-sm text-gray-600">
|
|
{workflow.clientSettings.defaultWorkflowName}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Auto Publish Articles
|
|
</div>
|
|
<div
|
|
className={`text-sm font-medium ${workflow.clientSettings.autoPublishArticles ? "text-green-600" : "text-red-600"}`}
|
|
>
|
|
{workflow.clientSettings.autoPublishArticles
|
|
? "Yes"
|
|
: "No"}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Requires Approval
|
|
</div>
|
|
<div
|
|
className={`text-sm font-medium ${workflow.clientSettings.requiresApproval ? "text-green-600" : "text-red-600"}`}
|
|
>
|
|
{workflow.clientSettings.requiresApproval
|
|
? "Yes"
|
|
: "No"}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Settings Active
|
|
</div>
|
|
<div
|
|
className={`text-sm font-medium ${workflow.clientSettings.isActive ? "text-green-600" : "text-red-600"}`}
|
|
>
|
|
{workflow.clientSettings.isActive ? "Yes" : "No"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Statistics */}
|
|
<div className="mb-6">
|
|
<h4 className="text-lg font-medium mb-3">
|
|
Workflow Statistics
|
|
</h4>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Total Articles Processed
|
|
</div>
|
|
<div className="text-2xl font-bold text-blue-600">
|
|
{workflow.statistics.totalArticlesProcessed}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Pending Articles
|
|
</div>
|
|
<div className="text-2xl font-bold text-yellow-600">
|
|
{workflow.statistics.pendingArticles}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Approved Articles
|
|
</div>
|
|
<div className="text-2xl font-bold text-green-600">
|
|
{workflow.statistics.approvedArticles}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Rejected Articles
|
|
</div>
|
|
<div className="text-2xl font-bold text-red-600">
|
|
{workflow.statistics.rejectedArticles}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Average Processing Time
|
|
</div>
|
|
<div className="text-2xl font-bold text-purple-600">
|
|
{workflow.statistics.averageProcessingTime}h
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Most Active Step
|
|
</div>
|
|
<div className="text-sm text-gray-600">
|
|
{workflow.statistics.mostActiveStep || "N/A"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Workflow Metadata */}
|
|
<div className="mb-6">
|
|
<h4 className="text-lg font-medium mb-3">
|
|
Workflow Information
|
|
</h4>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Client ID
|
|
</div>
|
|
<div className="text-sm text-gray-600 font-mono">
|
|
{workflow.workflow.clientId}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Created At
|
|
</div>
|
|
<div className="text-sm text-gray-600">
|
|
{new Date(workflow.workflow.createdAt).toLocaleString()}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Updated At
|
|
</div>
|
|
<div className="text-sm text-gray-600">
|
|
{new Date(workflow.workflow.updatedAt).toLocaleString()}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Workflow ID
|
|
</div>
|
|
<div className="text-sm text-gray-600 font-mono">
|
|
{workflow.workflow.id}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Has Branches
|
|
</div>
|
|
<div
|
|
className={`text-sm font-medium ${workflow.workflow.hasBranches ? "text-green-600" : "text-gray-600"}`}
|
|
>
|
|
{workflow.workflow.hasBranches ? "Yes" : "No"}
|
|
</div>
|
|
</div>
|
|
<div className="p-3 bg-gray-50 rounded-lg">
|
|
<div className="text-sm font-medium text-gray-700 mb-1">
|
|
Max Step Order
|
|
</div>
|
|
<div className="text-sm text-gray-600">
|
|
{workflow.workflow.maxStepOrder}
|
|
</div>
|
|
</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 Configured
|
|
</h3>
|
|
<p className="text-gray-500 mb-4">
|
|
Set up your approval workflow to manage content approval
|
|
process
|
|
</p>
|
|
<Button onClick={() => setIsEditingWorkflow(true)}>
|
|
<PlusIcon className="h-4 w-4 mr-2" />
|
|
Setup 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</h2>
|
|
<Dialog
|
|
open={isUserLevelDialogOpen}
|
|
onOpenChange={setIsUserLevelDialogOpen}
|
|
>
|
|
<DialogTrigger asChild>
|
|
<Button className="flex items-center gap-2">
|
|
<PlusIcon className="h-4 w-4" />
|
|
Create User Level
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="md:max-w-6xl 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>
|
|
|
|
{/* User Levels Summary */}
|
|
{userLevels.length > 0 && (
|
|
<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">
|
|
{userLevels.length}
|
|
</div>
|
|
<div className="text-sm text-gray-600">Total User Levels</div>
|
|
</div>
|
|
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<div className="text-2xl font-bold text-green-600">
|
|
{userLevels.filter((ul) => ul.isActive).length}
|
|
</div>
|
|
<div className="text-sm text-gray-600">Active Levels</div>
|
|
</div>
|
|
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<div className="text-2xl font-bold text-purple-600">
|
|
{userLevels.filter((ul) => ul.isApprovalActive).length}
|
|
</div>
|
|
<div className="text-sm text-gray-600">Approval Active</div>
|
|
</div>
|
|
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<div className="text-2xl font-bold text-orange-600">
|
|
{userLevels.filter((ul) => ul.parentLevelId).length}
|
|
</div>
|
|
<div className="text-sm text-gray-600">Child Levels</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* User Levels Hierarchy */}
|
|
{userLevels.length > 0 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<UsersIcon className="h-5 w-5" />
|
|
User Levels Hierarchy
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-3">
|
|
{userLevels
|
|
.filter((ul) => !ul.parentLevelId)
|
|
.sort((a, b) => a.levelNumber - b.levelNumber)
|
|
.map((rootLevel) => (
|
|
<div key={rootLevel.id} className="space-y-2">
|
|
{/* Root Level */}
|
|
<div className="flex items-center gap-3 p-3 bg-blue-50 rounded-lg border-l-4 border-blue-500">
|
|
<div className="w-8 h-8 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-sm font-medium">
|
|
{rootLevel.levelNumber}
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="font-medium">{rootLevel.name}</div>
|
|
<div className="text-sm text-gray-500">
|
|
{rootLevel.aliasName} •{" "}
|
|
{rootLevel.group || "No group"}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{rootLevel.isActive && (
|
|
<span className="px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">
|
|
Active
|
|
</span>
|
|
)}
|
|
{rootLevel.isApprovalActive && (
|
|
<span className="px-2 py-1 text-xs bg-purple-100 text-purple-800 rounded-full">
|
|
Approval Active
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => handleEditUserLevel(rootLevel)}
|
|
className="h-8 w-8 p-0 border-gray-300 hover:border-blue-500 hover:bg-blue-50"
|
|
title="Edit User Level"
|
|
>
|
|
<DotsIcon className="h-4 w-4 text-gray-600 hover:text-blue-600" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => handleDeleteUserLevel(rootLevel)}
|
|
className="h-8 w-8 p-0 border-gray-300 hover:border-red-500 hover:bg-red-50"
|
|
title="Delete User Level"
|
|
>
|
|
<DeleteIcon className="h-4 w-4 text-gray-600 hover:text-red-600" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Child Levels */}
|
|
{userLevels
|
|
.filter((ul) => ul.parentLevelId === rootLevel.id)
|
|
.sort((a, b) => a.levelNumber - b.levelNumber)
|
|
.map((childLevel) => (
|
|
<div
|
|
key={childLevel.id}
|
|
className="ml-8 flex items-center gap-3 p-3 bg-gray-50 rounded-lg border-l-4 border-gray-300"
|
|
>
|
|
<div className="w-6 h-6 bg-gray-100 text-gray-600 rounded-full flex items-center justify-center text-xs font-medium">
|
|
{childLevel.levelNumber}
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="font-medium text-sm">
|
|
{childLevel.name}
|
|
</div>
|
|
<div className="text-xs text-gray-500">
|
|
{childLevel.aliasName} •{" "}
|
|
{childLevel.group || "No group"}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
{childLevel.isActive && (
|
|
<span className="px-1 py-0.5 text-xs bg-green-100 text-green-800 rounded-full">
|
|
Active
|
|
</span>
|
|
)}
|
|
{childLevel.isApprovalActive && (
|
|
<span className="px-1 py-0.5 text-xs bg-purple-100 text-purple-800 rounded-full">
|
|
Approval
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() =>
|
|
handleEditUserLevel(childLevel)
|
|
}
|
|
className="h-7 w-7 p-0 border-gray-300 hover:border-blue-500 hover:bg-blue-50"
|
|
title="Edit User Level"
|
|
>
|
|
<DotsIcon className="h-3.5 w-3.5 text-gray-600 hover:text-blue-600" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() =>
|
|
handleDeleteUserLevel(childLevel)
|
|
}
|
|
className="h-7 w-7 p-0 border-gray-300 hover:border-red-500 hover:bg-red-50"
|
|
title="Delete User Level"
|
|
>
|
|
<DeleteIcon className="h-3.5 w-3.5 text-gray-600 hover:text-red-600" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{userLevels.length > 0 ? userLevels.map((userLevel) => (
|
|
<Card key={userLevel.id} className="hover:shadow-lg transition-shadow">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
<span className="truncate">{userLevel.name}</span>
|
|
<div className="flex items-center gap-2">
|
|
<span className="px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded-full">
|
|
Level {userLevel.levelNumber}
|
|
</span>
|
|
{userLevel.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>
|
|
<div className="space-y-2 mb-4">
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className="text-gray-500">Alias:</span>
|
|
<span className="font-mono text-xs bg-gray-100 px-2 py-1 rounded">
|
|
{userLevel.aliasName}
|
|
</span>
|
|
</div>
|
|
|
|
{userLevel.group && (
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className="text-gray-500">Group:</span>
|
|
<span className="font-medium">{userLevel.group}</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className="text-gray-500">Approval Active:</span>
|
|
<span className={`font-medium ${userLevel.isApprovalActive ? 'text-green-600' : 'text-red-600'}`}>
|
|
{userLevel.isApprovalActive ? 'Yes' : 'No'}
|
|
</span>
|
|
</div>
|
|
|
|
{userLevel.parentLevelId && (
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className="text-gray-500">Parent Level:</span>
|
|
<span className="font-medium">
|
|
{ userLevels.length > 0 ? userLevels.find(ul => ul.id === userLevel.parentLevelId)?.name || `Level ${userLevel.parentLevelId}` : `Level ${userLevel.parentLevelId}`}
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{userLevel.provinceId && (
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className="text-gray-500">Province:</span>
|
|
<span className="font-medium">Province {userLevel.provinceId}</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className="text-gray-500">Created:</span>
|
|
<span className="font-medium text-xs">
|
|
{userLevel.createdAt ? new Date(userLevel.createdAt).toLocaleDateString() : 'N/A'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<Button variant="outline" size="sm" className="flex-1">
|
|
Edit
|
|
</Button>
|
|
<Button variant="outline" size="sm" className="flex-1">
|
|
Users
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)) : ''}
|
|
</div> */}
|
|
|
|
{userLevels.length === 0 && !isLoading && (
|
|
<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 approval hierarchy
|
|
</p>
|
|
<Button onClick={() => setIsUserLevelDialogOpen(true)}>
|
|
<PlusIcon className="h-4 w-4 mr-2" />
|
|
Create User Level
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function TenantSettingsPage() {
|
|
return <TenantSettingsPageTable />;
|
|
}
|