feat: update fixing tenant
This commit is contained in:
parent
580900ac14
commit
f7475d16fd
|
|
@ -7,24 +7,27 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from
|
|||
import { PlusIcon, SettingsIcon, UsersIcon, WorkflowIcon } from "@/components/icons";
|
||||
import { ApprovalWorkflowForm } from "@/components/form/ApprovalWorkflowForm";
|
||||
import { UserLevelsForm } from "@/components/form/UserLevelsForm";
|
||||
import { WorkflowModalProvider } from "@/components/modals/WorkflowModalProvider";
|
||||
import { useWorkflowModal } from "@/components/modals/WorkflowModalProvider";
|
||||
import { useWorkflowStatusCheck } from "@/hooks/useWorkflowStatusCheck";
|
||||
import {
|
||||
CreateApprovalWorkflowWithClientSettingsRequest,
|
||||
UserLevelsCreateRequest,
|
||||
UserLevel,
|
||||
getUserLevels,
|
||||
getApprovalWorkflows,
|
||||
getApprovalWorkflowComprehensiveDetails,
|
||||
ComprehensiveWorkflowResponse,
|
||||
createUserLevel,
|
||||
} from "@/service/approval-workflows";
|
||||
|
||||
function TenantSettingsContent() {
|
||||
const [activeTab, setActiveTab] = useState("workflows");
|
||||
const [isUserLevelDialogOpen, setIsUserLevelDialogOpen] = useState(false);
|
||||
const [workflow, setWorkflow] = useState<any>(null);
|
||||
const [workflow, setWorkflow] = useState<ComprehensiveWorkflowResponse | null>(null);
|
||||
const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isEditingWorkflow, setIsEditingWorkflow] = useState(false);
|
||||
const { checkWorkflowStatus } = useWorkflowStatusCheck();
|
||||
const { showWorkflowModal } = useWorkflowModal();
|
||||
|
||||
// Load data on component mount
|
||||
React.useEffect(() => {
|
||||
|
|
@ -34,16 +37,17 @@ function TenantSettingsContent() {
|
|||
const loadData = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const [workflowsRes, userLevelsRes] = await Promise.all([
|
||||
getApprovalWorkflows(),
|
||||
const [comprehensiveWorkflowRes, userLevelsRes] = await Promise.all([
|
||||
getApprovalWorkflowComprehensiveDetails(4), // Using workflow ID 4 as per API example
|
||||
getUserLevels(),
|
||||
]);
|
||||
|
||||
if (!workflowsRes?.error) {
|
||||
const workflows = workflowsRes?.data || [];
|
||||
// Set the first workflow as the single workflow for this tenant
|
||||
setWorkflow(workflows.length > 0 ? workflows[0] : null);
|
||||
if (!comprehensiveWorkflowRes?.error) {
|
||||
setWorkflow(comprehensiveWorkflowRes?.data?.data || null);
|
||||
} else {
|
||||
setWorkflow(null);
|
||||
}
|
||||
|
||||
if (!userLevelsRes?.error) {
|
||||
setUserLevels(userLevelsRes?.data?.data || []);
|
||||
}
|
||||
|
|
@ -60,6 +64,20 @@ function TenantSettingsContent() {
|
|||
};
|
||||
|
||||
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);
|
||||
await loadData(); // Reload data after saving
|
||||
};
|
||||
|
|
@ -87,6 +105,14 @@ function TenantSettingsContent() {
|
|||
>
|
||||
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>
|
||||
|
||||
|
|
@ -132,7 +158,34 @@ function TenantSettingsContent() {
|
|||
</CardHeader>
|
||||
<CardContent>
|
||||
<ApprovalWorkflowForm
|
||||
initialData={workflow}
|
||||
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}
|
||||
onSave={handleWorkflowSave}
|
||||
onCancel={() => setIsEditingWorkflow(false)}
|
||||
/>
|
||||
|
|
@ -142,14 +195,14 @@ function TenantSettingsContent() {
|
|||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>{workflow.name}</span>
|
||||
<span>{workflow.workflow.name}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{workflow.isDefault && (
|
||||
{workflow.workflow.isDefault && (
|
||||
<span className="px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded-full">
|
||||
Default
|
||||
</span>
|
||||
)}
|
||||
{workflow.isActive ? (
|
||||
{workflow.workflow.isActive ? (
|
||||
<span className="px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">
|
||||
Active
|
||||
</span>
|
||||
|
|
@ -163,34 +216,32 @@ function TenantSettingsContent() {
|
|||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
{workflow.description}
|
||||
{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.steps?.length || 0}</div>
|
||||
<div className="text-sm text-gray-600">Workflow Steps</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>
|
||||
|
||||
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
||||
<div className={`text-2xl font-bold ${workflow.requiresApproval ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{workflow.requiresApproval ? 'Yes' : 'No'}
|
||||
<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.autoPublish ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{workflow.autoPublish ? 'Yes' : 'No'}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">Auto Publish</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-purple-600">
|
||||
{workflow.clientApprovalSettings?.approvalExemptUsers?.length || 0}
|
||||
<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">Exempt Users</div>
|
||||
<div className="text-sm text-gray-600">Auto Publish</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -210,6 +261,7 @@ function TenantSettingsContent() {
|
|||
<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>
|
||||
|
|
@ -224,6 +276,21 @@ function TenantSettingsContent() {
|
|||
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>
|
||||
))}
|
||||
|
|
@ -231,38 +298,102 @@ function TenantSettingsContent() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Client Settings Overview */}
|
||||
{workflow.clientApprovalSettings && (
|
||||
<div className="mb-6">
|
||||
<h4 className="text-lg font-medium mb-3">Client Approval 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">Exempt Users</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{workflow.clientApprovalSettings.approvalExemptUsers?.length || 0} users
|
||||
</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 className="p-3 bg-gray-50 rounded-lg">
|
||||
<div className="text-sm font-medium text-gray-700 mb-1">Exempt Roles</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{workflow.clientApprovalSettings.approvalExemptRoles?.length || 0} roles
|
||||
</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 className="p-3 bg-gray-50 rounded-lg">
|
||||
<div className="text-sm font-medium text-gray-700 mb-1">Exempt Categories</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{workflow.clientApprovalSettings.approvalExemptCategories?.length || 0} categories
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-3 bg-gray-50 rounded-lg">
|
||||
<div className="text-sm font-medium text-gray-700 mb-1">Content Types</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{workflow.clientApprovalSettings.requireApprovalFor?.length || 0} requiring approval
|
||||
</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>
|
||||
) : (
|
||||
|
|
@ -295,7 +426,7 @@ function TenantSettingsContent() {
|
|||
Create User Level
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="md:max-w-6xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New User Level</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
|
@ -465,7 +596,7 @@ function TenantSettingsContent() {
|
|||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-500">Parent Level:</span>
|
||||
<span className="font-medium">
|
||||
{userLevels.find(ul => ul.id === userLevel.parentLevelId)?.name || `Level ${userLevel.parentLevelId}`}
|
||||
{ userLevels.length > 0 ? userLevels.find(ul => ul.id === userLevel.parentLevelId)?.name || `Level ${userLevel.parentLevelId}` : `Level ${userLevel.parentLevelId}`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -522,9 +653,5 @@ function TenantSettingsContent() {
|
|||
}
|
||||
|
||||
export default function TenantSettingsPage() {
|
||||
return (
|
||||
<WorkflowModalProvider>
|
||||
<TenantSettingsContent />
|
||||
</WorkflowModalProvider>
|
||||
);
|
||||
return <TenantSettingsContent />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,19 @@ import DashCodeFooter from "@/components/partials/footer";
|
|||
import ThemeCustomize from "@/components/partials/customizer";
|
||||
import DashCodeHeader from "@/components/partials/header";
|
||||
import MountedProvider from "@/providers/mounted.provider";
|
||||
import { WorkflowModalProvider } from "@/components/modals/WorkflowModalProvider";
|
||||
|
||||
const layout = async ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<MountedProvider isProtected={true}>
|
||||
<LayoutProvider>
|
||||
<ThemeCustomize />
|
||||
<DashCodeHeader />
|
||||
<DashCodeSidebar />
|
||||
<LayoutContentProvider>{children}</LayoutContentProvider>
|
||||
<DashCodeFooter />
|
||||
<WorkflowModalProvider>
|
||||
<ThemeCustomize />
|
||||
<DashCodeHeader />
|
||||
<DashCodeSidebar />
|
||||
<LayoutContentProvider>{children}</LayoutContentProvider>
|
||||
<DashCodeFooter />
|
||||
</WorkflowModalProvider>
|
||||
</LayoutProvider>
|
||||
</MountedProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
getProvinces(),
|
||||
]);
|
||||
|
||||
if (!userLevelsRes?.error) setUserLevels(userLevelsRes?.data || []);
|
||||
if (!provincesRes?.error) setProvinces(provincesRes?.data || []);
|
||||
if (!userLevelsRes?.error) setUserLevels(userLevelsRes?.data?.data || []);
|
||||
if (!provincesRes?.error) setProvinces(provincesRes?.data?.data || []);
|
||||
} catch (error) {
|
||||
console.error("Error loading form data:", error);
|
||||
} finally {
|
||||
|
|
@ -99,8 +99,6 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
newErrors.aliasName = "Alias name is required";
|
||||
} else if (data.aliasName.trim().length < 3) {
|
||||
newErrors.aliasName = "Alias name must be at least 3 characters";
|
||||
} else if (!/^[A-Z_]+$/.test(data.aliasName.trim())) {
|
||||
newErrors.aliasName = "Alias name must contain only uppercase letters and underscores";
|
||||
}
|
||||
|
||||
if (!data.levelNumber || data.levelNumber <= 0) {
|
||||
|
|
@ -108,10 +106,10 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
}
|
||||
|
||||
// Check for duplicate level numbers
|
||||
const existingLevel = userLevels.find(level => level.levelNumber === data.levelNumber);
|
||||
if (existingLevel && (!initialData || existingLevel.id !== (initialData as any).id)) {
|
||||
newErrors.levelNumber = "Level number already exists";
|
||||
}
|
||||
// const existingLevel = userLevels.length > 0 ? userLevels.find(level => level.levelNumber === data.levelNumber) : null;
|
||||
// if (existingLevel && (!initialData || existingLevel.id !== (initialData as any).id)) {
|
||||
// newErrors.levelNumber = "Level number already exists";
|
||||
// }
|
||||
|
||||
return newErrors;
|
||||
};
|
||||
|
|
@ -202,11 +200,14 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
type="select"
|
||||
placeholder={userLevels.length > 0 ? "Select parent level" : "No parent levels available"}
|
||||
value={item.parentLevelId}
|
||||
onChange={(value) => onUpdate({ ...item, parentLevelId: value ? Number(value) : undefined })}
|
||||
options={userLevels.map(level => ({
|
||||
value: level.id,
|
||||
label: `${level.name} (Level ${level.levelNumber})`,
|
||||
}))}
|
||||
onChange={(value) => onUpdate({ ...item, parentLevelId: value !== undefined ? Number(value) : undefined })}
|
||||
options={[
|
||||
{ value: 0, label: "No Parent (Root Level)" },
|
||||
...(userLevels.length > 0 ? userLevels.map(level => ({
|
||||
value: level.id,
|
||||
label: `${level.name} (Level ${level.levelNumber})`,
|
||||
})) : [])
|
||||
]}
|
||||
helpText={userLevels.length === 0 ? "No parent levels found. This will be a root level." : "Select parent level for hierarchy"}
|
||||
disabled={userLevels.length === 0}
|
||||
/>
|
||||
|
|
@ -218,10 +219,10 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
placeholder={provinces.length > 0 ? "Select province" : "No provinces available"}
|
||||
value={item.provinceId}
|
||||
onChange={(value) => onUpdate({ ...item, provinceId: value ? Number(value) : undefined })}
|
||||
options={provinces.map(province => ({
|
||||
options={provinces.length > 0 ? provinces.map(province => ({
|
||||
value: province.id,
|
||||
label: province.provinceName,
|
||||
}))}
|
||||
label: province.prov_name,
|
||||
})) : []}
|
||||
helpText={provinces.length === 0 ? "No provinces found. Please ensure provinces are available in the system." : "Geographic scope for this level"}
|
||||
disabled={provinces.length === 0}
|
||||
/>
|
||||
|
|
@ -265,6 +266,8 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
|
||||
if (mode === "single") {
|
||||
const validationErrors = validateForm(formData);
|
||||
console.log(validationErrors);
|
||||
|
||||
if (Object.keys(validationErrors).length > 0) {
|
||||
setErrors(validationErrors);
|
||||
Swal.fire({
|
||||
|
|
@ -429,7 +432,7 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
<Tabs defaultValue={mode === "single" ? "basic" : "bulk"} className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="basic" disabled={isLoadingData}>Basic Information</TabsTrigger>
|
||||
<TabsTrigger value="hierarchy" disabled={isLoadingData}>Hierarchy</TabsTrigger>
|
||||
{/* <TabsTrigger value="hierarchy" disabled={isLoadingData}>Hierarchy</TabsTrigger> */}
|
||||
<TabsTrigger value="bulk" disabled={isLoadingData}>Bulk Operations</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
|
|
@ -485,11 +488,14 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
type="select"
|
||||
placeholder={userLevels.length > 0 ? "Select parent level" : "No parent levels available"}
|
||||
value={formData.parentLevelId}
|
||||
onChange={(value) => handleFieldChange("parentLevelId", value ? Number(value) : undefined)}
|
||||
options={userLevels.map(level => ({
|
||||
value: level.id,
|
||||
label: `${level.name} (Level ${level.levelNumber})`,
|
||||
}))}
|
||||
onChange={(value) => handleFieldChange("parentLevelId", value !== undefined ? Number(value) : undefined)}
|
||||
options={[
|
||||
{ value: 0, label: "No Parent (Root Level)" },
|
||||
...(userLevels.length > 0 ? userLevels.map(level => ({
|
||||
value: level.id,
|
||||
label: `${level.name} (Level ${level.levelNumber})`,
|
||||
})) : [])
|
||||
]}
|
||||
helpText={userLevels.length === 0 ? "No parent levels found. This will be a root level." : "Select parent level for hierarchy"}
|
||||
disabled={userLevels.length === 0}
|
||||
/>
|
||||
|
|
@ -501,10 +507,10 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
placeholder={provinces.length > 0 ? "Select province" : "No provinces available"}
|
||||
value={formData.provinceId}
|
||||
onChange={(value) => handleFieldChange("provinceId", value ? Number(value) : undefined)}
|
||||
options={provinces.map(province => ({
|
||||
options={provinces.length > 0 ? provinces.map(province => ({
|
||||
value: province.id,
|
||||
label: province.provinceName,
|
||||
}))}
|
||||
label: province.prov_name,
|
||||
})) : []}
|
||||
helpText={provinces.length === 0 ? "No provinces found. Please ensure provinces are available in the system." : "Geographic scope for this level"}
|
||||
disabled={provinces.length === 0}
|
||||
/>
|
||||
|
|
@ -544,7 +550,7 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
</TabsContent>
|
||||
|
||||
{/* Hierarchy Tab */}
|
||||
<TabsContent value="hierarchy" className="space-y-6">
|
||||
{/* <TabsContent value="hierarchy" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
|
|
@ -572,7 +578,7 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|||
</Collapsible>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</TabsContent> */}
|
||||
|
||||
{/* Bulk Operations Tab */}
|
||||
<TabsContent value="bulk" className="space-y-6">
|
||||
|
|
|
|||
|
|
@ -2868,19 +2868,53 @@ export const ArrowDownIcon = ({ size = 24, width, height, ...props }: IconSvgPro
|
|||
|
||||
export const SettingsIcon = ({ size = 24, width, height, ...props }: IconSvgProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size || width}
|
||||
height={size || height}
|
||||
{...props}
|
||||
width={width || size}
|
||||
height={height || size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1 1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const AlertTriangleIcon = ({ size = 24, width, height, ...props }: IconSvgProps) => (
|
||||
<svg
|
||||
width={width || size}
|
||||
height={height || size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
|
||||
<path d="M12 9v4" />
|
||||
<path d="M12 17h.01" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CheckCircleIcon = ({ size = 24, width, height, ...props }: IconSvgProps) => (
|
||||
<svg
|
||||
width={width || size}
|
||||
height={height || size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
||||
<path d="m9 11 3 3L22 4" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ export default function Layout({ children }: LayoutProps) {
|
|||
<WorkflowModalProvider>
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Header>
|
||||
{/* Header content will be provided by parent components */}
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
{/* Header content */}
|
||||
</div>
|
||||
</Header>
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { createContext, useContext, useState, ReactNode } from "react";
|
||||
import React, { createContext, useContext, useState, ReactNode, useEffect, useCallback } from "react";
|
||||
import WorkflowSetupModal from "./WorkflowSetupModal";
|
||||
import { getInfoProfile } from "@/service/auth";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
interface WorkflowInfo {
|
||||
hasWorkflowSetup: boolean;
|
||||
|
|
@ -14,6 +16,7 @@ interface WorkflowInfo {
|
|||
interface WorkflowModalContextType {
|
||||
showWorkflowModal: (workflowInfo: WorkflowInfo) => void;
|
||||
hideWorkflowModal: () => void;
|
||||
refreshWorkflowStatus: () => Promise<void>;
|
||||
}
|
||||
|
||||
const WorkflowModalContext = createContext<WorkflowModalContextType | undefined>(undefined);
|
||||
|
|
@ -33,24 +36,88 @@ interface WorkflowModalProviderProps {
|
|||
export function WorkflowModalProvider({ children }: WorkflowModalProviderProps) {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [workflowInfo, setWorkflowInfo] = useState<WorkflowInfo | undefined>(undefined);
|
||||
const pathname = usePathname();
|
||||
|
||||
// Force hide modal when on tenant settings page
|
||||
const forceHideModal = useCallback(() => {
|
||||
console.log("Force hiding modal");
|
||||
setIsModalOpen(false);
|
||||
setWorkflowInfo(undefined);
|
||||
}, []);
|
||||
|
||||
const refreshWorkflowStatus = async () => {
|
||||
try {
|
||||
const response = await getInfoProfile();
|
||||
|
||||
if (response?.data?.data && response?.data?.data?.approvalWorkflowInfo) {
|
||||
const workflowInfo = response.data.data.approvalWorkflowInfo;
|
||||
|
||||
// Update workflow info
|
||||
setWorkflowInfo(workflowInfo);
|
||||
|
||||
// If workflow is now setup, allow closing modal
|
||||
if (workflowInfo.hasWorkflowSetup) {
|
||||
console.log("Workflow is now setup, modal can be closed");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error refreshing workflow status:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-hide modal when user is on tenant settings page
|
||||
useEffect(() => {
|
||||
console.log("Current pathname:", pathname);
|
||||
|
||||
// Add small delay to ensure pathname is fully updated
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (pathname?.includes('/admin/settings/tenant') || pathname?.includes('/tenant')) {
|
||||
console.log("User is on tenant settings page, hiding modal");
|
||||
forceHideModal();
|
||||
}
|
||||
}, 100); // Small delay to ensure pathname is updated
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [pathname, forceHideModal]);
|
||||
|
||||
// Auto-refresh workflow status when modal is open and user is not on tenant settings page
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
|
||||
if (isModalOpen && !pathname?.includes('/admin/settings/tenant') && !pathname?.includes('/tenant')) {
|
||||
console.log("Starting auto-refresh for workflow status");
|
||||
interval = setInterval(async () => {
|
||||
await refreshWorkflowStatus();
|
||||
}, 5000); // Refresh every 5 seconds
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
};
|
||||
}, [isModalOpen, pathname, refreshWorkflowStatus]);
|
||||
|
||||
const showWorkflowModal = (info: WorkflowInfo) => {
|
||||
console.log("Show workflow modal: ", info);
|
||||
setWorkflowInfo(info);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const hideWorkflowModal = () => {
|
||||
console.log("Hide workflow modal");
|
||||
setIsModalOpen(false);
|
||||
setWorkflowInfo(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<WorkflowModalContext.Provider value={{ showWorkflowModal, hideWorkflowModal }}>
|
||||
<WorkflowModalContext.Provider value={{ showWorkflowModal, hideWorkflowModal, refreshWorkflowStatus }}>
|
||||
{children}
|
||||
<WorkflowSetupModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={hideWorkflowModal}
|
||||
workflowInfo={workflowInfo}
|
||||
onRefresh={refreshWorkflowStatus}
|
||||
/>
|
||||
</WorkflowModalContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import React, { useState, useEffect } from "react";
|
|||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { AlertTriangleIcon, CheckCircleIcon, SettingsIcon } from "@/components/icons";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { IconX, SettingsIcon } from "@/components/icons";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
|
||||
interface WorkflowSetupModalProps {
|
||||
isOpen: boolean;
|
||||
|
|
@ -17,10 +17,12 @@ interface WorkflowSetupModalProps {
|
|||
autoPublishArticles?: boolean;
|
||||
isApprovalActive?: boolean;
|
||||
};
|
||||
onRefresh?: () => Promise<void>;
|
||||
}
|
||||
|
||||
export default function WorkflowSetupModal({ isOpen, onClose, workflowInfo }: WorkflowSetupModalProps) {
|
||||
export default function WorkflowSetupModal({ isOpen, onClose, workflowInfo, onRefresh }: WorkflowSetupModalProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -30,28 +32,53 @@ export default function WorkflowSetupModal({ isOpen, onClose, workflowInfo }: Wo
|
|||
}, [isOpen]);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsVisible(false);
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
}, 200);
|
||||
// Allow closing if workflow is setup OR if user is on tenant settings page
|
||||
if (workflowInfo?.hasWorkflowSetup || pathname?.includes('/admin/settings/tenant') || pathname?.includes('/tenant')) {
|
||||
setIsVisible(false);
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSetupWorkflow = () => {
|
||||
handleClose();
|
||||
// Navigate to tenant settings
|
||||
router.push("/admin/settings/tenant");
|
||||
// Modal will be auto-hidden by WorkflowModalProvider when user reaches tenant settings page
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={isVisible} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-md">
|
||||
<Dialog
|
||||
open={isVisible}
|
||||
onOpenChange={(workflowInfo?.hasWorkflowSetup || pathname?.includes('/admin/settings/tenant') || pathname?.includes('/tenant')) ? handleClose : undefined}
|
||||
>
|
||||
<DialogContent
|
||||
className="max-w-md"
|
||||
onPointerDownOutside={(e) => {
|
||||
// Prevent closing by clicking outside unless workflow is setup or on tenant settings page
|
||||
if (!workflowInfo?.hasWorkflowSetup && !pathname?.includes('/admin/settings/tenant') && !pathname?.includes('/tenant')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
onEscapeKeyDown={(e) => {
|
||||
// Prevent closing by pressing ESC unless workflow is setup or on tenant settings page
|
||||
if (!workflowInfo?.hasWorkflowSetup && !pathname?.includes('/admin/settings/tenant') && !pathname?.includes('/tenant')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
{workflowInfo?.hasWorkflowSetup ? (
|
||||
<CheckCircleIcon className="h-5 w-5 text-green-600" />
|
||||
<div className="h-5 w-5 rounded-full bg-green-600 flex items-center justify-center">
|
||||
<span className="text-white text-xs">✓</span>
|
||||
</div>
|
||||
) : (
|
||||
<AlertTriangleIcon className="h-5 w-5 text-orange-600" />
|
||||
<div className="h-5 w-5 rounded-full bg-orange-600 flex items-center justify-center">
|
||||
<span className="text-white text-xs">!</span>
|
||||
</div>
|
||||
)}
|
||||
Workflow Status
|
||||
</DialogTitle>
|
||||
|
|
@ -63,7 +90,9 @@ export default function WorkflowSetupModal({ isOpen, onClose, workflowInfo }: Wo
|
|||
<Card className="border-orange-200 bg-orange-50">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangleIcon className="h-6 w-6 text-orange-600 mt-1" />
|
||||
<div className="h-6 w-6 rounded-full bg-orange-600 flex items-center justify-center mt-1">
|
||||
<span className="text-white text-sm">!</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-orange-900 mb-2">
|
||||
Workflow Belum Dikonfigurasi
|
||||
|
|
@ -80,74 +109,71 @@ export default function WorkflowSetupModal({ isOpen, onClose, workflowInfo }: Wo
|
|||
<SettingsIcon className="h-4 w-4 mr-2" />
|
||||
Setup Workflow
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
size="sm"
|
||||
>
|
||||
Nanti
|
||||
</Button>
|
||||
{(pathname?.includes('/admin/settings/tenant') || pathname?.includes('/tenant')) && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
size="sm"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
) : ''
|
||||
// Workflow Setup Complete
|
||||
<Card className="border-green-200 bg-green-50">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircleIcon className="h-6 w-6 text-green-600 mt-1" />
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-green-900 mb-2">
|
||||
Workflow Sudah Dikonfigurasi
|
||||
</h3>
|
||||
<div className="space-y-2 text-sm text-green-700">
|
||||
<div className="flex items-center justify-between">
|
||||
<span>Workflow:</span>
|
||||
<span className="font-medium">{workflowInfo.defaultWorkflowName}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>Requires Approval:</span>
|
||||
<span className={`font-medium ${workflowInfo.requiresApproval ? 'text-green-600' : 'text-gray-500'}`}>
|
||||
{workflowInfo.requiresApproval ? 'Yes' : 'No'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>Auto Publish:</span>
|
||||
<span className={`font-medium ${workflowInfo.autoPublishArticles ? 'text-green-600' : 'text-gray-500'}`}>
|
||||
{workflowInfo.autoPublishArticles ? 'Yes' : 'No'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>Status:</span>
|
||||
<span className={`font-medium ${workflowInfo.isApprovalActive ? 'text-green-600' : 'text-gray-500'}`}>
|
||||
{workflowInfo.isApprovalActive ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
<Button
|
||||
onClick={handleSetupWorkflow}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
<SettingsIcon className="h-4 w-4 mr-2" />
|
||||
Manage Workflow
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700 text-white"
|
||||
>
|
||||
OK
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
// <Card className="border-green-200 bg-green-50">
|
||||
// <CardContent className="p-4">
|
||||
// <div className="flex items-start gap-3">
|
||||
// <div className="h-6 w-6 rounded-full bg-green-600 flex items-center justify-center mt-1">
|
||||
// <span className="text-white text-sm">✓</span>
|
||||
// </div>
|
||||
// <div className="flex-1">
|
||||
// <h3 className="font-medium text-green-900 mb-2">
|
||||
// Workflow Sudah Dikonfigurasi
|
||||
// </h3>
|
||||
// <div className="space-y-2 text-sm text-green-700">
|
||||
// <div className="flex items-center justify-between">
|
||||
// <span>Workflow:</span>
|
||||
// <span className="font-medium">{workflowInfo.defaultWorkflowName}</span>
|
||||
// </div>
|
||||
// <div className="flex items-center justify-between">
|
||||
// <span>Requires Approval:</span>
|
||||
// <span className={`font-medium ${workflowInfo.requiresApproval ? 'text-green-600' : 'text-gray-500'}`}>
|
||||
// {workflowInfo.requiresApproval ? 'Yes' : 'No'}
|
||||
// </span>
|
||||
// </div>
|
||||
// <div className="flex items-center justify-between">
|
||||
// <span>Auto Publish:</span>
|
||||
// <span className={`font-medium ${workflowInfo.autoPublishArticles ? 'text-green-600' : 'text-gray-500'}`}>
|
||||
// {workflowInfo.autoPublishArticles ? 'Yes' : 'No'}
|
||||
// </span>
|
||||
// </div>
|
||||
// <div className="flex items-center justify-between">
|
||||
// <span>Status:</span>
|
||||
// <span className={`font-medium ${workflowInfo.isApprovalActive ? 'text-green-600' : 'text-gray-500'}`}>
|
||||
// {workflowInfo.isApprovalActive ? 'Active' : 'Inactive'}
|
||||
// </span>
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className="flex gap-2 mt-4">
|
||||
// <Button
|
||||
// onClick={handleSetupWorkflow}
|
||||
// variant="outline"
|
||||
// size="sm"
|
||||
// >
|
||||
// <SettingsIcon className="h-4 w-4 mr-2" />
|
||||
// Manage Workflow
|
||||
// </Button>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </CardContent>
|
||||
// </Card>
|
||||
}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,12 @@ import { SheetMenu } from "@/components/partials/sidebar/menu/sheet-menu";
|
|||
import HorizontalMenu from "./horizontal-menu";
|
||||
import LocalSwitcher from "./locale-switcher";
|
||||
import HeaderLogo from "./header-logo";
|
||||
import { useAutoWorkflowCheck } from "@/hooks/useWorkflowStatusCheck";
|
||||
|
||||
const DashCodeHeader = () => {
|
||||
// Auto-check workflow status when header mounts
|
||||
useAutoWorkflowCheck();
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderContent>
|
||||
|
|
|
|||
|
|
@ -121,6 +121,12 @@ export const useAuth = (): AuthContextType => {
|
|||
Cookies.set("time_refresh", newTime, {
|
||||
expires: 1,
|
||||
});
|
||||
if (response?.data?.data?.approvalWorkflowInfo?.hasWorkflowSetup) {
|
||||
Cookies.set("default_workflow", response?.data?.data?.approvalWorkflowInfo?.defaultWorkflowId, {
|
||||
expires: 1,
|
||||
});
|
||||
}
|
||||
|
||||
Cookies.set("is_first_login", "true", {
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
"use client";
|
||||
import { useEffect } from "react";
|
||||
import { useWorkflowModal } from "./WorkflowModalProvider";
|
||||
import { httpGetInterceptor } from "@/service/http-config/http-interceptor-service";
|
||||
import { useWorkflowModal } from "@/components/modals/WorkflowModalProvider";
|
||||
import { getInfoProfile } from "@/service/auth";
|
||||
|
||||
interface UserInfo {
|
||||
id: number;
|
||||
|
|
@ -48,19 +49,17 @@ export function useWorkflowStatusCheck() {
|
|||
|
||||
const checkWorkflowStatus = async () => {
|
||||
try {
|
||||
const response = await httpGetInterceptor("users/info") as ApiResponse;
|
||||
const response = await getInfoProfile();
|
||||
|
||||
console.log("Response approvalWorkflowInfo: ", response);
|
||||
|
||||
if (response?.success && response?.data?.approvalWorkflowInfo) {
|
||||
const workflowInfo = response.data.approvalWorkflowInfo;
|
||||
if (response?.data?.data && response?.data?.data?.approvalWorkflowInfo) {
|
||||
const workflowInfo = response.data.data.approvalWorkflowInfo;
|
||||
|
||||
// Show modal if workflow is not setup
|
||||
if (!workflowInfo.hasWorkflowSetup) {
|
||||
showWorkflowModal(workflowInfo);
|
||||
}
|
||||
// Optional: Also show modal if workflow is setup (for confirmation)
|
||||
// else {
|
||||
// showWorkflowModal(workflowInfo);
|
||||
// }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking workflow status:", error);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,124 @@ export interface UserLevel {
|
|||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface WorkflowStep {
|
||||
id: number;
|
||||
workflowId: number;
|
||||
stepOrder: number;
|
||||
stepName: string;
|
||||
requiredUserLevelId: number;
|
||||
canSkip: boolean;
|
||||
autoApproveAfterHours?: number;
|
||||
isActive: boolean;
|
||||
parentStepId?: number;
|
||||
conditionType: string;
|
||||
conditionValue: string;
|
||||
isParallel: boolean;
|
||||
branchName?: string;
|
||||
branchOrder?: number;
|
||||
clientId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
workflow?: any;
|
||||
requiredUserLevel?: any;
|
||||
parentStep?: any;
|
||||
childSteps?: any;
|
||||
}
|
||||
|
||||
export interface ApprovalWorkflow {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
isDefault: boolean;
|
||||
isActive: boolean;
|
||||
requiresApproval: boolean;
|
||||
autoPublish: boolean;
|
||||
clientId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
steps: WorkflowStep[];
|
||||
}
|
||||
|
||||
export interface ComprehensiveWorkflowStep {
|
||||
id: number;
|
||||
workflowId: number;
|
||||
stepOrder: number;
|
||||
stepName: string;
|
||||
requiredUserLevelId: number;
|
||||
requiredUserLevelName: string;
|
||||
canSkip: boolean;
|
||||
autoApproveAfterHours?: number;
|
||||
isActive: boolean;
|
||||
parentStepId?: number;
|
||||
parentStepName?: string;
|
||||
conditionType: string;
|
||||
conditionValue: string;
|
||||
isParallel: boolean;
|
||||
branchName?: string;
|
||||
branchOrder?: number;
|
||||
hasChildren: boolean;
|
||||
isFirstStep: boolean;
|
||||
isLastStep: boolean;
|
||||
}
|
||||
|
||||
export interface ComprehensiveWorkflow {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
isDefault: boolean;
|
||||
isActive: boolean;
|
||||
requiresApproval: boolean;
|
||||
autoPublish: boolean;
|
||||
clientId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
totalSteps: number;
|
||||
activeSteps: number;
|
||||
hasBranches: boolean;
|
||||
maxStepOrder: number;
|
||||
}
|
||||
|
||||
export interface ClientSettings {
|
||||
id: number;
|
||||
clientId: string;
|
||||
requiresApproval: boolean;
|
||||
defaultWorkflowId: number;
|
||||
defaultWorkflowName: string;
|
||||
autoPublishArticles: boolean;
|
||||
approvalExemptUsers?: any;
|
||||
approvalExemptRoles?: any;
|
||||
approvalExemptCategories?: any;
|
||||
requireApprovalFor?: any;
|
||||
skipApprovalFor?: any;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
exemptUsersDetails: any[];
|
||||
exemptRolesDetails: any[];
|
||||
exemptCategoriesDetails: any[];
|
||||
}
|
||||
|
||||
export interface WorkflowStatistics {
|
||||
totalArticlesProcessed: number;
|
||||
pendingArticles: number;
|
||||
approvedArticles: number;
|
||||
rejectedArticles: number;
|
||||
averageProcessingTime: number;
|
||||
mostActiveStep: string;
|
||||
lastUsedAt?: string;
|
||||
}
|
||||
|
||||
export interface ComprehensiveWorkflowResponse {
|
||||
workflow: ComprehensiveWorkflow;
|
||||
steps: ComprehensiveWorkflowStep[];
|
||||
clientSettings: ClientSettings;
|
||||
relatedData: {
|
||||
userLevels: any[];
|
||||
};
|
||||
statistics: WorkflowStatistics;
|
||||
lastUpdated: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
fullname: string;
|
||||
|
|
@ -81,7 +199,7 @@ export interface ArticleCategory {
|
|||
|
||||
export interface Province {
|
||||
id: number;
|
||||
provinceName: string;
|
||||
prov_name: string;
|
||||
}
|
||||
|
||||
// API Functions
|
||||
|
|
@ -126,7 +244,7 @@ export async function getArticleCategories() {
|
|||
}
|
||||
|
||||
export async function getProvinces() {
|
||||
const url = "provinces";
|
||||
const url = "provinces?limit=100";
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
|
|
@ -144,3 +262,9 @@ export async function deleteApprovalWorkflow(id: number) {
|
|||
const url = `approval-workflows/${id}`;
|
||||
return httpDeleteInterceptor(url);
|
||||
}
|
||||
|
||||
export async function getApprovalWorkflowComprehensiveDetails(workflowId: number) {
|
||||
const url = `approval-workflows/comprehensive-details`;
|
||||
const data = { workflowId };
|
||||
return httpPostInterceptor(url, data);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue