From 96f283b951057beacbcae74e846d865a43982a41 Mon Sep 17 00:00:00 2001 From: Saeed AB Date: Wed, 27 Aug 2025 13:36:31 +0330 Subject: [PATCH] feat/digital-innovation (#1) Co-authored-by: MehrdadAdabi <126083584+mehrdadAdabi@users.noreply.github.com> Co-authored-by: mehrdad Reviewed-on: https://git.pelekan.org/Saeed0920/inogen/pulls/1 --- .../digital-innovation-page.tsx | 1097 +++ .../process-innovation-page.tsx | 138 +- app/components/dashboard/sidebar.tsx | 20 +- app/components/ui/custom-bar-chart.tsx | 51 +- app/components/ui/table.tsx | 2 +- app/routes.ts | 9 +- app/routes/digital-innovation-page.tsx | 17 + app/routes/project-management.tsx | 2 +- package-lock.json | 6859 +++++++++++++++++ package.json | 2 +- 10 files changed, 8087 insertions(+), 110 deletions(-) create mode 100644 app/components/dashboard/project-management/digital-innovation-page.tsx create mode 100644 app/routes/digital-innovation-page.tsx create mode 100644 package-lock.json diff --git a/app/components/dashboard/project-management/digital-innovation-page.tsx b/app/components/dashboard/project-management/digital-innovation-page.tsx new file mode 100644 index 0000000..15a18df --- /dev/null +++ b/app/components/dashboard/project-management/digital-innovation-page.tsx @@ -0,0 +1,1097 @@ +import { useState, useEffect, useCallback, useRef, useMemo } from "react"; +import { DashboardLayout } from "../layout"; +import { Card, CardContent } from "~/components/ui/card"; +import { Button } from "~/components/ui/button"; +import { Badge } from "~/components/ui/badge"; +import { Checkbox } from "~/components/ui/checkbox"; +import moment from "moment-jalaali"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "~/components/ui/table"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "~/components/ui/dialog"; +import { + ChevronUp, + ChevronDown, + RefreshCw, + Building2, + PickaxeIcon, + UserIcon, + UsersIcon, +} from "lucide-react"; +import apiService from "~/lib/api"; +import toast from "react-hot-toast"; +import { + Database, + Zap, + TrendingDown, + TrendingUp, + Key, + Sprout, + BrainCircuit, + LoaderCircle, +} from "lucide-react"; +import { CustomBarChart } from "~/components/ui/custom-bar-chart"; + +moment.loadPersian({ usePersianDigits: true }); + +interface SortConfig { + field: string; + direction: "asc" | "desc"; +} + +interface StatsCard { + id: string; + title: string; + value: string; + description?: string; + icon: React.ReactNode; + color: string; +} + +// Raw API response interface for digital innovation metrics +interface DigitalInnovationMetrics { + count_innovation_digital_projects: string; + increased_revenue: string; + increased_revenue_percent: string; + reduce_costs: string; + reduce_costs_percent: string; + reduce_energy_consumption: string; + reduce_energy_consumption_percent: string; + resource_productivity: string; + resource_productivity_percent: string; +} + +// Normalized interface for digital innovation stats +interface DigitalInnovationStats { + // totalDigitalProjects: number; + increasedRevenue: number; + increasedRevenuePercent: number; + reduceCosts: number; + reduceCostsPercent: number; + reduceEnergyConsumption: number; + reduceEnergyConsumptionPercent: number; + resourceProductivity: number; + resourceProductivityPercent: number; +} + +enum DigitalCardLabel { + decreasCost = "کاهش هزینه‌ها", + increaseRevenue = "افزایش درآمد", + performance = "بهره‌وری منابع", + decreaseEnergy = "کاهش مصرف انرژی", +} + +enum projectStatus { + propozal = "پروپوزال", + contract = "پیشنویس قرارداد", + inprogress = "در حال انجام", + stop = "متوقف شده", + mafasa = "مرحله مفاصا", + finish = "پایان یافته", +} +interface ProcessInnovationData { + WorkflowID: number; + desired_strategy: string; + digital_capability: string; + digital_competence: string; + digital_puberty_elements: string; + innovation_cost_reduction: number | string; + operational_plan: string; + originality_digital_solution: string; + project_description: string; + project_no: string; + project_rating: number | string; + project_status: string; + reduce_costs_percent: number; + title: string; +} + +interface HouseItem { + index: number; + color?: string; + style?: string; +} + +interface ListItem { + label: string; + development: number; + house: HouseItem[]; +} + +const columns = [ + // { key: "select", label: "", sortable: false, width: "50px" }, + { key: "project_no", label: "شماره پروژه", sortable: true, width: "140px" }, + { key: "title", label: "عنوان پروژه", sortable: true, width: "400px" }, + { + key: "project_status", + label: "وضعیت پروژه", + sortable: true, + width: "140px", + }, + { + key: "project_rating", + label: "امتیاز پروژه", + sortable: true, + width: "140px", + }, + { key: "details", label: "جزئیات پروژه", sortable: false, width: "140px" }, +]; + +export function DigitalInnovationPage() { + const [projects, setProjects] = useState([]); + const [loading, setLoading] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize] = useState(20); + const [hasMore, setHasMore] = useState(true); + const [totalCount, setTotalCount] = useState(0); + const [actualTotalCount, setActualTotalCount] = useState(0); + const [statsLoading, setStatsLoading] = useState(false); + const [rating, setRating] = useState([]); + const [dialogInfo, setDialogInfo] = useState(); + const [stats, setStats] = useState({ + increasedRevenue: 0, + increasedRevenuePercent: 0, + reduceCosts: 0, + reduceCostsPercent: 0, + reduceEnergyConsumption: 0, + reduceEnergyConsumptionPercent: 0, + resourceProductivity: 0, + resourceProductivityPercent: 0, + }); + const [sortConfig, setSortConfig] = useState({ + field: "start_date", + direction: "asc", + }); + const [selectedProjects, setSelectedProjects] = useState>( + new Set() + ); + const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); + const [avarage, setAvarage] = useState(0); + const observerRef = useRef(null); + const fetchingRef = useRef(false); + + // Selection handlers + const handleSelectAll = () => { + if (selectedProjects.size === projects.length) { + setSelectedProjects(new Set()); + } else { + setSelectedProjects(new Set(projects.map((p: any) => p.project_no))); + } + }; + + const handleProjectDetails = (project: ProcessInnovationData) => { + const model: ListItem = { + label: `فرآیند-${project.WorkflowID}`, + development: +project.project_rating, + house: [], + }; + setRating([model]); + setDialogInfo(project); + setDetailsDialogOpen(true); + }; + + const formatNumber = (value: string | number) => { + if (!value) return "0"; + const numericValue = typeof value === "string" ? parseFloat(value) : value; + if (isNaN(numericValue)) return "0"; + return new Intl.NumberFormat("fa-IR").format(numericValue); + }; + + const statsCards: StatsCard[] = [ + { + id: "production-stops-prevention", + title: DigitalCardLabel.decreasCost, + value: formatNumber(stats.reduceCosts.toFixed?.(1) ?? stats.reduceCosts), + description: "میلیون ریال کاهش یافته", + icon: , + color: "text-emerald-400", + }, + { + id: "bottleneck-removal", + title: DigitalCardLabel.increaseRevenue, + value: formatNumber(stats.increasedRevenue), + description: "میلیون ریال افزایش یافته", + icon: , + color: "text-emerald-400", + }, + + { + id: "currency-reduction", + title: DigitalCardLabel.performance, + value: formatNumber( + stats.resourceProductivity.toFixed?.(0) ?? stats.resourceProductivity + ), + description: "هزار تن صرفه جوریی شده", + icon: , + color: "text-emerald-400", + }, + { + id: "frequent-failures-reduction", + title: DigitalCardLabel.decreaseEnergy, + value: formatNumber( + stats.reduceEnergyConsumption.toFixed?.(1) ?? + stats.reduceEnergyConsumption + ), + description: "مگاوات کاهش یافته", + icon: , + color: "text-emerald-400", + }, + ]; + + const fetchTable = async (reset = false) => { + if (fetchingRef.current) { + return; + } + + try { + fetchingRef.current = true; + + if (reset) { + setLoading(true); + setCurrentPage(1); + } else { + setLoadingMore(true); + } + + const pageToFetch = reset ? 1 : currentPage; + + const response = await apiService.select({ + ProcessName: "project", + OutputFields: [ + "project_no", + "title", + "project_status", + "project_rating", + "project_description", + "digital_competence", + "originality_digital_solution", + "digital_puberty_elements", + "digital_capability", + "operational_plan", + "desired_strategy", + "innovation_cost_reduction", + "reduce_costs_percent", + ], + Sorts: [[sortConfig.field, sortConfig.direction]], + Conditions: [["type_of_innovation", "=", "نوآوری دیجیتال"]], + Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, + }); + + // console.log(JSON.parse(response.data)); + if (response.state === 0) { + const dataString = response.data; + if (dataString && typeof dataString === "string") { + try { + const parsedData: ProcessInnovationData = JSON.parse(dataString); + if (Array.isArray(parsedData)) { + if (reset) { + setProjects(parsedData); + calculateAverage(parsedData); + setTotalCount(parsedData.length); + } else { + setProjects((prev) => [...prev, ...parsedData]); + setTotalCount((prev) => prev + parsedData.length); + } + setHasMore(parsedData.length === pageSize); + } else { + if (reset) { + setProjects([]); + setTotalCount(0); + } + setHasMore(false); + } + } catch (parseError) { + console.error("Error parsing project data:", parseError); + if (reset) { + setProjects([]); + setTotalCount(0); + } + setHasMore(false); + } + } else { + if (reset) { + setProjects([]); + setTotalCount(0); + } + setHasMore(false); + } + } else { + toast.error(response.message || "خطا در دریافت اطلاعات پروژه‌ها"); + if (reset) { + setProjects([]); + setTotalCount(0); + } + setHasMore(false); + } + } catch (error) { + console.error("Error fetching projects:", error); + toast.error("خطا در دریافت اطلاعات پروژه‌ها"); + if (reset) { + setProjects([]); + setTotalCount(0); + } + setHasMore(false); + } finally { + setLoading(false); + setLoadingMore(false); + + fetchingRef.current = false; + } + }; + + const loadMore = useCallback(() => { + if (!loadingMore && hasMore && !loading) { + setCurrentPage((prev) => prev + 1); + } + }, [loadingMore, hasMore, loading]); + + useEffect(() => { + fetchTable(true); + fetchTotalCount(); + fetchStats(); + }, [sortConfig]); + + useEffect(() => { + if (currentPage > 1) { + fetchTable(false); + } + }, [currentPage]); + + useEffect(() => { + const scrollContainer = document.querySelector(".overflow-auto"); + + const handleScroll = () => { + if (!scrollContainer || !hasMore || loadingMore) return; + + const { scrollTop, scrollHeight, clientHeight } = scrollContainer; + const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; + + if (scrollPercentage >= 0.9) { + loadMore(); + } + }; + + if (scrollContainer) { + scrollContainer.addEventListener("scroll", handleScroll); + } + + return () => { + if (scrollContainer) { + scrollContainer.removeEventListener("scroll", handleScroll); + } + }; + }, [loadMore, hasMore, loadingMore]); + + const handleSort = (field: string) => { + fetchingRef.current = false; + setSortConfig((prev) => ({ + field, + direction: + prev.field === field && prev.direction === "asc" ? "desc" : "asc", + })); + fetchTotalCount(); + fetchStats(); + setCurrentPage(1); + setProjects([]); + setHasMore(true); + }; + + const fetchTotalCount = async () => { + try { + const response = await apiService.select({ + ProcessName: "project", + OutputFields: ["count(project_no)"], + Conditions: [["type_of_innovation", "=", "نوآوری دیجیتال"]], + }); + + if (response.state === 0) { + const dataString = response.data; + if (dataString && typeof dataString === "string") { + try { + const parsedData = JSON.parse(dataString); + if (Array.isArray(parsedData) && parsedData[0]) { + const count = parsedData[0].project_no_count || 0; + setActualTotalCount(count); + // Keep stats in sync if backend stats not yet loaded + setStats((prev) => ({ ...prev, totalProjects: count })); + } + } catch (parseError) { + console.error("Error parsing count data:", parseError); + } + } + } + } catch (error) { + console.error("Error fetching total count:", error); + } + }; + + // Fetch aggregated stats from backend call API (innovation_process_function) + const fetchStats = async () => { + try { + setStatsLoading(true); + const raw = await apiService.callInnovationProcess({ + innovation_digital_function: {}, + }); + + let payload: DigitalInnovationMetrics = raw?.data; + if (typeof payload === "string") { + try { + payload = JSON.parse(payload); + } catch {} + } + + const parseNum = (v: unknown): number => { + if (v == null) return 0; + if (typeof v === "number") return v; + if (typeof v === "string") { + const cleaned = v.replace(/,/g, "").trim(); + const n = parseFloat(cleaned); + return isNaN(n) ? 0 : n; + } + return 0; + }; + const normalized: DigitalInnovationStats = { + increasedRevenue: parseNum(payload?.increased_revenue), + increasedRevenuePercent: parseNum(payload?.increased_revenue_percent), + reduceCosts: parseNum(payload?.reduce_costs), + reduceCostsPercent: parseNum(payload?.reduce_costs_percent), + reduceEnergyConsumption: parseNum(payload?.reduce_energy_consumption), + reduceEnergyConsumptionPercent: parseNum( + payload?.reduce_energy_consumption_percent + ), + resourceProductivity: parseNum(payload?.resource_productivity), + resourceProductivityPercent: parseNum( + payload?.resource_productivity_percent + ), + }; + + setStats(normalized); + } catch (error) { + console.error("Error fetching stats:", error); + } finally { + setStatsLoading(false); + } + }; + + // const handleRefresh = () => { + // fetchingRef.current = false; + // setCurrentPage(1); + // setProjects([]); + // setHasMore(true); + // fetchTable(true); + // fetchTotalCount(); + // fetchStats(); + // }; + + const formatCurrency = (amount: string | number) => { + if (!amount) return "0 ریال"; + const numericAmount = + typeof amount === "string" + ? parseFloat(amount.replace(/,/g, "")) + : amount; + if (isNaN(numericAmount)) return "0 ریال"; + return new Intl.NumberFormat("fa-IR").format(numericAmount) + " ریال"; + }; + + const renderProgress = useMemo(() => { + const total = 10; + for (let i = 0; i < rating.length; i++) { + const currentElm = rating[i]; + currentElm.house = []; + const greenBoxes = Math.floor((total * currentElm.development) / 100); + const partialPercent = + (total * currentElm.development) / 100 - greenBoxes; + for (let j = 0; j < greenBoxes; j++) { + currentElm.house.push({ + index: j, + color: "!bg-emerald-400", + }); + } + if (partialPercent != 0 && greenBoxes != 10) + currentElm.house.push({ + index: greenBoxes + 1, + style: `linear-gradient( + to right, + oklch(76.5% 0.177 163.223) 0%, + oklch(76.5% 0.177 163.223) ${partialPercent * 100}%, + oklch(55.1% 0.027 264.364) ${partialPercent * 100}%, + oklch(55.1% 0.027 264.364) 100% + )`, + }); + } + }, [rating]); + + const ststusColor = (status: projectStatus): any => { + let el = null; + switch (status) { + case projectStatus.contract: + el = "teal"; + break; + case projectStatus.finish: + el = "info"; + break; + case projectStatus.stop: + el = "warning"; + break; + case projectStatus.inprogress: + el = "teal"; + break; + case projectStatus.mafasa: + el = "destructive"; + break; + case projectStatus.propozal: + el = "info"; + } + return el; + }; + + const renderCellContent = (item: any, column: any) => { + const value = item[column.key as keyof ProcessInnovationData]; + + switch (column.key) { + case "select": + return ( + handleSelectProject(item.project_no)} + className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600" + /> + ); + case "details": + return ( + + ); + case "amount_currency_reduction": + return ( + + {formatCurrency(String(value))} + + ); + case "project_no": + return ( + + {String(value)} + + ); + case "title": + return {String(value)}; + case "project_status": + return ( +
+ + {String(value)} +
+ ); + case "project_rating": + return ( + + {formatNumber(String(value))} + + ); + case "reduce_prevention_production_stops": + case "throat_removal": + case "Reduce_rate_failure": + return ( + + {formatNumber(String(value))} + + ); + default: + return {String(value) || "-"}; + } + }; + + const calculateAverage = (data: Array) => { + let number = 0; + data.map( + (item: ProcessInnovationData) => (number = number + +item.project_rating) + ); + setAvarage(number / data.length); + }; + + return ( + +
+ {/* Stats Cards */} +
+
+ {/* Stats Grid */} +
+ {loading || statsLoading + ? // Loading skeleton for stats cards - matching new design + Array.from({ length: 4 }).map((_, index) => ( + + +
+
+
+
+
+
+
+
+
+
+
+
+ + + )) + : statsCards.map((card) => ( + + +
+
+

+ {card.title} +

+
+ {card.icon} +
+
+
+

+ {card.value} +

+

+ {card.description} +

+
+
+
+
+ ))} +
+
+ + {/* Process Impacts Chart */} + + {/* */} + + {/* */} + +
+ + {/* Data Table */} + + +
+ + + + {columns.map((column) => ( + + {column.key === "select" ? ( +
+ 0 + } + onCheckedChange={handleSelectAll} + className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600" + /> +
+ ) : column.sortable ? ( + + ) : ( + column.label + )} +
+ ))} +
+
+ + {loading ? ( + // Skeleton loading rows (compact) + Array.from({ length: 10 }).map((_, index) => ( + + {columns.map((column) => ( + +
+
+
+
+ + ))} + + )) + ) : projects.length === 0 ? ( + + + + هیچ پروژه‌ای یافت نشد + + + + ) : ( + projects.map((project, index) => ( + + {columns.map((column) => ( + + {renderCellContent(project, column)} + + ))} + + )) + )} + +
+
+ + {/* Infinite scroll trigger */} +
+ {loadingMore && ( +
+
+ + +
+
+ )} +
+
+ + {/* Footer */} +
+
+
+
+ کل پروژه ها :{formatNumber(actualTotalCount)} +
+
+ {/* Project number column - empty */} +
+ {/* Title column - empty */} +
+ {/* Project status column - empty */} +
+ + + + +
+ {/* Project rating column - show average */} +
+
+ میانگین امتیاز :‌ +
+
+ {formatNumber(((avarage ?? 0) as number).toFixed?.(1) ?? 0)} +
+
+
+
+
+
+ + {/* Project Details Dialog */} + + + + + شرح پروژه + + +
+
+ + {dialogInfo?.title} + +

