UPDATE
This commit is contained in:
@@ -1,39 +1,39 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
// React Imports
|
||||
import type { RefObject } from 'react'
|
||||
import { useEffect, useMemo, useState, useCallback, useContext } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import type { RefObject } from "react";
|
||||
import { useEffect, useMemo, useState, useCallback, useContext } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
// Context Imports
|
||||
import NavbarSlotContext from '@/contexts/navbarSlotContext'
|
||||
import NavbarSlotContext from "@/contexts/navbarSlotContext";
|
||||
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Box from '@mui/material/Box'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
import Grid from "@mui/material/Grid2";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Box from "@mui/material/Box";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
|
||||
// Third-party imports
|
||||
import { useDragAndDrop } from '@formkit/drag-and-drop/react'
|
||||
import { animations } from '@formkit/drag-and-drop'
|
||||
import { useDragAndDrop } from "@formkit/drag-and-drop/react";
|
||||
import { animations } from "@formkit/drag-and-drop";
|
||||
|
||||
// Component Imports
|
||||
import FarmOverviewKPIs from '@views/dashboards/farm/FarmOverviewKPIs'
|
||||
import FarmWeatherCard from '@views/dashboards/farm/FarmWeatherCard'
|
||||
import FarmAlertsTracker from '@views/dashboards/farm/FarmAlertsTracker'
|
||||
import SensorValuesList from '@views/dashboards/farm/SensorValuesList'
|
||||
import SensorRadarChart from '@views/dashboards/farm/SensorRadarChart'
|
||||
import SensorComparisonChart from '@views/dashboards/farm/SensorComparisonChart'
|
||||
import FarmAlertsTimeline from '@views/dashboards/farm/FarmAlertsTimeline'
|
||||
import WaterNeedPrediction from '@views/dashboards/farm/WaterNeedPrediction'
|
||||
import YieldPredictionChart from '@views/dashboards/farm/YieldPredictionChart'
|
||||
import HarvestPredictionCard from '@views/dashboards/farm/HarvestPredictionCard'
|
||||
import SoilMoistureHeatmap from '@views/dashboards/farm/SoilMoistureHeatmap'
|
||||
import AnomalyDetectionCard from '@views/dashboards/farm/AnomalyDetectionCard'
|
||||
import NDVIHealthCard from '@views/dashboards/farm/NDVIHealthCard'
|
||||
import RecommendationsList from '@views/dashboards/farm/RecommendationsList'
|
||||
import EconomicOverview from '@views/dashboards/farm/EconomicOverview'
|
||||
import FarmOverviewKPIs from "@views/dashboards/farm/FarmOverviewKPIs";
|
||||
import FarmWeatherCard from "@views/dashboards/farm/FarmWeatherCard";
|
||||
import FarmAlertsTracker from "@views/dashboards/farm/FarmAlertsTracker";
|
||||
import SensorValuesList from "@views/dashboards/farm/SensorValuesList";
|
||||
import SensorRadarChart from "@views/dashboards/farm/SensorRadarChart";
|
||||
import SensorComparisonChart from "@views/dashboards/farm/SensorComparisonChart";
|
||||
import FarmAlertsTimeline from "@views/dashboards/farm/FarmAlertsTimeline";
|
||||
import WaterNeedPrediction from "@views/dashboards/farm/WaterNeedPrediction";
|
||||
import YieldPredictionChart from "@views/dashboards/farm/YieldPredictionChart";
|
||||
import HarvestPredictionCard from "@views/dashboards/farm/HarvestPredictionCard";
|
||||
import SoilMoistureHeatmap from "@views/dashboards/farm/SoilMoistureHeatmap";
|
||||
import AnomalyDetectionCard from "@views/dashboards/farm/AnomalyDetectionCard";
|
||||
import NDVIHealthCard from "@views/dashboards/farm/NDVIHealthCard";
|
||||
import RecommendationsList from "@views/dashboards/farm/RecommendationsList";
|
||||
import EconomicOverview from "@views/dashboards/farm/EconomicOverview";
|
||||
|
||||
// Config & Service
|
||||
import {
|
||||
@@ -43,18 +43,21 @@ import {
|
||||
DEFAULT_FARM_DASHBOARD_CONFIG,
|
||||
type RowId,
|
||||
type CardId,
|
||||
type FarmDashboardConfig
|
||||
} from '@views/dashboards/farm/farmDashboardConfig'
|
||||
import { farmDashboardService } from '@/libs/api/services/farmDashboardService'
|
||||
import FarmDashboardSettingsDropdown from '@views/dashboards/farm/FarmDashboardSettingsDropdown'
|
||||
type FarmDashboardConfig,
|
||||
} from "@views/dashboards/farm/farmDashboardConfig";
|
||||
import { farmDashboardService } from "@/libs/api/services/farmDashboardService";
|
||||
import FarmDashboardSettingsDropdown from "@views/dashboards/farm/FarmDashboardSettingsDropdown";
|
||||
|
||||
const cardRowSx = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
'& > *': { flex: 1, minHeight: 0 }
|
||||
}
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
"& > *": { flex: 1, minHeight: 0 },
|
||||
};
|
||||
|
||||
const CARD_COMPONENTS: Record<CardId, React.ComponentType<{ data?: Record<string, unknown> }>> = {
|
||||
const CARD_COMPONENTS: Record<
|
||||
CardId,
|
||||
React.ComponentType<{ data?: Record<string, unknown> }>
|
||||
> = {
|
||||
farmOverviewKpis: FarmOverviewKPIs,
|
||||
farmWeatherCard: FarmWeatherCard,
|
||||
farmAlertsTracker: FarmAlertsTracker,
|
||||
@@ -69,158 +72,179 @@ const CARD_COMPONENTS: Record<CardId, React.ComponentType<{ data?: Record<string
|
||||
soilMoistureHeatmap: SoilMoistureHeatmap,
|
||||
ndviHealthCard: NDVIHealthCard,
|
||||
recommendationsList: RecommendationsList,
|
||||
economicOverview: EconomicOverview
|
||||
}
|
||||
economicOverview: EconomicOverview,
|
||||
};
|
||||
|
||||
function mergeRowOrderAfterDrag(
|
||||
currentRowOrder: string[],
|
||||
newVisibleOrder: string[],
|
||||
visibleRows: string[]
|
||||
visibleRows: string[],
|
||||
): string[] {
|
||||
const result = [...currentRowOrder]
|
||||
let visibleIndex = 0
|
||||
const result = [...currentRowOrder];
|
||||
let visibleIndex = 0;
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
if (visibleRows.includes(result[i])) {
|
||||
result[i] = newVisibleOrder[visibleIndex++]
|
||||
result[i] = newVisibleOrder[visibleIndex++];
|
||||
}
|
||||
}
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
const FarmDashboardWrapper = () => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const { setSlotContent } = useContext(NavbarSlotContext)
|
||||
const [config, setConfig] = useState<FarmDashboardConfig>(DEFAULT_FARM_DASHBOARD_CONFIG)
|
||||
const t = useTranslations("farmDashboard");
|
||||
const { setSlotContent } = useContext(NavbarSlotContext);
|
||||
const [config, setConfig] = useState<FarmDashboardConfig>(
|
||||
DEFAULT_FARM_DASHBOARD_CONFIG,
|
||||
);
|
||||
|
||||
const cardLabels = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
(
|
||||
[
|
||||
'farmOverviewKpis',
|
||||
'farmWeatherCard',
|
||||
'farmAlertsTracker',
|
||||
'sensorValuesList',
|
||||
'sensorRadarChart',
|
||||
'sensorComparisonChart',
|
||||
'anomalyDetectionCard',
|
||||
'farmAlertsTimeline',
|
||||
'waterNeedPrediction',
|
||||
'harvestPredictionCard',
|
||||
'yieldPredictionChart',
|
||||
'soilMoistureHeatmap',
|
||||
'ndviHealthCard',
|
||||
'recommendationsList',
|
||||
'economicOverview'
|
||||
"farmOverviewKpis",
|
||||
"farmWeatherCard",
|
||||
"farmAlertsTracker",
|
||||
"sensorValuesList",
|
||||
"sensorRadarChart",
|
||||
"sensorComparisonChart",
|
||||
"anomalyDetectionCard",
|
||||
"farmAlertsTimeline",
|
||||
"waterNeedPrediction",
|
||||
"harvestPredictionCard",
|
||||
"yieldPredictionChart",
|
||||
"soilMoistureHeatmap",
|
||||
"ndviHealthCard",
|
||||
"recommendationsList",
|
||||
"economicOverview",
|
||||
] as CardId[]
|
||||
).map((id) => [id, t(`cards.${id}`)])
|
||||
).map((id) => [id, t(`cards.${id}`)]),
|
||||
) as Record<CardId, string>,
|
||||
[t]
|
||||
)
|
||||
[t],
|
||||
);
|
||||
|
||||
const rowLabels = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
(
|
||||
[
|
||||
'overviewKpis',
|
||||
'weatherAlerts',
|
||||
'sensorMonitoring',
|
||||
'sensorCharts',
|
||||
'alertsWater',
|
||||
'predictions',
|
||||
'soilHeatmap',
|
||||
'ndviRecommendations',
|
||||
'economic'
|
||||
"overviewKpis",
|
||||
"weatherAlerts",
|
||||
"sensorMonitoring",
|
||||
"sensorCharts",
|
||||
"alertsWater",
|
||||
"predictions",
|
||||
"soilHeatmap",
|
||||
"ndviRecommendations",
|
||||
"economic",
|
||||
] as RowId[]
|
||||
).map((id) => [id, t(`rows.${id}`)])
|
||||
).map((id) => [id, t(`rows.${id}`)]),
|
||||
) as Record<RowId, string>,
|
||||
[t]
|
||||
)
|
||||
const [cardsData, setCardsData] = useState<Partial<Record<CardId, Record<string, unknown>>>>({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
[t],
|
||||
);
|
||||
|
||||
const disabledSet = new Set(config.disabledCardIds)
|
||||
const [cardsData, setCardsData] = useState<
|
||||
Partial<Record<CardId, Record<string, unknown>>>
|
||||
>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const disabledSet = new Set(config.disabledCardIds);
|
||||
|
||||
const hasVisibleCard = useCallback(
|
||||
(rowId: string) => {
|
||||
const cards = ROW_CARDS[rowId as RowId]
|
||||
if (!Array.isArray(cards)) return false
|
||||
return cards.some(cardId => !disabledSet.has(cardId))
|
||||
const cards = ROW_CARDS[rowId as RowId];
|
||||
if (!Array.isArray(cards)) return false;
|
||||
return cards.some((cardId) => !disabledSet.has(cardId));
|
||||
},
|
||||
[config.disabledCardIds]
|
||||
)
|
||||
[config.disabledCardIds],
|
||||
);
|
||||
|
||||
const visibleRowOrder = config.rowOrder.filter(hasVisibleCard)
|
||||
const visibleRowOrder = config.rowOrder.filter(hasVisibleCard);
|
||||
|
||||
const [containerRef, orderedRows, setOrderedRows] = useDragAndDrop(
|
||||
visibleRowOrder,
|
||||
{
|
||||
plugins: [animations()],
|
||||
dragHandle: ".row-drag-handle",
|
||||
},
|
||||
);
|
||||
|
||||
// useEffect(()=>{
|
||||
// console.log("ksjf",visibleRowOrder,orderedRows)
|
||||
|
||||
// },[visibleRowOrder,visibleRowOrder])
|
||||
|
||||
const [containerRef, orderedRows, setOrderedRows] = useDragAndDrop(visibleRowOrder, {
|
||||
plugins: [animations()],
|
||||
dragHandle: '.row-drag-handle'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([farmDashboardService.getConfig(), farmDashboardService.getAllCards()])
|
||||
Promise.all([
|
||||
farmDashboardService.getConfig(),
|
||||
farmDashboardService.getAllCards(),
|
||||
])
|
||||
.then(([configData, cards]) => {
|
||||
const validRowOrder = (configData.rowOrder ?? []).filter(
|
||||
(id): id is RowId => id in ROW_CARDS
|
||||
)
|
||||
(id): id is RowId => id in ROW_CARDS,
|
||||
);
|
||||
const merged: FarmDashboardConfig = {
|
||||
disabledCardIds: configData.disabledCardIds ?? [],
|
||||
rowOrder: validRowOrder.length ? validRowOrder : [...ROW_IDS],
|
||||
enableDragReorder: configData.enableDragReorder ?? true
|
||||
}
|
||||
setConfig(merged)
|
||||
setCardsData(cards ?? {})
|
||||
enableDragReorder: configData.enableDragReorder ?? true,
|
||||
};
|
||||
setConfig(merged);
|
||||
setCardsData(cards ?? {});
|
||||
})
|
||||
.catch(() => setConfig(DEFAULT_FARM_DASHBOARD_CONFIG))
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setOrderedRows(visibleRowOrder)
|
||||
setOrderedRows(visibleRowOrder);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [config.disabledCardIds])
|
||||
|
||||
}, [visibleRowOrder]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return
|
||||
if (JSON.stringify(orderedRows) === JSON.stringify(visibleRowOrder)) return
|
||||
const newRowOrder = mergeRowOrderAfterDrag(config.rowOrder, orderedRows, visibleRowOrder)
|
||||
setConfig(prev => ({ ...prev, rowOrder: newRowOrder }))
|
||||
setSaving(true)
|
||||
if (loading) return;
|
||||
if (JSON.stringify(orderedRows) === JSON.stringify(visibleRowOrder)) return;
|
||||
const newRowOrder = mergeRowOrderAfterDrag(
|
||||
config.rowOrder,
|
||||
orderedRows,
|
||||
visibleRowOrder,
|
||||
);
|
||||
setConfig((prev) => ({ ...prev, rowOrder: newRowOrder }));
|
||||
setSaving(true);
|
||||
farmDashboardService
|
||||
.updateConfig({ rowOrder: newRowOrder })
|
||||
.then(updated => setConfig(updated))
|
||||
.then((updated) => setConfig(updated))
|
||||
.catch(() => {})
|
||||
.finally(() => setSaving(false))
|
||||
.finally(() => setSaving(false));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [orderedRows])
|
||||
}, [orderedRows]);
|
||||
|
||||
const handleToggleDragReorder = useCallback((enabled: boolean) => {
|
||||
setConfig(prev => ({ ...prev, enableDragReorder: enabled }))
|
||||
setSaving(true)
|
||||
setConfig((prev) => ({ ...prev, enableDragReorder: enabled }));
|
||||
setSaving(true);
|
||||
farmDashboardService
|
||||
.updateConfig({ enableDragReorder: enabled })
|
||||
.then(updated => setConfig(updated))
|
||||
.finally(() => setSaving(false))
|
||||
}, [])
|
||||
.then((updated) => setConfig(updated))
|
||||
.finally(() => setSaving(false));
|
||||
}, []);
|
||||
|
||||
const handleToggleCard = useCallback(
|
||||
(cardId: CardId, disabled: boolean) => {
|
||||
const next = disabled
|
||||
? [...config.disabledCardIds, cardId]
|
||||
: config.disabledCardIds.filter(id => id !== cardId)
|
||||
setConfig(prev => ({ ...prev, disabledCardIds: next }))
|
||||
setSaving(true)
|
||||
: config.disabledCardIds.filter((id) => id !== cardId);
|
||||
setConfig((prev) => ({ ...prev, disabledCardIds: next }));
|
||||
setSaving(true);
|
||||
farmDashboardService
|
||||
.updateConfig({ disabledCardIds: next })
|
||||
.then(updated => setConfig(updated))
|
||||
.catch(() => setConfig(prev => ({ ...prev, disabledCardIds: next })))
|
||||
.finally(() => setSaving(false))
|
||||
.then((updated) => setConfig(updated))
|
||||
.catch(() => setConfig((prev) => ({ ...prev, disabledCardIds: next })))
|
||||
.finally(() => setSaving(false));
|
||||
},
|
||||
[config.disabledCardIds]
|
||||
)
|
||||
[config.disabledCardIds],
|
||||
);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setSlotContent(
|
||||
@@ -233,77 +257,99 @@ const FarmDashboardWrapper = () => {
|
||||
rowLabels={rowLabels}
|
||||
rowCards={ROW_CARDS}
|
||||
saving={saving}
|
||||
/>
|
||||
)
|
||||
return () => setSlotContent(null)
|
||||
}, [setSlotContent, config.disabledCardIds, config.enableDragReorder, handleToggleCard, handleToggleDragReorder, saving])
|
||||
/>,
|
||||
);
|
||||
return () => setSlotContent(null);
|
||||
}, [
|
||||
setSlotContent,
|
||||
config.disabledCardIds,
|
||||
config.enableDragReorder,
|
||||
handleToggleCard,
|
||||
handleToggleDragReorder,
|
||||
saving,
|
||||
]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box display='flex' justifyContent='center' alignItems='center' minHeight={200}>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
minHeight={200}
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box position='relative'>
|
||||
<Grid container spacing={6} ref={containerRef as RefObject<HTMLDivElement>}>
|
||||
<Box position="relative">
|
||||
<Grid
|
||||
container
|
||||
spacing={6}
|
||||
ref={containerRef as RefObject<HTMLDivElement>}
|
||||
>
|
||||
{orderedRows.map((rowId: string) => {
|
||||
const cards = ROW_CARDS[rowId as RowId].filter(cardId => !disabledSet.has(cardId))
|
||||
if (cards.length === 0) return null
|
||||
const cards = ROW_CARDS[rowId as RowId].filter(
|
||||
(cardId) => !disabledSet.has(cardId),
|
||||
);
|
||||
if (cards.length === 0) return null;
|
||||
|
||||
const isOverviewRow = rowId === 'overviewKpis'
|
||||
const isOverviewRow = rowId === "overviewKpis";
|
||||
|
||||
return (
|
||||
<Grid
|
||||
key={rowId}
|
||||
size={12}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: 2,
|
||||
...(config.enableDragReorder !== false && { '&:hover .row-drag-handle': { opacity: 1 } })
|
||||
...(config.enableDragReorder !== false && {
|
||||
"&:hover .row-drag-handle": { opacity: 1 },
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{config.enableDragReorder !== false && (
|
||||
<IconButton
|
||||
className='row-drag-handle'
|
||||
size='small'
|
||||
className="row-drag-handle"
|
||||
size="small"
|
||||
sx={{
|
||||
opacity: 0.5,
|
||||
cursor: 'grab',
|
||||
cursor: "grab",
|
||||
flexShrink: 0,
|
||||
mt: 1,
|
||||
'&:active': { cursor: 'grabbing' }
|
||||
"&:active": { cursor: "grabbing" },
|
||||
}}
|
||||
aria-label={t('settings.dragRow', { row: rowLabels[rowId as RowId] })}
|
||||
aria-label={t("settings.dragRow", {
|
||||
row: rowLabels[rowId as RowId],
|
||||
})}
|
||||
>
|
||||
<i className='tabler-arrows-move text-textSecondary' />
|
||||
<i className="tabler-arrows-move text-textSecondary" />
|
||||
</IconButton>
|
||||
)}
|
||||
<Grid container spacing={6} sx={{ flex: 1, minWidth: 0 }}>
|
||||
{isOverviewRow && cards.includes('farmOverviewKpis') && (
|
||||
{isOverviewRow && cards.includes("farmOverviewKpis") && (
|
||||
<FarmOverviewKPIs data={cardsData?.farmOverviewKpis} />
|
||||
)}
|
||||
{!isOverviewRow &&
|
||||
cards.map((cardId: CardId) => {
|
||||
const size = CARD_GRID_SIZE[cardId]
|
||||
const Component = CARD_COMPONENTS[cardId]
|
||||
if (!Component) return null
|
||||
const size = CARD_GRID_SIZE[cardId];
|
||||
const Component = CARD_COMPONENTS[cardId];
|
||||
if (!Component) return null;
|
||||
return (
|
||||
<Grid key={cardId} size={size} sx={cardRowSx}>
|
||||
<Component data={cardsData?.[cardId]} />
|
||||
</Grid>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default FarmDashboardWrapper
|
||||
export default FarmDashboardWrapper;
|
||||
|
||||
@@ -25,6 +25,7 @@ const FarmOverviewKPIs = ({ data }: FarmOverviewKPIsProps) => {
|
||||
const kpis = (data?.kpis as KpiItem[] | undefined) ?? []
|
||||
if (kpis.length === 0) return null
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{kpis.map((kpi) => (
|
||||
|
||||
Reference in New Issue
Block a user