diff --git a/app/[locale]/(admin)/admin/settings/tenant/page.tsx b/app/[locale]/(admin)/admin/settings/tenant/page.tsx index d07b0bd..9fc0104 100644 --- a/app/[locale]/(admin)/admin/settings/tenant/page.tsx +++ b/app/[locale]/(admin)/admin/settings/tenant/page.tsx @@ -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(null); + const [workflow, setWorkflow] = useState(null); const [userLevels, setUserLevels] = useState([]); 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 + @@ -132,7 +158,34 @@ function TenantSettingsContent() { ({ + 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() { - {workflow.name} + {workflow.workflow.name}
- {workflow.isDefault && ( + {workflow.workflow.isDefault && ( Default )} - {workflow.isActive ? ( + {workflow.workflow.isActive ? ( Active @@ -163,34 +216,32 @@ function TenantSettingsContent() {

- {workflow.description} + {workflow.workflow.description}

-
{workflow.steps?.length || 0}
-
Workflow Steps
+
{workflow.workflow.totalSteps}
+
Total Steps
-
- {workflow.requiresApproval ? 'Yes' : 'No'} +
{workflow.workflow.activeSteps}
+
Active Steps
+
+ +
+
+ {workflow.workflow.requiresApproval ? 'Yes' : 'No'}
Requires Approval
- -
-
- {workflow.autoPublish ? 'Yes' : 'No'} -
-
Auto Publish
-
-
- {workflow.clientApprovalSettings?.approvalExemptUsers?.length || 0} +
+ {workflow.workflow.autoPublish ? 'Yes' : 'No'}
-
Exempt Users
+
Auto Publish
@@ -210,6 +261,7 @@ function TenantSettingsContent() {
{step.conditionType && `Condition: ${step.conditionType}`} {step.autoApproveAfterHours && ` • Auto-approve after ${step.autoApproveAfterHours}h`} + {step.requiredUserLevelName && ` • Required Level: ${step.requiredUserLevelName}`}
@@ -224,6 +276,21 @@ function TenantSettingsContent() { Parallel )} + {step.isActive && ( + + Active + + )} + {step.isFirstStep && ( + + First Step + + )} + {step.isLastStep && ( + + Last Step + + )}
))} @@ -231,38 +298,102 @@ function TenantSettingsContent() { )} - {/* Client Settings Overview */} - {workflow.clientApprovalSettings && ( -
-

Client Approval Settings

-
-
-
Exempt Users
-
- {workflow.clientApprovalSettings.approvalExemptUsers?.length || 0} users -
+ {/* Client Settings */} +
+

Client Settings

+
+
+
Default Workflow
+
{workflow.clientSettings.defaultWorkflowName}
+
+
+
Auto Publish Articles
+
+ {workflow.clientSettings.autoPublishArticles ? 'Yes' : 'No'}
-
-
Exempt Roles
-
- {workflow.clientApprovalSettings.approvalExemptRoles?.length || 0} roles -
+
+
+
Requires Approval
+
+ {workflow.clientSettings.requiresApproval ? 'Yes' : 'No'}
-
-
Exempt Categories
-
- {workflow.clientApprovalSettings.approvalExemptCategories?.length || 0} categories -
-
-
-
Content Types
-
- {workflow.clientApprovalSettings.requireApprovalFor?.length || 0} requiring approval -
+
+
+
Settings Active
+
+ {workflow.clientSettings.isActive ? 'Yes' : 'No'}
- )} +
+ + {/* Statistics */} +
+

Workflow Statistics

+
+
+
Total Articles Processed
+
{workflow.statistics.totalArticlesProcessed}
+
+
+
Pending Articles
+
{workflow.statistics.pendingArticles}
+
+
+
Approved Articles
+
{workflow.statistics.approvedArticles}
+
+
+
Rejected Articles
+
{workflow.statistics.rejectedArticles}
+
+
+
Average Processing Time
+
{workflow.statistics.averageProcessingTime}h
+
+
+
Most Active Step
+
{workflow.statistics.mostActiveStep || 'N/A'}
+
+
+
+ + {/* Workflow Metadata */} +
+

Workflow Information

+
+
+
Client ID
+
{workflow.workflow.clientId}
+
+
+
Created At
+
+ {new Date(workflow.workflow.createdAt).toLocaleString()} +
+
+
+
Updated At
+
+ {new Date(workflow.workflow.updatedAt).toLocaleString()} +
+
+
+
Workflow ID
+
{workflow.workflow.id}
+
+
+
Has Branches
+
+ {workflow.workflow.hasBranches ? 'Yes' : 'No'} +
+
+
+
Max Step Order
+
{workflow.workflow.maxStepOrder}
+
+
+
) : ( @@ -295,7 +426,7 @@ function TenantSettingsContent() { Create User Level - + Create New User Level @@ -465,7 +596,7 @@ function TenantSettingsContent() {
Parent Level: - {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}`}
)} @@ -522,9 +653,5 @@ function TenantSettingsContent() { } export default function TenantSettingsPage() { - return ( - - - - ); + return ; } diff --git a/app/[locale]/(admin)/layout.tsx b/app/[locale]/(admin)/layout.tsx index aa42048..a12ddab 100644 --- a/app/[locale]/(admin)/layout.tsx +++ b/app/[locale]/(admin)/layout.tsx @@ -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 ( - - - - {children} - + + + + + {children} + + ); diff --git a/components/form/UserLevelsForm.tsx b/components/form/UserLevelsForm.tsx index 510c1f7..08cf133 100644 --- a/components/form/UserLevelsForm.tsx +++ b/components/form/UserLevelsForm.tsx @@ -73,8 +73,8 @@ export const UserLevelsForm: React.FC = ({ 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 = ({ 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 = ({ } // 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 = ({ 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 = ({ 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 = ({ 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 = ({ Basic Information - Hierarchy + {/* Hierarchy */} Bulk Operations @@ -485,11 +488,14 @@ export const UserLevelsForm: React.FC = ({ 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 = ({ 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 = ({ {/* Hierarchy Tab */} - + {/* @@ -572,7 +578,7 @@ export const UserLevelsForm: React.FC = ({ - + */} {/* Bulk Operations Tab */} diff --git a/components/icons.tsx b/components/icons.tsx index 9298cbb..969010b 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -2868,19 +2868,53 @@ export const ArrowDownIcon = ({ size = 24, width, height, ...props }: IconSvgPro export const SettingsIcon = ({ size = 24, width, height, ...props }: IconSvgProps) => ( + - + +); + +export const AlertTriangleIcon = ({ size = 24, width, height, ...props }: IconSvgProps) => ( + + + + + +); + +export const CheckCircleIcon = ({ size = 24, width, height, ...props }: IconSvgProps) => ( + + + ); diff --git a/components/layout/Layout.tsx b/components/layout/Layout.tsx index 65cd8f1..8bcbf0a 100644 --- a/components/layout/Layout.tsx +++ b/components/layout/Layout.tsx @@ -12,7 +12,9 @@ export default function Layout({ children }: LayoutProps) {
- {/* Header content will be provided by parent components */} +
+ {/* Header content */} +
{children} diff --git a/components/modals/WorkflowModalProvider.tsx b/components/modals/WorkflowModalProvider.tsx index cd6e01c..e53fb71 100644 --- a/components/modals/WorkflowModalProvider.tsx +++ b/components/modals/WorkflowModalProvider.tsx @@ -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; } const WorkflowModalContext = createContext(undefined); @@ -33,24 +36,88 @@ interface WorkflowModalProviderProps { export function WorkflowModalProvider({ children }: WorkflowModalProviderProps) { const [isModalOpen, setIsModalOpen] = useState(false); const [workflowInfo, setWorkflowInfo] = useState(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 ( - + {children} ); diff --git a/components/modals/WorkflowSetupModal.tsx b/components/modals/WorkflowSetupModal.tsx index cf57f1c..730247f 100644 --- a/components/modals/WorkflowSetupModal.tsx +++ b/components/modals/WorkflowSetupModal.tsx @@ -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; } -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 ( - - + + { + // 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(); + } + }} + > {workflowInfo?.hasWorkflowSetup ? ( - +
+ +
) : ( - +
+ ! +
)} Workflow Status
@@ -63,7 +90,9 @@ export default function WorkflowSetupModal({ isOpen, onClose, workflowInfo }: Wo
- +
+ ! +

Workflow Belum Dikonfigurasi @@ -80,74 +109,71 @@ export default function WorkflowSetupModal({ isOpen, onClose, workflowInfo }: Wo Setup Workflow - + {(pathname?.includes('/admin/settings/tenant') || pathname?.includes('/tenant')) && ( + + )}

- ) : ( + ) : '' // Workflow Setup Complete - - -
- -
-

- Workflow Sudah Dikonfigurasi -

-
-
- Workflow: - {workflowInfo.defaultWorkflowName} -
-
- Requires Approval: - - {workflowInfo.requiresApproval ? 'Yes' : 'No'} - -
-
- Auto Publish: - - {workflowInfo.autoPublishArticles ? 'Yes' : 'No'} - -
-
- Status: - - {workflowInfo.isApprovalActive ? 'Active' : 'Inactive'} - -
-
-
- - -
-
-
-
-
- )} + // + // + //
+ //
+ // + //
+ //
+ //

+ // Workflow Sudah Dikonfigurasi + //

+ //
+ //
+ // Workflow: + // {workflowInfo.defaultWorkflowName} + //
+ //
+ // Requires Approval: + // + // {workflowInfo.requiresApproval ? 'Yes' : 'No'} + // + //
+ //
+ // Auto Publish: + // + // {workflowInfo.autoPublishArticles ? 'Yes' : 'No'} + // + //
+ //
+ // Status: + // + // {workflowInfo.isApprovalActive ? 'Active' : 'Inactive'} + // + //
+ //
+ //
+ // + //
+ //
+ //
+ //
+ //
+ }
diff --git a/components/partials/header/index.tsx b/components/partials/header/index.tsx index f63ec32..21aa00f 100644 --- a/components/partials/header/index.tsx +++ b/components/partials/header/index.tsx @@ -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 ( <> diff --git a/hooks/use-auth.ts b/hooks/use-auth.ts index ca956b9..7a9cb24 100644 --- a/hooks/use-auth.ts +++ b/hooks/use-auth.ts @@ -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", diff --git a/hooks/useWorkflowStatusCheck.ts b/hooks/useWorkflowStatusCheck.ts index 65e2959..9f65a26 100644 --- a/hooks/useWorkflowStatusCheck.ts +++ b/hooks/useWorkflowStatusCheck.ts @@ -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); diff --git a/service/approval-workflows.ts b/service/approval-workflows.ts index 154d0e8..f6337ad 100644 --- a/service/approval-workflows.ts +++ b/service/approval-workflows.ts @@ -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); +}