+ {dialogInfo?.project_description} +

+
+ ویژگی های اصلی پروژه: +
+
+
+ + + شایستگی دیجیتال: + +
+ + {dialogInfo?.digital_capability} + +
+
+
+ + + اصالت راهکار دیجیتال: + +
+ + {dialogInfo?.digital_competence} + +
+
+
+ + + المان های بلوغ دیجیتال: + +
+ + {dialogInfo?.digital_puberty_elements} + +
+
+
+
+
+
+ + توسعه قابلیت های دیجیتال:{" "} + +
+
+ + + {dialogInfo?.digital_capability} + +
+
+
+ +
+ + برنامه های عملیاتی مرتبط: + +
+
+ + + {dialogInfo?.operational_plan} + +
+
+
+
+ + استراتژی های مورد نظر: + +
+
+ + + {dialogInfo?.desired_strategy} + +
+ {/*
+ + قابلیت شماره یک +
*/} + {/*
+ + قابلیت شماره یک +
*/} +
+
+
+
+
+
+ + کاهش هزینه ها + + +
+
+ + %{" "} + {formatNumber( + ( + Math.round( + dialogInfo?.reduce_costs_percent! * 100 + ) / 100 + ).toFixed(2) + )} + + + درصد به کل هزینه ها + +
+ +
+ + {formatNumber(+dialogInfo?.innovation_cost_reduction!)} + + + میلیون ریال + +
+
+
+
+
+
+ عنوان فرآیند + درصد پیشرفت +
+
+ {rating.map((el, index) => { + return ( +
+ {el.label} +
+ {Array.from({ length: 10 }, (_, i) => { + return ( + + ); + })} +
+
+ ); + })} +
+
+
+
+
+
+ + ); +} + +export default DigitalInnovationPage; diff --git a/app/components/dashboard/project-management/process-innovation-page.tsx b/app/components/dashboard/project-management/process-innovation-page.tsx index 365f417..6d52d71 100644 --- a/app/components/dashboard/project-management/process-innovation-page.tsx +++ b/app/components/dashboard/project-management/process-innovation-page.tsx @@ -34,7 +34,6 @@ import { import apiService from "~/lib/api"; import toast from "react-hot-toast"; import { Funnel, Wrench, CirclePause, DollarSign } from "lucide-react"; -import ProjectDetail from "../projects/project-detail"; moment.loadPersian({ usePersianDigits: true }); interface ProcessInnovationData { @@ -150,7 +149,6 @@ export function ProcessInnovationPage() { }; const handleProjectDetails = (project: ProcessInnovationData) => { - console.log(project); setSelectedProjectDetails(project); setDetailsDialogOpen(true); }; @@ -169,7 +167,7 @@ export function ProcessInnovationPage() { title: "جلوگیری از توقفات تولید", value: formatNumber( stats.productionStopsPreventionSum.toFixed?.(1) ?? - stats.productionStopsPreventionSum, + stats.productionStopsPreventionSum, ), description: "تن افزایش یافته", icon: , @@ -199,7 +197,7 @@ export function ProcessInnovationPage() { title: "کاهش خرابی های پرتکرار", value: formatNumber( stats.frequentFailuresReductionSum.toFixed?.(1) ?? - stats.frequentFailuresReductionSum, + stats.frequentFailuresReductionSum, ), description: "مجموع درصد کاهش خرابی", icon: , @@ -402,7 +400,7 @@ export function ProcessInnovationPage() { if (typeof payload === "string") { try { payload = JSON.parse(payload); - } catch {} + } catch { } } const parseNum = (v: unknown): number => { @@ -575,73 +573,73 @@ export function ProcessInnovationPage() {
{loading || statsLoading ? // Loading skeleton for stats cards - matching new design - Array.from({ length: 4 }).map((_, index) => ( - - -
-
-
-
-
-
-
-
-
-
+ Array.from({ length: 4 }).map((_, index) => ( + + +
+
+
+
+
- - - )) +
+
+
+
+
+ + + )) : statsCards.map((card) => ( - - -
-
-

- {card.title} -

-
- {card.icon} -
-
-
-

- {card.value} -

-

- {card.description} -

+ + +
+
+

+ {card.title} +

+
+ {card.icon}
- - - ))} +
+

+ {card.value} +

+

+ {card.description} +

+
+
+
+
+ ))}
{/* Process Impacts Chart */} - + {formatNumber( ((stats.averageScore ?? 0) as number).toFixed?.(1) ?? - stats.averageScore ?? - 0, + stats.averageScore ?? + 0, )}
@@ -883,9 +881,9 @@ export function ProcessInnovationPage() { {selectedProjectDetails?.start_date ? moment( - selectedProjectDetails?.start_date, - "YYYY-MM-DD", - ).format("YYYY/MM/DD") + selectedProjectDetails?.start_date, + "YYYY-MM-DD", + ).format("YYYY/MM/DD") : "-"}
@@ -898,9 +896,9 @@ export function ProcessInnovationPage() { {selectedProjectDetails?.done_date ? moment( - selectedProjectDetails?.done_date, - "YYYY-MM-DD", - ).format("YYYY/MM/DD") + selectedProjectDetails?.done_date, + "YYYY-MM-DD", + ).format("YYYY/MM/DD") : "-"}
diff --git a/app/components/dashboard/sidebar.tsx b/app/components/dashboard/sidebar.tsx index 91c80a6..1803dd0 100644 --- a/app/components/dashboard/sidebar.tsx +++ b/app/components/dashboard/sidebar.tsx @@ -149,7 +149,7 @@ export function Sidebar({ React.useEffect(() => { const autoExpandParents = () => { const newExpandedItems: string[] = []; - + menuItems.forEach((item) => { if (item.children) { const hasActiveChild = item.children.some( @@ -160,10 +160,10 @@ export function Sidebar({ } } }); - + setExpandedItems(newExpandedItems); }; - + autoExpandParents(); }, [location.pathname]); @@ -200,8 +200,8 @@ export function Sidebar({ const renderMenuItem = (item: MenuItem, level = 0) => { const isActive = isActiveRoute(item.href, item.children); - const isExpanded = expandedItems.includes(item.id) || - (item.children && item.children.some(child => + const isExpanded = expandedItems.includes(item.id) || + (item.children && item.children.some(child => child.href && location.pathname === child.href )); const hasChildren = item.children && item.children.length > 0; @@ -265,14 +265,14 @@ export function Sidebar({
) : ( -