diff --git a/farm_dashboard.json b/farm_dashboard.json new file mode 100644 index 0000000..6556e80 --- /dev/null +++ b/farm_dashboard.json @@ -0,0 +1 @@ +{"info":{"name":"Farm Dashboard","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json","description":"Farm Dashboard API. GET/PATCH config (disabled_card_ids, row_order, enable_drag_reorder). GET all cards. Static mock data only. No database."},"item":[{"name":"Get config","request":{"method":"GET","header":[{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/api/farm-dashboard-config/","description":"Get dashboard config: disabled_card_ids, row_order, enable_drag_reorder."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"code\": 200,\n \"msg\": \"OK\",\n \"data\": {\n \"disabled_card_ids\": [\"farmWeatherCard\", \"sensorRadarChart\"],\n \"row_order\": [\"overviewKpis\", \"weatherAlerts\", \"sensorMonitoring\", \"sensorCharts\", \"alertsWater\", \"predictions\", \"soilHeatmap\", \"ndviRecommendations\", \"economic\"],\n \"enable_drag_reorder\": true\n }\n}"}]},{"name":"Patch config (disable card)","request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n \"disabled_card_ids\": [\"farmWeatherCard\", \"sensorRadarChart\"]\n}"},"url":"{{baseUrl}}/api/farm-dashboard-config/","description":"PATCH to update disabled_card_ids. Input is ignored; returns static config."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"code\": 200,\n \"msg\": \"OK\",\n \"data\": {\n \"disabled_card_ids\": [\"farmWeatherCard\", \"sensorRadarChart\"],\n \"row_order\": [\"overviewKpis\", \"weatherAlerts\", \"sensorMonitoring\", \"sensorCharts\", \"alertsWater\", \"predictions\", \"soilHeatmap\", \"ndviRecommendations\", \"economic\"],\n \"enable_drag_reorder\": true\n }\n}"}]},{"name":"Patch config (row order)","request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n \"row_order\": [\"overviewKpis\", \"weatherAlerts\", \"sensorMonitoring\", \"predictions\", \"sensorCharts\", \"alertsWater\", \"soilHeatmap\", \"ndviRecommendations\", \"economic\"]\n}"},"url":"{{baseUrl}}/api/farm-dashboard-config/","description":"PATCH to update row_order. Input is ignored; returns static config."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"code\": 200,\n \"msg\": \"OK\",\n \"data\": {\n \"disabled_card_ids\": [\"farmWeatherCard\", \"sensorRadarChart\"],\n \"row_order\": [\"overviewKpis\", \"weatherAlerts\", \"sensorMonitoring\", \"sensorCharts\", \"alertsWater\", \"predictions\", \"soilHeatmap\", \"ndviRecommendations\", \"economic\"],\n \"enable_drag_reorder\": true\n }\n}"}]},{"name":"Patch config (enable drag reorder)","request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n \"enable_drag_reorder\": false\n}"},"url":"{{baseUrl}}/api/farm-dashboard-config/","description":"PATCH to update enable_drag_reorder. Input is ignored; returns static config."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"code\": 200,\n \"msg\": \"OK\",\n \"data\": {\n \"disabled_card_ids\": [\"farmWeatherCard\", \"sensorRadarChart\"],\n \"row_order\": [\"overviewKpis\", \"weatherAlerts\", \"sensorMonitoring\", \"sensorCharts\", \"alertsWater\", \"predictions\", \"soilHeatmap\", \"ndviRecommendations\", \"economic\"],\n \"enable_drag_reorder\": true\n }\n}"}]},{"name":"Get all cards","request":{"method":"GET","header":[{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/api/farm-dashboard/","description":"Get unified response with all 15 card payloads."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"code\": 200,\n \"msg\": \"OK\",\n \"data\": {\n \"farmOverviewKpis\": {},\n \"farmWeatherCard\": {},\n \"farmAlertsTracker\": {},\n \"sensorValuesList\": {},\n \"sensorRadarChart\": {},\n \"sensorComparisonChart\": {},\n \"anomalyDetectionCard\": {},\n \"farmAlertsTimeline\": {},\n \"waterNeedPrediction\": {},\n \"harvestPredictionCard\": {},\n \"yieldPredictionChart\": {},\n \"soilMoistureHeatmap\": {},\n \"ndviHealthCard\": {},\n \"recommendationsList\": {},\n \"economicOverview\": {}\n }\n}"}]},{"name":"Get all cards (cards path)","request":{"method":"GET","header":[{"key":"Content-Type","value":"application/json"}],"url":"{{baseUrl}}/api/farm-dashboard/cards/","description":"Get unified response with all 15 card payloads. Same as base path."},"response":[{"name":"Success","status":"OK","code":200,"body":"{\n \"code\": 200,\n \"msg\": \"OK\",\n \"data\": {\n \"farmOverviewKpis\": {},\n \"farmWeatherCard\": {},\n \"farmAlertsTracker\": {},\n \"sensorValuesList\": {},\n \"sensorRadarChart\": {},\n \"sensorComparisonChart\": {},\n \"anomalyDetectionCard\": {},\n \"farmAlertsTimeline\": {},\n \"waterNeedPrediction\": {},\n \"harvestPredictionCard\": {},\n \"yieldPredictionChart\": {},\n \"soilMoistureHeatmap\": {},\n \"ndviHealthCard\": {},\n \"recommendationsList\": {},\n \"economicOverview\": {}\n }\n}"}]}],"variable":[{"key":"baseUrl","value":"http://localhost:8000"}]} diff --git a/src/libs/api/services/farmDashboardService.ts b/src/libs/api/services/farmDashboardService.ts index f63b65d..4949b89 100644 --- a/src/libs/api/services/farmDashboardService.ts +++ b/src/libs/api/services/farmDashboardService.ts @@ -1,11 +1,13 @@ /** - * Farm Dashboard Config Service - * Handles API calls for dashboard customization (disabled cards, row order). - * Authenticated user required. + * Farm Dashboard Service + * Handles API calls for dashboard config and card data. + * - Config: disabled cards, row order, drag reorder + * - Cards: all 15 card payloads from /api/farm-dashboard/ */ import { apiClient } from '../client' import type { FarmDashboardConfig } from '@/views/dashboards/farm/farmDashboardConfig' +import type { CardId } from '@/views/dashboards/farm/farmDashboardConfig' export interface ApiResponse { code: number @@ -19,6 +21,25 @@ export interface FarmDashboardConfigResponse { enable_drag_reorder?: boolean } +/** API response shape for /api/farm-dashboard/ - each key matches CardId */ +export interface FarmDashboardCardsResponse { + farmOverviewKpis?: Record + farmWeatherCard?: Record + farmAlertsTracker?: Record + sensorValuesList?: Record + sensorRadarChart?: Record + sensorComparisonChart?: Record + anomalyDetectionCard?: Record + farmAlertsTimeline?: Record + waterNeedPrediction?: Record + harvestPredictionCard?: Record + yieldPredictionChart?: Record + soilMoistureHeatmap?: Record + ndviHealthCard?: Record + recommendationsList?: Record + economicOverview?: Record +} + const STORAGE_KEY = 'farm_dashboard_config' /** @@ -114,5 +135,22 @@ export const farmDashboardService = { } throw err } + }, + + /** + * Get all dashboard card data from API + * Response: { code: 200, msg: "OK", data: { farmOverviewKpis, farmWeatherCard, ... } } + */ + async getAllCards(): Promise>>> { + try { + const response = await apiClient.get>('/api/farm-dashboard/') + const raw = response?.data ?? response + if (raw && typeof raw === 'object') { + return raw as Partial>> + } + return {} + } catch { + return {} + } } } diff --git a/src/views/dashboards/farm/AnomalyDetectionCard.tsx b/src/views/dashboards/farm/AnomalyDetectionCard.tsx index 51bdae3..396526b 100644 --- a/src/views/dashboards/farm/AnomalyDetectionCard.tsx +++ b/src/views/dashboards/farm/AnomalyDetectionCard.tsx @@ -18,12 +18,13 @@ type AnomalyItem = { severity: 'warning' | 'error' } -const anomalies: AnomalyItem[] = [ - { sensor: 'Soil Moisture Z3', value: '38%', expected: '45-65%', deviation: '-12%', severity: 'warning' }, - { sensor: 'pH Sector 2', value: '5.2', expected: '6.0-7.0', deviation: '-0.8', severity: 'error' } -] +interface AnomalyDetectionCardProps { + data?: Record +} + +const AnomalyDetectionCard = ({ data }: AnomalyDetectionCardProps) => { + const anomalies = (data?.anomalies as AnomalyItem[] | undefined) ?? [] -const AnomalyDetectionCard = () => { return ( import('@/libs/styles/AppReactApexCharts')) -// Vars - Cost breakdown (stacked bar style) -const series = [ - { name: 'Water Cost', data: [120, 115, 110, 125, 118, 122] }, - { name: 'Fertilizer', data: [80, 85, 90, 75, 82, 78] } -] - type EconomicItem = { title: string value: string @@ -36,14 +30,14 @@ type EconomicItem = { avatarColor: 'primary' | 'success' | 'info' | 'warning' } -const economicData: EconomicItem[] = [ - { title: 'Water Cost', value: '€720', subtitle: 'This month', avatarIcon: 'tabler-droplet', avatarColor: 'primary' }, - { title: 'AI Water Savings', value: '€156', subtitle: '18% saved', avatarIcon: 'tabler-bulb', avatarColor: 'success' }, - { title: 'Platform ROI', value: '127%', subtitle: 'vs last year', avatarIcon: 'tabler-chart-line', avatarColor: 'info' }, - { title: 'Income Forecast', value: '€42k', subtitle: 'This season', avatarIcon: 'tabler-currency-euro', avatarColor: 'success' } -] +interface EconomicOverviewProps { + data?: Record +} -const EconomicOverview = () => { +const EconomicOverview = ({ data }: EconomicOverviewProps) => { + const economicData = (data?.economicData as EconomicItem[] | undefined) ?? [] + const chartSeries = (data?.chartSeries as Array<{ name: string; data: number[] }>) ?? [] + const chartCategories = (data?.chartCategories as string[]) ?? ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'] const theme = useTheme() const options: ApexOptions = { @@ -69,7 +63,7 @@ const EconomicOverview = () => { padding: { top: -40, left: -10, right: 0, bottom: -15 } }, xaxis: { - categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], + categories: chartCategories, labels: { show: false }, axisTicks: { show: false }, axisBorder: { show: false } @@ -85,27 +79,31 @@ const EconomicOverview = () => { action={} /> - - {economicData.map((item, index) => ( - -
- - - -
- {item.value} - - {item.title} - - - {item.subtitle} - + {economicData.length > 0 && ( + + {economicData.map((item, index) => ( + +
+ + + +
+ {item.value} + + {item.title} + + + {item.subtitle} + +
-
- - ))} - - + + ))} + + )} + {chartSeries.length > 0 && ( + + )} ) diff --git a/src/views/dashboards/farm/FarmAlertsTimeline.tsx b/src/views/dashboards/farm/FarmAlertsTimeline.tsx index 3f1938c..3c4ef45 100644 --- a/src/views/dashboards/farm/FarmAlertsTimeline.tsx +++ b/src/views/dashboards/farm/FarmAlertsTimeline.tsx @@ -34,36 +34,13 @@ type AlertItem = { color: 'primary' | 'warning' | 'error' | 'info' | 'success' } -const alerts: AlertItem[] = [ - { - title: 'Water Shortage Risk', - description: - 'Soil moisture at 10cm depth (42%) is below optimal. AI predicts stress in 2-3 days if no irrigation. Recommended: irrigate within 24h.', - time: '15 min ago', - color: 'warning' - }, - { - title: 'Fungal Disease Risk', - description: - 'High humidity (65%) + temp 24°C creates favorable conditions for fungal growth. Consider preventive fungicide or reduce irrigation.', - time: '1 hour ago', - color: 'error' - }, - { - title: 'Irrigation Suggestion', - description: 'Optimal watering window: 6:00-8:00 AM. Suggested amount: 450 m³ for Zone A. Expected efficiency gain: 12%.', - time: '2 hours ago', - color: 'info' - }, - { - title: 'Soil Salinity Check', - description: 'EC reading 1.2 dS/m is within range. No action needed. Next check recommended in 5 days.', - time: '4 hours ago', - color: 'success' - } -] +interface FarmAlertsTimelineProps { + data?: Record +} + +const FarmAlertsTimeline = ({ data }: FarmAlertsTimelineProps) => { + const alerts = (data?.alerts as AlertItem[] | undefined) ?? [] -const FarmAlertsTimeline = () => { return ( import('@/libs/styles/AppReactApexChart type AlertStatType = { title: string - subtitle: string + count: string avatarIcon: string avatarColor?: ThemeColor } -const data: AlertStatType[] = [ - { title: 'Water Shortage', subtitle: '2', avatarColor: 'error', avatarIcon: 'tabler-droplet-half-2' }, - { title: 'Fungal Risk', subtitle: '1', avatarColor: 'warning', avatarIcon: 'tabler-mushroom' }, - { title: 'Frost Alert', subtitle: '0', avatarColor: 'info', avatarIcon: 'tabler-snowflake' } -] +interface FarmAlertsTrackerProps { + data?: Record +} -const FarmAlertsTracker = () => { +const FarmAlertsTracker = ({ data }: FarmAlertsTrackerProps) => { + const alertStats = (data?.alertStats as AlertStatType[] | undefined) ?? [] + const totalAlerts = (data?.totalAlerts as number | undefined) ?? 0 + const radialBarValue = (data?.radialBarValue as number | undefined) ?? 30 const theme = useTheme() const disabledText = 'var(--mui-palette-text-disabled)' @@ -77,7 +78,7 @@ const FarmAlertsTracker = () => { value: { offsetY: 8, fontWeight: 500, - formatter: () => '3', + formatter: () => String(totalAlerts), color: 'var(--mui-palette-text-primary)', fontFamily: theme.typography.fontFamily, fontSize: theme.typography.h2.fontSize as string @@ -100,11 +101,11 @@ const FarmAlertsTracker = () => {
- 3 + {totalAlerts} Total Alerts
- {data.map((item, index) => ( + {alertStats.map((item, index) => (
@@ -113,13 +114,19 @@ const FarmAlertsTracker = () => { {item.title} - {item.subtitle} + {item.count}
))}
- +
) diff --git a/src/views/dashboards/farm/FarmDashboardWrapper.tsx b/src/views/dashboards/farm/FarmDashboardWrapper.tsx index cfbd9ec..d99edca 100644 --- a/src/views/dashboards/farm/FarmDashboardWrapper.tsx +++ b/src/views/dashboards/farm/FarmDashboardWrapper.tsx @@ -91,13 +91,18 @@ function mergeRowOrderAfterDrag( const FarmDashboardWrapper = () => { const { setSlotContent } = useContext(NavbarSlotContext) const [config, setConfig] = useState(DEFAULT_FARM_DASHBOARD_CONFIG) + const [cardsData, setCardsData] = useState>>>({}) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const disabledSet = new Set(config.disabledCardIds) const hasVisibleCard = useCallback( - (rowId: RowId) => ROW_CARDS[rowId].some(cardId => !disabledSet.has(cardId)), + (rowId: string) => { + const cards = ROW_CARDS[rowId as RowId] + if (!Array.isArray(cards)) return false + return cards.some(cardId => !disabledSet.has(cardId)) + }, [config.disabledCardIds] ) @@ -109,15 +114,18 @@ const FarmDashboardWrapper = () => { }) useEffect(() => { - farmDashboardService - .getConfig() - .then(data => { + Promise.all([farmDashboardService.getConfig(), farmDashboardService.getAllCards()]) + .then(([configData, cards]) => { + const validRowOrder = (configData.rowOrder ?? []).filter( + (id): id is RowId => id in ROW_CARDS + ) const merged: FarmDashboardConfig = { - disabledCardIds: data.disabledCardIds ?? [], - rowOrder: data.rowOrder?.length ? data.rowOrder : [...ROW_IDS], - enableDragReorder: data.enableDragReorder ?? true + disabledCardIds: configData.disabledCardIds ?? [], + rowOrder: validRowOrder.length ? validRowOrder : [...ROW_IDS], + enableDragReorder: configData.enableDragReorder ?? true } setConfig(merged) + setCardsData(cards ?? {}) }) .catch(() => setConfig(DEFAULT_FARM_DASHBOARD_CONFIG)) .finally(() => setLoading(false)) @@ -229,7 +237,9 @@ const FarmDashboardWrapper = () => { )} - {isOverviewRow && cards.includes('farmOverviewKpis') && } + {isOverviewRow && cards.includes('farmOverviewKpis') && ( + + )} {!isOverviewRow && cards.map((cardId: CardId) => { const size = CARD_GRID_SIZE[cardId] @@ -237,7 +247,7 @@ const FarmDashboardWrapper = () => { if (!Component) return null return ( - + ) })} diff --git a/src/views/dashboards/farm/FarmOverviewKPIs.tsx b/src/views/dashboards/farm/FarmOverviewKPIs.tsx index 927a418..5b01e76 100644 --- a/src/views/dashboards/farm/FarmOverviewKPIs.tsx +++ b/src/views/dashboards/farm/FarmOverviewKPIs.tsx @@ -6,93 +6,43 @@ import Grid from '@mui/material/Grid2' // Component Imports import CardStatsVertical from '@components/card-statistics/Vertical' -const FarmOverviewKPIs = () => { +type KpiItem = { + id: string + title: string + subtitle: string + stats: string + avatarColor?: string + avatarIcon?: string + chipText?: string + chipColor?: string +} + +interface FarmOverviewKPIsProps { + data?: Record +} + +const FarmOverviewKPIs = ({ data }: FarmOverviewKPIsProps) => { + const kpis = (data?.kpis as KpiItem[] | undefined) ?? [] + if (kpis.length === 0) return null + return ( <> - - - - - - - - - - - - - - - - - - + {kpis.map((kpi) => ( + + + + ))} ) } diff --git a/src/views/dashboards/farm/FarmWeatherCard.tsx b/src/views/dashboards/farm/FarmWeatherCard.tsx index 1f7e5b4..9cde823 100644 --- a/src/views/dashboards/farm/FarmWeatherCard.tsx +++ b/src/views/dashboards/farm/FarmWeatherCard.tsx @@ -19,10 +19,20 @@ import OptionMenu from '@core/components/option-menu' // Styled Component Imports const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) -// Vars - Mock weather data (temp variation through day) -const series = [{ data: [18, 22, 26, 28, 25, 20, 18] }] +interface FarmWeatherCardProps { + data?: Record +} -const FarmWeatherCard = () => { +const FarmWeatherCard = ({ data }: FarmWeatherCardProps) => { + const temperature = data?.temperature ?? 24 + const condition = (data?.condition as string) ?? '' + const humidity = data?.humidity ?? 45 + const windSpeed = data?.windSpeed ?? 12 + const windUnit = (data?.windUnit as string) ?? 'km/h' + const unit = (data?.unit as string) ?? '°C' + const chartData = data?.chartData as { labels?: string[]; series?: number[][] } | undefined + const seriesData = chartData?.series?.[0] ?? [18, 22, 26, 28, 25, 20, 18] + const series = [{ data: seriesData }] const theme = useTheme() const infoColor = theme.palette.info.main @@ -77,16 +87,19 @@ const FarmWeatherCard = () => { } />
- 24°C + + {temperature} + {unit} + - Humid: 45% | Wind: 12 km/h + Humid: {humidity}% | Wind: {windSpeed} {windUnit}
diff --git a/src/views/dashboards/farm/HarvestPredictionCard.tsx b/src/views/dashboards/farm/HarvestPredictionCard.tsx index 7f940a5..a0326c3 100644 --- a/src/views/dashboards/farm/HarvestPredictionCard.tsx +++ b/src/views/dashboards/farm/HarvestPredictionCard.tsx @@ -11,7 +11,16 @@ import Chip from '@mui/material/Chip' import CustomAvatar from '@core/components/mui/Avatar' import OptionMenu from '@core/components/option-menu' -const HarvestPredictionCard = () => { +interface HarvestPredictionCardProps { + data?: Record +} + +const HarvestPredictionCard = ({ data }: HarvestPredictionCardProps) => { + const harvestDate = (data?.dateFormatted as string) ?? '' + const daysUntil = (data?.daysUntil as number | undefined) ?? 0 + const daysLeftFormatted = daysUntil > 0 ? `${daysUntil} days` : '' + const description = (data?.description as string) ?? '' + return ( { />
- Oct 15, 2025 - + {harvestDate} + {daysLeftFormatted && ( + + )}
- - Based on current GDD accumulation and weather forecast. Optimal harvest window: Oct 12-18. - + {description && ( + + {description} + + )}
) diff --git a/src/views/dashboards/farm/NDVIHealthCard.tsx b/src/views/dashboards/farm/NDVIHealthCard.tsx index 5b78fa3..70d3c84 100644 --- a/src/views/dashboards/farm/NDVIHealthCard.tsx +++ b/src/views/dashboards/farm/NDVIHealthCard.tsx @@ -21,7 +21,14 @@ import CustomAvatar from '@core/components/mui/Avatar' // Styled Component Imports const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) -const NDVIHealthCard = () => { +interface NDVIHealthCardProps { + data?: Record +} + +const NDVIHealthCard = ({ data }: NDVIHealthCardProps) => { + const ndviIndex = (data?.ndviIndex as number | undefined) ?? 0 + const healthData = + (data?.healthData as Array<{ title: string; value: string; color: string; icon: string }>) ?? [] const theme = useTheme() const successColor = theme.palette.success.main const disabledText = 'var(--mui-palette-text-disabled)' @@ -72,10 +79,7 @@ const NDVIHealthCard = () => { } } - const healthData = [ - { title: 'Nitrogen Stress', value: 'Low', color: 'success', icon: 'tabler-leaf' }, - { title: 'Crop Health', value: 'Good', color: 'success', icon: 'tabler-plant' } - ] + const ndviPercent = (typeof ndviIndex === 'number' ? ndviIndex : parseFloat(String(ndviIndex)) || 0) * 100 return ( @@ -87,23 +91,31 @@ const NDVIHealthCard = () => { />
- 0.78 + {ndviIndex} NDVI Index (0-1) -
- {healthData.map((item, index) => ( -
- - - -
- {item.title} - + {healthData.length > 0 && ( +
+ {healthData.map((item, index) => ( +
+ + + +
+ {item.title} + +
-
- ))} -
+ ))} +
+ )}
- + ) diff --git a/src/views/dashboards/farm/RecommendationsList.tsx b/src/views/dashboards/farm/RecommendationsList.tsx index e2921cd..70dd431 100644 --- a/src/views/dashboards/farm/RecommendationsList.tsx +++ b/src/views/dashboards/farm/RecommendationsList.tsx @@ -20,34 +20,14 @@ type RecommendationType = { avatarColor: 'primary' | 'info' | 'success' | 'warning' | 'error' } -const data: RecommendationType[] = [ - { - title: 'Irrigation: 6:00-8:00 AM', - subtitle: '450 m³ for Zone A. Without irrigation, yield may drop ~8%.', - avatarIcon: 'tabler-droplet', - avatarColor: 'primary' - }, - { - title: 'Fertilizer: NPK 20-20-20', - subtitle: 'Apply 25 kg/ha in 7 days. Current N deficiency in sector 2.', - avatarIcon: 'tabler-leaf', - avatarColor: 'success' - }, - { - title: 'Fungicide: Preventive', - subtitle: 'Humidity + temp favor fungi. Consider copper-based spray.', - avatarIcon: 'tabler-mushroom', - avatarColor: 'warning' - }, - { - title: 'Harvest Window: Oct 12-18', - subtitle: 'Peak ripeness expected Oct 15. Plan labor accordingly.', - avatarIcon: 'tabler-calendar-event', - avatarColor: 'info' - } -] +interface RecommendationsListProps { + data?: Record +} + +const RecommendationsList = ({ data }: RecommendationsListProps) => { + const recommendations = (data?.recommendations as RecommendationType[] | undefined) ?? [] + if (recommendations.length === 0) return null -const RecommendationsList = () => { return ( { action={} /> - {data.map((item, index) => ( + {recommendations.map((item, index) => (
diff --git a/src/views/dashboards/farm/SensorComparisonChart.tsx b/src/views/dashboards/farm/SensorComparisonChart.tsx index 18024d8..1e73ad7 100644 --- a/src/views/dashboards/farm/SensorComparisonChart.tsx +++ b/src/views/dashboards/farm/SensorComparisonChart.tsx @@ -16,14 +16,17 @@ import type { ApexOptions } from 'apexcharts' // Styled Component Imports const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) -// Vars - Soil moisture: today vs last week (7 days) -const series = [ - { name: 'Today', data: [42, 45, 48, 52, 50, 48, 46] }, - { name: 'Last Week', data: [38, 40, 42, 45, 43, 40, 38] } -] +interface SensorComparisonChartProps { + data?: Record +} -const SensorComparisonChart = () => { +const SensorComparisonChart = ({ data }: SensorComparisonChartProps) => { + const series = (data?.series as Array<{ name: string; data: number[] }>) ?? [] + const categories = (data?.categories as string[]) ?? ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + const currentValue = data?.currentValue ?? 48 + const vsLastWeek = (data?.vsLastWeek as string) ?? '+5% vs last week' const theme = useTheme() + if (series.length === 0) return null const options: ApexOptions = { chart: { @@ -46,7 +49,7 @@ const SensorComparisonChart = () => { yaxis: { lines: { show: true } } }, xaxis: { - categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + categories, labels: { style: { colors: 'var(--mui-palette-text-disabled)' } }, @@ -77,9 +80,9 @@ const SensorComparisonChart = () => { />
- 48% + {currentValue}% - +5% vs last week + {vsLastWeek}
diff --git a/src/views/dashboards/farm/SensorRadarChart.tsx b/src/views/dashboards/farm/SensorRadarChart.tsx index eb561e1..35bb222 100644 --- a/src/views/dashboards/farm/SensorRadarChart.tsx +++ b/src/views/dashboards/farm/SensorRadarChart.tsx @@ -18,18 +18,17 @@ import OptionMenu from '@core/components/option-menu' // Styled Component Imports const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) -// Vars - Today vs ideal ranges (normalized 0-100) -const series = [ - { name: 'Today', data: [75, 65, 80, 70, 85, 60] }, - { name: 'Ideal', data: [80, 70, 75, 75, 90, 50] } -] +interface SensorRadarChartProps { + data?: Record +} -const labels = ['Temp', 'Humidity', 'pH', 'EC', 'Light', 'Wind'] - -const SensorRadarChart = () => { +const SensorRadarChart = ({ data }: SensorRadarChartProps) => { + const series = (data?.series as Array<{ name: string; data: number[] }>) ?? [] + const labels = (data?.labels as string[]) ?? [] const theme = useTheme() const textDisabled = 'var(--mui-palette-text-disabled)' const divider = 'var(--mui-palette-divider)' + if (series.length === 0) return null const options: ApexOptions = { chart: { @@ -61,7 +60,7 @@ const SensorRadarChart = () => { show: true, style: { fontSize: '13px', - colors: Array(6).fill(textDisabled) + colors: Array(labels.length || 6).fill(textDisabled) } } }, diff --git a/src/views/dashboards/farm/SensorValuesList.tsx b/src/views/dashboards/farm/SensorValuesList.tsx index c55ec0f..f7b9e5f 100644 --- a/src/views/dashboards/farm/SensorValuesList.tsx +++ b/src/views/dashboards/farm/SensorValuesList.tsx @@ -20,18 +20,14 @@ type SensorDataType = { unit: string } -const data: SensorDataType[] = [ - { title: '28°C', subtitle: 'Air Temperature', trendNumber: 2.1, unit: '°C' }, - { title: '24°C', subtitle: 'Soil Temperature', trendNumber: -0.5, trend: 'negative', unit: '°C' }, - { title: '65%', subtitle: 'Air Humidity', trendNumber: 3.2, unit: '%' }, - { title: '42%', subtitle: 'Soil Moisture (10cm)', trendNumber: -1.8, trend: 'negative', unit: '%' }, - { title: '6.8', subtitle: 'Soil pH', trendNumber: 0.2, unit: 'pH' }, - { title: '1.2', subtitle: 'EC (dS/m)', trendNumber: 0.1, unit: 'dS/m' }, - { title: '850', subtitle: 'Light Intensity (lux)', trendNumber: 15.3, unit: 'lux' }, - { title: '12', subtitle: 'Wind Speed (km/h)', trendNumber: -2.4, trend: 'negative', unit: 'km/h' } -] +interface SensorValuesListProps { + data?: Record +} + +const SensorValuesList = ({ data }: SensorValuesListProps) => { + const sensors = (data?.sensors as SensorDataType[] | undefined) ?? [] + if (sensors.length === 0) return null -const SensorValuesList = () => { return ( { action={} /> - {data.map((item, index) => ( + {sensors.map((item, index) => (
diff --git a/src/views/dashboards/farm/SoilMoistureHeatmap.tsx b/src/views/dashboards/farm/SoilMoistureHeatmap.tsx index 66b3ed8..bde22af 100644 --- a/src/views/dashboards/farm/SoilMoistureHeatmap.tsx +++ b/src/views/dashboards/farm/SoilMoistureHeatmap.tsx @@ -15,20 +15,24 @@ import type { ApexOptions } from 'apexcharts' // Styled Component Imports const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) -// Generate soil moisture data: rows = field zones (Z1-Z7), cols = hours (6am-6pm) -const zones = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'Z7'] -const hours = ['6h', '8h', '10h', '12h', '14h', '16h', '18h'] +interface HeatmapDataPoint { + x: string + y: number +} -const series = zones.map((zone, i) => ({ - name: zone, - data: hours.map((h, j) => ({ - x: h, - y: Math.floor(Math.random() * 40) + 35 - })) -})) +interface HeatmapSeries { + name: string + data: HeatmapDataPoint[] +} -const SoilMoistureHeatmap = () => { +interface SoilMoistureHeatmapProps { + data?: Record +} + +const SoilMoistureHeatmap = ({ data }: SoilMoistureHeatmapProps) => { + const series = (data?.series as HeatmapSeries[]) ?? [] const theme = useTheme() + if (series.length === 0) return null const options: ApexOptions = { chart: { diff --git a/src/views/dashboards/farm/WaterNeedPrediction.tsx b/src/views/dashboards/farm/WaterNeedPrediction.tsx index 6186438..42ba317 100644 --- a/src/views/dashboards/farm/WaterNeedPrediction.tsx +++ b/src/views/dashboards/farm/WaterNeedPrediction.tsx @@ -19,12 +19,19 @@ import OptionMenu from '@core/components/option-menu' // Styled Component Imports const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) -// Vars - 7-day water need prediction (m³) -const series = [{ name: 'Water Need', data: [420, 450, 480, 460, 490, 510, 480] }] +interface WaterNeedPredictionProps { + data?: Record +} -const WaterNeedPrediction = () => { +const WaterNeedPrediction = ({ data }: WaterNeedPredictionProps) => { + const series = (data?.series as Array<{ name: string; data: number[] }>) ?? [] + const categories = (data?.categories as string[]) ?? ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7'] + const totalNext7Days = data?.totalNext7Days ?? 0 + const unit = (data?.unit as string) ?? 'm³' + const totalFormatted = typeof totalNext7Days === 'number' ? totalNext7Days.toLocaleString() : String(totalNext7Days) const theme = useTheme() const primaryColor = theme.palette.primary.main + if (series.length === 0) return null const options: ApexOptions = { chart: { @@ -48,7 +55,7 @@ const WaterNeedPrediction = () => { xaxis: { lines: { show: false } } }, xaxis: { - categories: ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7'], + categories, labels: { style: { colors: 'var(--mui-palette-text-disabled)' } }, axisBorder: { show: false }, axisTicks: { show: false } @@ -56,11 +63,11 @@ const WaterNeedPrediction = () => { yaxis: { labels: { style: { colors: 'var(--mui-palette-text-disabled)' }, - formatter: (val: number) => `${val} m³` + formatter: (val: number) => `${val} ${unit}` } }, tooltip: { - y: { formatter: (val: number) => `${val} m³` } + y: { formatter: (val: number) => `${val} ${unit}` } } } @@ -73,7 +80,9 @@ const WaterNeedPrediction = () => { />
- 3,290 m³ + + {totalFormatted} {unit} + Total next 7 days diff --git a/src/views/dashboards/farm/YieldPredictionChart.tsx b/src/views/dashboards/farm/YieldPredictionChart.tsx index b2e39ce..6c23b59 100644 --- a/src/views/dashboards/farm/YieldPredictionChart.tsx +++ b/src/views/dashboards/farm/YieldPredictionChart.tsx @@ -21,14 +21,26 @@ import CustomAvatar from '@core/components/mui/Avatar' // Styled Component Imports const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) -// Vars - Yield comparison: this year vs last year (tons per month) -const series = [ - { name: 'This Year', data: [35, 38, 40, 42, 45, 48, 50, 48, 46, 44, 42, 42] }, - { name: 'Last Year', data: [32, 34, 36, 38, 40, 42, 44, 42, 40, 38, 36, 38] } -] +type SummaryItem = { + title: string + subtitle: string + amount: string + avatarColor: string + avatarIcon: string +} -const YieldPredictionChart = () => { +interface YieldPredictionChartProps { + data?: Record +} + +const YieldPredictionChart = ({ data }: YieldPredictionChartProps) => { + const series = (data?.series as Array<{ name: string; data: number[] }>) ?? [] + const categories = + (data?.categories as string[]) ?? + ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + const summary = (data?.summary as SummaryItem[]) ?? [] const theme = useTheme() + if (series.length === 0) return null const options: ApexOptions = { chart: { @@ -48,7 +60,7 @@ const YieldPredictionChart = () => { strokeDashArray: 4 }, xaxis: { - categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + categories, labels: { style: { colors: 'var(--mui-palette-text-disabled)' } } }, yaxis: { @@ -62,11 +74,6 @@ const YieldPredictionChart = () => { } } - const summaryData = [ - { title: 'Predicted Yield', subtitle: 'This Season', amount: '42 ton', avatarColor: 'primary', avatarIcon: 'tabler-chart-bar' }, - { title: 'Harvest Date', subtitle: 'Est. Oct 15', amount: '+8%', avatarColor: 'success', avatarIcon: 'tabler-calendar' } - ] - return ( { /> -
- {summaryData.map((item, index) => ( -
- - - -
-
- - {item.title} + {summary.length > 0 && ( +
+ {summary.map((item, index) => ( +
+ + + +
+
+ + {item.title} + + {item.subtitle} +
+ + {item.amount} - {item.subtitle}
- - {item.amount} -
-
- ))} -
+ ))} +
+ )} )