fix: ui and checkbox in all content

This commit is contained in:
Sabda Yagra 2026-01-23 18:12:56 +07:00
parent c793844840
commit 02a253ccdc
9 changed files with 369 additions and 179 deletions

View File

@ -412,7 +412,7 @@ const useTableColumns = (onEdit?: (data: any) => void) => {
) : detailData ? ( ) : detailData ? (
<Tabs defaultValue="basic" className="w-full mt-4"> <Tabs defaultValue="basic" className="w-full mt-4">
<TabsList className="grid w-full grid-cols-3"> <TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="basic">Basic Information</TabsTrigger> <TabsTrigger className="border border-black" value="basic">Basic Information</TabsTrigger>
<TabsTrigger value="menus">Menu Access</TabsTrigger> <TabsTrigger value="menus">Menu Access</TabsTrigger>
<TabsTrigger value="actions">Action Access</TabsTrigger> <TabsTrigger value="actions">Action Access</TabsTrigger>
</TabsList> </TabsList>
@ -467,7 +467,7 @@ const useTableColumns = (onEdit?: (data: any) => void) => {
</div> </div>
<div> <div>
<span className="text-sm font-medium text-gray-600">Created At:</span> <span className="text-sm font-medium text-gray-600">Created At:</span>
<p className="text-base text-sm"> <p className="text-sm">
{detailData.createdAt {detailData.createdAt
? new Date(detailData.createdAt).toLocaleString("id-ID") ? new Date(detailData.createdAt).toLocaleString("id-ID")
: "-"} : "-"}
@ -475,7 +475,7 @@ const useTableColumns = (onEdit?: (data: any) => void) => {
</div> </div>
<div> <div>
<span className="text-sm font-medium text-gray-600">Updated At:</span> <span className="text-sm font-medium text-gray-600">Updated At:</span>
<p className="text-base text-sm"> <p className="text-sm">
{detailData.updatedAt {detailData.updatedAt
? new Date(detailData.updatedAt).toLocaleString("id-ID") ? new Date(detailData.updatedAt).toLocaleString("id-ID")
: "-"} : "-"}
@ -595,7 +595,7 @@ const useTableColumns = (onEdit?: (data: any) => void) => {
)} )}
<div className="flex justify-end mt-5"> <div className="flex justify-end mt-5">
<Button onClick={() => setIsDialogOpen(false)}>Tutup</Button> <Button variant="outline" onClick={() => setIsDialogOpen(false)}>Tutup</Button>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -60,7 +60,10 @@ import { close, loading } from "@/config/swal";
import DetailTenant from "@/components/form/tenant/tenant-detail-update-form"; import DetailTenant from "@/components/form/tenant/tenant-detail-update-form";
function TenantSettingsContentTable() { function TenantSettingsContentTable() {
const [activeTab, setActiveTab] = useLocalStorage('tenant-settings-active-tab', 'profile'); const [activeTab, setActiveTab] = useLocalStorage(
"tenant-settings-active-tab",
"profile",
);
const [isUserLevelDialogOpen, setIsUserLevelDialogOpen] = useState(false); const [isUserLevelDialogOpen, setIsUserLevelDialogOpen] = useState(false);
const [workflow, setWorkflow] = const [workflow, setWorkflow] =
useState<ComprehensiveWorkflowResponse | null>(null); useState<ComprehensiveWorkflowResponse | null>(null);
@ -120,7 +123,7 @@ function TenantSettingsContentTable() {
}; };
const handleWorkflowSave = async ( const handleWorkflowSave = async (
data: CreateApprovalWorkflowWithClientSettingsRequest data: CreateApprovalWorkflowWithClientSettingsRequest,
) => { ) => {
setIsEditingWorkflow(false); setIsEditingWorkflow(false);
await loadData(); await loadData();
@ -160,7 +163,7 @@ function TenantSettingsContentTable() {
const columns = React.useMemo( const columns = React.useMemo(
() => useTableColumns((data) => handleEditUserLevel(data)), () => useTableColumns((data) => handleEditUserLevel(data)),
[] [],
); );
const [showData, setShowData] = React.useState("10"); const [showData, setShowData] = React.useState("10");
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
@ -252,7 +255,7 @@ function TenantSettingsContentTable() {
Approval Workflow Setup Approval Workflow Setup
</h2> </h2>
{workflow && !isEditingWorkflow && ( {workflow && !isEditingWorkflow && (
<Button <Button variant="outline"
onClick={() => setIsEditingWorkflow(true)} onClick={() => setIsEditingWorkflow(true)}
className="flex items-center gap-2" className="flex items-center gap-2"
> >
@ -667,7 +670,10 @@ function TenantSettingsContentTable() {
onOpenChange={setIsUserLevelDialogOpen} onOpenChange={setIsUserLevelDialogOpen}
> >
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="flex items-center gap-2"> <Button
variant="outline"
className="flex items-center gap-2 hover:bg-muted focus-visible:bg-muted"
>
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
Create User Level Create User Level
</Button> </Button>
@ -830,7 +836,7 @@ function TenantSettingsContentTable() {
? null ? null
: flexRender( : flexRender(
header.column.columnDef.header, header.column.columnDef.header,
header.getContext() header.getContext(),
)} )}
</TableHead> </TableHead>
))} ))}
@ -849,7 +855,7 @@ function TenantSettingsContentTable() {
<TableCell key={cell.id}> <TableCell key={cell.id}>
{flexRender( {flexRender(
cell.column.columnDef.cell, cell.column.columnDef.cell,
cell.getContext() cell.getContext(),
)} )}
</TableCell> </TableCell>
))} ))}

View File

@ -3,8 +3,21 @@ import React, { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import {
import { PlusIcon, SettingsIcon, UsersIcon, WorkflowIcon, DotsIcon, DeleteIcon } from "@/components/icons"; 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 { ApprovalWorkflowForm } from "@/components/form/ApprovalWorkflowForm";
import { UserLevelsForm } from "@/components/form/UserLevelsForm"; import { UserLevelsForm } from "@/components/form/UserLevelsForm";
import { useWorkflowModal } from "@/components/modals/WorkflowModalProvider"; import { useWorkflowModal } from "@/components/modals/WorkflowModalProvider";
@ -42,11 +55,14 @@ import TenantSettingsPageTable from "./component/tenant-settings-content-table";
function TenantSettingsContent() { function TenantSettingsContent() {
const [activeTab, setActiveTab] = useState("workflows"); const [activeTab, setActiveTab] = useState("workflows");
const [isUserLevelDialogOpen, setIsUserLevelDialogOpen] = useState(false); const [isUserLevelDialogOpen, setIsUserLevelDialogOpen] = useState(false);
const [workflow, setWorkflow] = useState<ComprehensiveWorkflowResponse | null>(null); const [workflow, setWorkflow] =
useState<ComprehensiveWorkflowResponse | null>(null);
const [userLevels, setUserLevels] = useState<UserLevel[]>([]); const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isEditingWorkflow, setIsEditingWorkflow] = useState(false); const [isEditingWorkflow, setIsEditingWorkflow] = useState(false);
const [editingUserLevel, setEditingUserLevel] = useState<UserLevel | null>(null); const [editingUserLevel, setEditingUserLevel] = useState<UserLevel | null>(
null,
);
const { checkWorkflowStatus } = useWorkflowStatusCheck(); const { checkWorkflowStatus } = useWorkflowStatusCheck();
const { showWorkflowModal } = useWorkflowModal(); const { showWorkflowModal } = useWorkflowModal();
@ -79,7 +95,9 @@ function TenantSettingsContent() {
} }
}; };
const handleWorkflowSave = async (data: CreateApprovalWorkflowWithClientSettingsRequest) => { const handleWorkflowSave = async (
data: CreateApprovalWorkflowWithClientSettingsRequest,
) => {
setIsEditingWorkflow(false); setIsEditingWorkflow(false);
await loadData(); // Reload data after saving await loadData(); // Reload data after saving
}; };
@ -110,7 +128,11 @@ function TenantSettingsContent() {
}; };
const handleDeleteUserLevel = async (userLevel: UserLevel) => { const handleDeleteUserLevel = async (userLevel: UserLevel) => {
if (window.confirm(`Are you sure you want to delete "${userLevel.name}"? This action cannot be undone.`)) { if (
window.confirm(
`Are you sure you want to delete "${userLevel.name}"? This action cannot be undone.`,
)
) {
try { try {
// TODO: Implement delete API call // TODO: Implement delete API call
console.log("Delete user level:", userLevel.id); console.log("Delete user level:", userLevel.id);
@ -137,11 +159,7 @@ function TenantSettingsContent() {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<SettingsIcon className="h-6 w-6 text-gray-500" /> <SettingsIcon className="h-6 w-6 text-gray-500" />
<Button <Button variant="outline" size="sm" onClick={checkWorkflowStatus}>
variant="outline"
size="sm"
onClick={checkWorkflowStatus}
>
Check Workflow Status Check Workflow Status
</Button> </Button>
<Button <Button
@ -173,6 +191,7 @@ function TenantSettingsContent() {
<h2 className="text-2xl font-semibold">Approval Workflow Setup</h2> <h2 className="text-2xl font-semibold">Approval Workflow Setup</h2>
{workflow && !isEditingWorkflow && ( {workflow && !isEditingWorkflow && (
<Button <Button
variant="outline"
onClick={() => setIsEditingWorkflow(true)} onClick={() => setIsEditingWorkflow(true)}
className="flex items-center gap-2" className="flex items-center gap-2"
> >
@ -197,34 +216,47 @@ function TenantSettingsContent() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ApprovalWorkflowForm <ApprovalWorkflowForm
initialData={workflow ? { initialData={
name: workflow.workflow.name, workflow
description: workflow.workflow.description, ? {
isDefault: workflow.workflow.isDefault, name: workflow.workflow.name,
isActive: workflow.workflow.isActive, description: workflow.workflow.description,
requiresApproval: workflow.workflow.requiresApproval, isDefault: workflow.workflow.isDefault,
autoPublish: workflow.workflow.autoPublish, isActive: workflow.workflow.isActive,
steps: workflow.steps?.map(step => ({ requiresApproval: workflow.workflow.requiresApproval,
stepOrder: step.stepOrder, autoPublish: workflow.workflow.autoPublish,
stepName: step.stepName, steps:
requiredUserLevelId: step.requiredUserLevelId, workflow.steps?.map((step) => ({
canSkip: step.canSkip, stepOrder: step.stepOrder,
autoApproveAfterHours: step.autoApproveAfterHours, stepName: step.stepName,
isActive: step.isActive, requiredUserLevelId: step.requiredUserLevelId,
conditionType: step.conditionType, canSkip: step.canSkip,
conditionValue: step.conditionValue, autoApproveAfterHours: step.autoApproveAfterHours,
})) || [], isActive: step.isActive,
clientApprovalSettings: { conditionType: step.conditionType,
approvalExemptCategories: workflow.clientSettings.exemptCategoriesDetails || [], conditionValue: step.conditionValue,
approvalExemptRoles: workflow.clientSettings.exemptRolesDetails || [], })) || [],
approvalExemptUsers: workflow.clientSettings.exemptUsersDetails || [], clientApprovalSettings: {
autoPublishArticles: workflow.clientSettings.autoPublishArticles, approvalExemptCategories:
isActive: workflow.clientSettings.isActive, workflow.clientSettings.exemptCategoriesDetails ||
requireApprovalFor: workflow.clientSettings.requireApprovalFor || [], [],
requiresApproval: workflow.clientSettings.requiresApproval, approvalExemptRoles:
skipApprovalFor: workflow.clientSettings.skipApprovalFor || [] workflow.clientSettings.exemptRolesDetails || [],
} approvalExemptUsers:
} : undefined} 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} workflowId={workflow?.workflow.id}
onSave={handleWorkflowSave} onSave={handleWorkflowSave}
onCancel={() => setIsEditingWorkflow(false)} onCancel={() => setIsEditingWorkflow(false)}
@ -261,25 +293,35 @@ function TenantSettingsContent() {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6"> <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-center p-4 bg-gray-50 rounded-lg">
<div className="text-2xl font-bold text-blue-600">{workflow.workflow.totalSteps}</div> <div className="text-2xl font-bold text-blue-600">
{workflow.workflow.totalSteps}
</div>
<div className="text-sm text-gray-600">Total Steps</div> <div className="text-sm text-gray-600">Total Steps</div>
</div> </div>
<div className="text-center p-4 bg-gray-50 rounded-lg"> <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-2xl font-bold text-green-600">
{workflow.workflow.activeSteps}
</div>
<div className="text-sm text-gray-600">Active Steps</div> <div className="text-sm text-gray-600">Active Steps</div>
</div> </div>
<div className="text-center p-4 bg-gray-50 rounded-lg"> <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'}`}> <div
{workflow.workflow.requiresApproval ? 'Yes' : 'No'} 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-sm text-gray-600">Requires Approval</div>
</div> </div>
<div className="text-center p-4 bg-gray-50 rounded-lg"> <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'}`}> <div
{workflow.workflow.autoPublish ? 'Yes' : 'No'} className={`text-2xl font-bold ${workflow.workflow.autoPublish ? "text-green-600" : "text-red-600"}`}
>
{workflow.workflow.autoPublish ? "Yes" : "No"}
</div> </div>
<div className="text-sm text-gray-600">Auto Publish</div> <div className="text-sm text-gray-600">Auto Publish</div>
</div> </div>
@ -291,7 +333,10 @@ function TenantSettingsContent() {
<h4 className="text-lg font-medium mb-3">Workflow Steps</h4> <h4 className="text-lg font-medium mb-3">Workflow Steps</h4>
<div className="space-y-2"> <div className="space-y-2">
{workflow.steps.map((step: any, index: number) => ( {workflow.steps.map((step: any, index: number) => (
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"> <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="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"> <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} {step.stepOrder}
@ -299,9 +344,12 @@ function TenantSettingsContent() {
<div> <div>
<div className="font-medium">{step.stepName}</div> <div className="font-medium">{step.stepName}</div>
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
{step.conditionType && `Condition: ${step.conditionType}`} {step.conditionType &&
{step.autoApproveAfterHours && ` • Auto-approve after ${step.autoApproveAfterHours}h`} `Condition: ${step.conditionType}`}
{step.requiredUserLevelName && ` • Required Level: ${step.requiredUserLevelName}`} {step.autoApproveAfterHours &&
` • Auto-approve after ${step.autoApproveAfterHours}h`}
{step.requiredUserLevelName &&
` • Required Level: ${step.requiredUserLevelName}`}
</div> </div>
</div> </div>
</div> </div>
@ -343,25 +391,45 @@ function TenantSettingsContent() {
<h4 className="text-lg font-medium mb-3">Client Settings</h4> <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="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-3 bg-gray-50 rounded-lg"> <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 font-medium text-gray-700 mb-1">
<div className="text-sm text-gray-600">{workflow.clientSettings.defaultWorkflowName}</div> Default Workflow
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <div className="text-sm text-gray-600">
<div className="text-sm font-medium text-gray-700 mb-1">Auto Publish Articles</div> {workflow.clientSettings.defaultWorkflowName}
<div className={`text-sm font-medium ${workflow.clientSettings.autoPublishArticles ? 'text-green-600' : 'text-red-600'}`}>
{workflow.clientSettings.autoPublishArticles ? 'Yes' : 'No'}
</div> </div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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 text-gray-700 mb-1">
<div className={`text-sm font-medium ${workflow.clientSettings.requiresApproval ? 'text-green-600' : 'text-red-600'}`}> Auto Publish Articles
{workflow.clientSettings.requiresApproval ? 'Yes' : 'No'} </div>
<div
className={`text-sm font-medium ${workflow.clientSettings.autoPublishArticles ? "text-green-600" : "text-red-600"}`}
>
{workflow.clientSettings.autoPublishArticles
? "Yes"
: "No"}
</div> </div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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 text-gray-700 mb-1">
<div className={`text-sm font-medium ${workflow.clientSettings.isActive ? 'text-green-600' : 'text-red-600'}`}> Requires Approval
{workflow.clientSettings.isActive ? 'Yes' : 'No'} </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>
</div> </div>
@ -369,68 +437,116 @@ function TenantSettingsContent() {
{/* Statistics */} {/* Statistics */}
<div className="mb-6"> <div className="mb-6">
<h4 className="text-lg font-medium mb-3">Workflow Statistics</h4> <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="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="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-sm font-medium text-gray-700 mb-1">
<div className="text-2xl font-bold text-blue-600">{workflow.statistics.totalArticlesProcessed}</div> Total Articles Processed
</div>
<div className="text-2xl font-bold text-blue-600">
{workflow.statistics.totalArticlesProcessed}
</div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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-sm font-medium text-gray-700 mb-1">
<div className="text-2xl font-bold text-yellow-600">{workflow.statistics.pendingArticles}</div> Pending Articles
</div>
<div className="text-2xl font-bold text-yellow-600">
{workflow.statistics.pendingArticles}
</div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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-sm font-medium text-gray-700 mb-1">
<div className="text-2xl font-bold text-green-600">{workflow.statistics.approvedArticles}</div> Approved Articles
</div>
<div className="text-2xl font-bold text-green-600">
{workflow.statistics.approvedArticles}
</div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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-sm font-medium text-gray-700 mb-1">
<div className="text-2xl font-bold text-red-600">{workflow.statistics.rejectedArticles}</div> Rejected Articles
</div>
<div className="text-2xl font-bold text-red-600">
{workflow.statistics.rejectedArticles}
</div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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-sm font-medium text-gray-700 mb-1">
<div className="text-2xl font-bold text-purple-600">{workflow.statistics.averageProcessingTime}h</div> Average Processing Time
</div>
<div className="text-2xl font-bold text-purple-600">
{workflow.statistics.averageProcessingTime}h
</div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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 font-medium text-gray-700 mb-1">
<div className="text-sm text-gray-600">{workflow.statistics.mostActiveStep || 'N/A'}</div> Most Active Step
</div>
<div className="text-sm text-gray-600">
{workflow.statistics.mostActiveStep || "N/A"}
</div>
</div> </div>
</div> </div>
</div> </div>
{/* Workflow Metadata */} {/* Workflow Metadata */}
<div className="mb-6"> <div className="mb-6">
<h4 className="text-lg font-medium mb-3">Workflow Information</h4> <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="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-3 bg-gray-50 rounded-lg"> <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 font-medium text-gray-700 mb-1">
<div className="text-sm text-gray-600 font-mono">{workflow.workflow.clientId}</div> Client ID
</div>
<div className="text-sm text-gray-600 font-mono">
{workflow.workflow.clientId}
</div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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 font-medium text-gray-700 mb-1">
Created At
</div>
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600">
{new Date(workflow.workflow.createdAt).toLocaleString()} {new Date(workflow.workflow.createdAt).toLocaleString()}
</div> </div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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 font-medium text-gray-700 mb-1">
Updated At
</div>
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600">
{new Date(workflow.workflow.updatedAt).toLocaleString()} {new Date(workflow.workflow.updatedAt).toLocaleString()}
</div> </div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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 font-medium text-gray-700 mb-1">
<div className="text-sm text-gray-600 font-mono">{workflow.workflow.id}</div> Workflow ID
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <div className="text-sm text-gray-600 font-mono">
<div className="text-sm font-medium text-gray-700 mb-1">Has Branches</div> {workflow.workflow.id}
<div className={`text-sm font-medium ${workflow.workflow.hasBranches ? 'text-green-600' : 'text-gray-600'}`}>
{workflow.workflow.hasBranches ? 'Yes' : 'No'}
</div> </div>
</div> </div>
<div className="p-3 bg-gray-50 rounded-lg"> <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 font-medium text-gray-700 mb-1">
<div className="text-sm text-gray-600">{workflow.workflow.maxStepOrder}</div> 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> </div>
</div> </div>
@ -441,9 +557,12 @@ function TenantSettingsContent() {
<CardContent className="flex items-center justify-center py-12"> <CardContent className="flex items-center justify-center py-12">
<div className="text-center"> <div className="text-center">
<WorkflowIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" /> <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> <h3 className="text-lg font-medium text-gray-900 mb-2">
No Workflow Configured
</h3>
<p className="text-gray-500 mb-4"> <p className="text-gray-500 mb-4">
Set up your approval workflow to manage content approval process Set up your approval workflow to manage content approval
process
</p> </p>
<Button onClick={() => setIsEditingWorkflow(true)}> <Button onClick={() => setIsEditingWorkflow(true)}>
<PlusIcon className="h-4 w-4 mr-2" /> <PlusIcon className="h-4 w-4 mr-2" />
@ -459,7 +578,10 @@ function TenantSettingsContent() {
<TabsContent value="user-levels" className="space-y-6"> <TabsContent value="user-levels" className="space-y-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className="text-2xl font-semibold">User Levels</h2> <h2 className="text-2xl font-semibold">User Levels</h2>
<Dialog open={isUserLevelDialogOpen} onOpenChange={setIsUserLevelDialogOpen}> <Dialog
open={isUserLevelDialogOpen}
onOpenChange={setIsUserLevelDialogOpen}
>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="flex items-center gap-2"> <Button className="flex items-center gap-2">
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
@ -469,22 +591,28 @@ function TenantSettingsContent() {
<DialogContent className="md:max-w-6xl max-h-[90vh] overflow-y-auto"> <DialogContent className="md:max-w-6xl max-h-[90vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{editingUserLevel ? `Edit User Level: ${editingUserLevel.name}` : "Create New User Level"} {editingUserLevel
? `Edit User Level: ${editingUserLevel.name}`
: "Create New User Level"}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<UserLevelsForm <UserLevelsForm
mode="single" mode="single"
initialData={editingUserLevel ? { initialData={
// id: editingUserLevel.id, editingUserLevel
name: editingUserLevel.name, ? {
aliasName: editingUserLevel.aliasName, // id: editingUserLevel.id,
levelNumber: editingUserLevel.levelNumber, name: editingUserLevel.name,
parentLevelId: editingUserLevel.parentLevelId || 0, aliasName: editingUserLevel.aliasName,
provinceId: editingUserLevel.provinceId, levelNumber: editingUserLevel.levelNumber,
group: editingUserLevel.group || "", parentLevelId: editingUserLevel.parentLevelId || 0,
isApprovalActive: editingUserLevel.isApprovalActive, provinceId: editingUserLevel.provinceId,
isActive: editingUserLevel.isActive, group: editingUserLevel.group || "",
} : undefined} isApprovalActive: editingUserLevel.isApprovalActive,
isActive: editingUserLevel.isActive,
}
: undefined
}
onSave={handleUserLevelSave} onSave={handleUserLevelSave}
onCancel={() => { onCancel={() => {
setIsUserLevelDialogOpen(false); setIsUserLevelDialogOpen(false);
@ -499,27 +627,29 @@ function TenantSettingsContent() {
{userLevels.length > 0 && ( {userLevels.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6"> <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-center p-4 bg-gray-50 rounded-lg">
<div className="text-2xl font-bold text-blue-600">{userLevels.length}</div> <div className="text-2xl font-bold text-blue-600">
{userLevels.length}
</div>
<div className="text-sm text-gray-600">Total User Levels</div> <div className="text-sm text-gray-600">Total User Levels</div>
</div> </div>
<div className="text-center p-4 bg-gray-50 rounded-lg"> <div className="text-center p-4 bg-gray-50 rounded-lg">
<div className="text-2xl font-bold text-green-600"> <div className="text-2xl font-bold text-green-600">
{userLevels.filter(ul => ul.isActive).length} {userLevels.filter((ul) => ul.isActive).length}
</div> </div>
<div className="text-sm text-gray-600">Active Levels</div> <div className="text-sm text-gray-600">Active Levels</div>
</div> </div>
<div className="text-center p-4 bg-gray-50 rounded-lg"> <div className="text-center p-4 bg-gray-50 rounded-lg">
<div className="text-2xl font-bold text-purple-600"> <div className="text-2xl font-bold text-purple-600">
{userLevels.filter(ul => ul.isApprovalActive).length} {userLevels.filter((ul) => ul.isApprovalActive).length}
</div> </div>
<div className="text-sm text-gray-600">Approval Active</div> <div className="text-sm text-gray-600">Approval Active</div>
</div> </div>
<div className="text-center p-4 bg-gray-50 rounded-lg"> <div className="text-center p-4 bg-gray-50 rounded-lg">
<div className="text-2xl font-bold text-orange-600"> <div className="text-2xl font-bold text-orange-600">
{userLevels.filter(ul => ul.parentLevelId).length} {userLevels.filter((ul) => ul.parentLevelId).length}
</div> </div>
<div className="text-sm text-gray-600">Child Levels</div> <div className="text-sm text-gray-600">Child Levels</div>
</div> </div>
@ -538,9 +668,9 @@ function TenantSettingsContent() {
<CardContent> <CardContent>
<div className="space-y-3"> <div className="space-y-3">
{userLevels {userLevels
.filter(ul => !ul.parentLevelId) // Root levels .filter((ul) => !ul.parentLevelId)
.sort((a, b) => a.levelNumber - b.levelNumber) .sort((a, b) => a.levelNumber - b.levelNumber)
.map(rootLevel => ( .map((rootLevel) => (
<div key={rootLevel.id} className="space-y-2"> <div key={rootLevel.id} className="space-y-2">
{/* Root Level */} {/* Root Level */}
<div className="flex items-center gap-3 p-3 bg-blue-50 rounded-lg border-l-4 border-blue-500"> <div className="flex items-center gap-3 p-3 bg-blue-50 rounded-lg border-l-4 border-blue-500">
@ -550,7 +680,8 @@ function TenantSettingsContent() {
<div className="flex-1"> <div className="flex-1">
<div className="font-medium">{rootLevel.name}</div> <div className="font-medium">{rootLevel.name}</div>
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
{rootLevel.aliasName} {rootLevel.group || 'No group'} {rootLevel.aliasName} {" "}
{rootLevel.group || "No group"}
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -589,17 +720,23 @@ function TenantSettingsContent() {
{/* Child Levels */} {/* Child Levels */}
{userLevels {userLevels
.filter(ul => ul.parentLevelId === rootLevel.id) .filter((ul) => ul.parentLevelId === rootLevel.id)
.sort((a, b) => a.levelNumber - b.levelNumber) .sort((a, b) => a.levelNumber - b.levelNumber)
.map(childLevel => ( .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
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"> <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} {childLevel.levelNumber}
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="font-medium text-sm">{childLevel.name}</div> <div className="font-medium text-sm">
{childLevel.name}
</div>
<div className="text-xs text-gray-500"> <div className="text-xs text-gray-500">
{childLevel.aliasName} {childLevel.group || 'No group'} {childLevel.aliasName} {" "}
{childLevel.group || "No group"}
</div> </div>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@ -618,7 +755,9 @@ function TenantSettingsContent() {
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => handleEditUserLevel(childLevel)} onClick={() =>
handleEditUserLevel(childLevel)
}
className="h-7 w-7 p-0 border-gray-300 hover:border-blue-500 hover:bg-blue-50" className="h-7 w-7 p-0 border-gray-300 hover:border-blue-500 hover:bg-blue-50"
title="Edit User Level" title="Edit User Level"
> >
@ -627,7 +766,9 @@ function TenantSettingsContent() {
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => handleDeleteUserLevel(childLevel)} onClick={() =>
handleDeleteUserLevel(childLevel)
}
className="h-7 w-7 p-0 border-gray-300 hover:border-red-500 hover:bg-red-50" className="h-7 w-7 p-0 border-gray-300 hover:border-red-500 hover:bg-red-50"
title="Delete User Level" title="Delete User Level"
> >
@ -730,7 +871,9 @@ function TenantSettingsContent() {
<CardContent className="flex items-center justify-center py-12"> <CardContent className="flex items-center justify-center py-12">
<div className="text-center"> <div className="text-center">
<UsersIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" /> <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> <h3 className="text-lg font-medium text-gray-900 mb-2">
No User Levels Found
</h3>
<p className="text-gray-500 mb-4"> <p className="text-gray-500 mb-4">
Create your first user level to define approval hierarchy Create your first user level to define approval hierarchy
</p> </p>

View File

@ -60,9 +60,10 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { PlusIcon, EditIcon, DeleteIcon } from "@/components/icons"; import { PlusIcon, DeleteIcon } from "@/components/icons";
import { errorAutoClose, successAutoClose } from "@/lib/swal"; import { errorAutoClose, successAutoClose } from "@/lib/swal";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import { EditIcon } from "lucide-react";
export default function EditTenantPage() { export default function EditTenantPage() {
const router = useRouter(); const router = useRouter();
@ -610,7 +611,7 @@ export default function EditTenantPage() {
Approval Workflow Setup Approval Workflow Setup
</h2> </h2>
{workflow && !isEditingWorkflow && ( {workflow && !isEditingWorkflow && (
<Button <Button variant="outline"
onClick={() => setIsEditingWorkflow(true)} onClick={() => setIsEditingWorkflow(true)}
className="flex items-center gap-2" className="flex items-center gap-2"
> >

View File

@ -1486,7 +1486,7 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
</Button> </Button>
)} )}
<Button type="submit" disabled={isSubmitting}> <Button variant="outline" type="submit" disabled={isSubmitting}>
{isSubmitting ? "Saving..." : "Save User Level"} {isSubmitting ? "Saving..." : "Save User Level"}
</Button> </Button>

View File

@ -3,13 +3,28 @@ import React from "react";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
interface FormFieldProps { interface FormFieldProps {
label: string; label: string;
name: string; name: string;
type?: "text" | "email" | "password" | "number" | "tel" | "url" | "textarea" | "select" | "checkbox"; type?:
| "text"
| "email"
| "password"
| "number"
| "tel"
| "url"
| "textarea"
| "select"
| "checkbox";
placeholder?: string; placeholder?: string;
value: any; value: any;
onChange: (value: any) => void; onChange: (value: any) => void;
@ -68,7 +83,9 @@ export const FormField: React.FC<FormFieldProps> = ({
onValueChange={(val) => onChange(val)} onValueChange={(val) => onChange(val)}
disabled={disabled} disabled={disabled}
> >
<SelectTrigger className={`${hasError ? "border-red-500" : ""} ${className}`}> <SelectTrigger
className={`${hasError ? "border-red-500" : ""} ${className}`}
>
<SelectValue placeholder={placeholder || `Select ${label}`} /> <SelectValue placeholder={placeholder || `Select ${label}`} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -83,20 +100,45 @@ export const FormField: React.FC<FormFieldProps> = ({
case "checkbox": case "checkbox":
return ( return (
<div className="flex items-center space-x-2"> <div className="flex items-center gap-3">
<Checkbox <Checkbox
id={fieldId} checked={value}
checked={!!value} onCheckedChange={(v) => onChange(!!v)}
onCheckedChange={(checked) => onChange(checked)} className="
disabled={disabled} h-4 w-4
className={hasError ? "border-red-500" : ""} rounded-full
border border-black
text-white
data-[state=checked]:bg-black
focus-visible:ring-2 focus-visible:ring-black
"
/> />
<Label htmlFor={fieldId} className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
<Label
htmlFor={fieldId}
className="text-sm font-medium leading-none cursor-pointer"
>
{label} {label}
</Label> </Label>
</div> </div>
); );
// case "checkbox":
// return (
// <div className="flex items-center space-x-2">
// <Checkbox
// id={fieldId}
// checked={!!value}
// onCheckedChange={(checked) => onChange(checked)}
// disabled={disabled}
// className={hasError ? "border-red-500" : ""}
// />
// <Label htmlFor={fieldId} className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
// {label}
// </Label>
// </div>
// );
case "number": case "number":
return ( return (
<Input <Input
@ -104,7 +146,9 @@ export const FormField: React.FC<FormFieldProps> = ({
type="number" type="number"
placeholder={placeholder} placeholder={placeholder}
value={value || ""} value={value || ""}
onChange={(e) => onChange(e.target.value ? Number(e.target.value) : "")} onChange={(e) =>
onChange(e.target.value ? Number(e.target.value) : "")
}
disabled={disabled} disabled={disabled}
className={`${hasError ? "border-red-500" : ""} ${className}`} className={`${hasError ? "border-red-500" : ""} ${className}`}
min={min} min={min}
@ -139,13 +183,9 @@ export const FormField: React.FC<FormFieldProps> = ({
{renderField()} {renderField()}
{helpText && ( {helpText && <p className="text-xs text-gray-500">{helpText}</p>}
<p className="text-xs text-gray-500">{helpText}</p>
)}
{hasError && ( {hasError && <p className="text-red-500 text-xs">{error}</p>}
<p className="text-red-500 text-xs">{error}</p>
)}
</div> </div>
); );
}; };

View File

@ -180,7 +180,7 @@ export default function Navbar() {
</Button> </Button>
</Link> </Link>
<Link href="/auth"> <Link href="/auth">
<Button className="bg-red-700 text-white cursor-pointer hover:bg-white hover:text-red-700">{t("login")}</Button> <Button className="bg-red-700 text-white cursor-pointer hover:bg-white hover:border hover:border-red-700 hover:text-red-700">{t("login")}</Button>
</Link> </Link>
</> </>
) : ( ) : (

View File

@ -268,7 +268,7 @@ export default function DashboardContainer() {
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.7 }} transition={{ delay: 0.7 }}
> >
<div className="flex justify-between items-center mb-6"> {/* <div className="flex justify-between items-center mb-6">
<h3 className="text-lg font-semibold text-gray-900"> <h3 className="text-lg font-semibold text-gray-900">
Recent Content Recent Content
</h3> </h3>
@ -277,7 +277,7 @@ export default function DashboardContainer() {
Create Content Create Content
</Button> </Button>
</Link> </Link>
</div> </div> */}
<div className="space-y-4 max-h-96 overflow-y-auto scrollbar-thin"> <div className="space-y-4 max-h-96 overflow-y-auto scrollbar-thin">
{article?.map((list: any) => ( {article?.map((list: any) => (

View File

@ -143,14 +143,14 @@ const columns: ColumnDef<any>[] = [
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-slate-200 focus:text-blue-500 rounded-none">
<Link <Link
href={`/admin/management-user/detail/${row.original.id}`} href={`/admin/management-user/detail/${row.original.id}`}
> >
Detail Detail
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-slate-200 focus:text-blue-500 rounded-none">
<Link <Link
href={`/admin/management-user/edit/${row.original.id}`} href={`/admin/management-user/edit/${row.original.id}`}
> >
@ -159,7 +159,7 @@ const columns: ColumnDef<any>[] = [
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
color="red" color="red"
className="p-2 border-b text-red-500 group focus:bg-red-500 focus:text-white rounded-none" className="p-2 border-b text-red-500 group focus:bg-red-500 focus:text-white rounded-none cursor-pointer"
> >
<a <a
onClick={() => { onClick={() => {