diff --git a/src/app/(dashboard)/(private)/dashboards/farm/page.tsx b/src/app/(dashboard)/(private)/dashboards/farm/page.tsx
new file mode 100644
index 0000000..deaf1f6
--- /dev/null
+++ b/src/app/(dashboard)/(private)/dashboards/farm/page.tsx
@@ -0,0 +1,8 @@
+// Components Imports
+import FarmDashboardWrapper from '@views/dashboards/farm/FarmDashboardWrapper'
+
+const DashboardFarm = async () => {
+ return
+}
+
+export default DashboardFarm
diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx
index 0ef2ef1..e5ac8bd 100644
--- a/src/components/Providers.tsx
+++ b/src/components/Providers.tsx
@@ -3,6 +3,7 @@ import type { ChildrenType, Direction } from '@core/types'
// Context Imports
import { AuthProvider } from '@/contexts/authProvider'
+import { NavbarSlotProvider } from '@/contexts/navbarSlotContext'
import { VerticalNavProvider } from '@menu/contexts/verticalNavContext'
import { SettingsProvider } from '@core/contexts/settingsContext'
import ThemeProvider from '@components/theme'
@@ -29,6 +30,7 @@ const Providers = async (props: Props) => {
return (
+
@@ -37,6 +39,7 @@ const Providers = async (props: Props) => {
+
)
}
diff --git a/src/components/layout/vertical/NavbarContent.tsx b/src/components/layout/vertical/NavbarContent.tsx
index 3844404..209fc87 100644
--- a/src/components/layout/vertical/NavbarContent.tsx
+++ b/src/components/layout/vertical/NavbarContent.tsx
@@ -1,5 +1,11 @@
+'use client'
+
// Third-party Imports
import classnames from 'classnames'
+import { useContext } from 'react'
+
+// Context Imports
+import NavbarSlotContext from '@/contexts/navbarSlotContext'
// Type Imports
import type { ShortcutsType } from '@components/layout/shared/ShortcutsDropdown'
@@ -106,11 +112,14 @@ const notifications: NotificationsType[] = [
]
const NavbarContent = () => {
+ const { slotContent } = useContext(NavbarSlotContext)
+
return (
+ {slotContent}
diff --git a/src/contexts/navbarSlotContext.tsx b/src/contexts/navbarSlotContext.tsx
new file mode 100644
index 0000000..7159de5
--- /dev/null
+++ b/src/contexts/navbarSlotContext.tsx
@@ -0,0 +1,35 @@
+'use client'
+
+// React Imports
+import { createContext, useCallback, useMemo, useState } from 'react'
+
+// Type Imports
+import type { ChildrenType } from '@core/types'
+import type { ReactNode } from 'react'
+
+export type NavbarSlotContextProps = {
+ slotContent: ReactNode
+ setSlotContent: (content: ReactNode) => void
+}
+
+const NavbarSlotContext = createContext({} as NavbarSlotContextProps)
+
+export const NavbarSlotProvider = ({ children }: ChildrenType) => {
+ const [slotContent, setSlotContentState] = useState
(null)
+
+ const setSlotContent = useCallback((content: ReactNode) => {
+ setSlotContentState(content)
+ }, [])
+
+ const value = useMemo(
+ () => ({
+ slotContent,
+ setSlotContent
+ }),
+ [slotContent, setSlotContent]
+ )
+
+ return {children}
+}
+
+export default NavbarSlotContext
diff --git a/src/data/navigation/horizontalMenuData.tsx b/src/data/navigation/horizontalMenuData.tsx
index 9c81761..c50a8ab 100644
--- a/src/data/navigation/horizontalMenuData.tsx
+++ b/src/data/navigation/horizontalMenuData.tsx
@@ -28,6 +28,11 @@ const horizontalMenuData = (): HorizontalMenuDataType[] => [
label: 'logistics',
icon: 'tabler-truck',
href: '/dashboards/logistics'
+ },
+ {
+ label: 'farm',
+ icon: 'tabler-plant-2',
+ href: '/dashboards/farm'
}
]
},
diff --git a/src/data/navigation/verticalMenuData.tsx b/src/data/navigation/verticalMenuData.tsx
index 22f6bb3..64981ff 100644
--- a/src/data/navigation/verticalMenuData.tsx
+++ b/src/data/navigation/verticalMenuData.tsx
@@ -32,6 +32,11 @@ const verticalMenuData = (): VerticalMenuDataType[] => [
label: 'logistics',
icon: 'tabler-circle',
href: '/dashboards/logistics'
+ },
+ {
+ label: 'farm',
+ icon: 'tabler-plant-2',
+ href: '/dashboards/farm'
}
]
},
diff --git a/src/libs/api/index.ts b/src/libs/api/index.ts
index c96bba1..47e1364 100644
--- a/src/libs/api/index.ts
+++ b/src/libs/api/index.ts
@@ -15,4 +15,5 @@ export * from './services/todoService'
export * from './services/userManagementService'
export * from './services/rolesPermissionsService'
export * from './services/sensorHubService'
+export * from './services/farmDashboardService'
diff --git a/src/libs/api/services/farmDashboardService.ts b/src/libs/api/services/farmDashboardService.ts
new file mode 100644
index 0000000..f63b65d
--- /dev/null
+++ b/src/libs/api/services/farmDashboardService.ts
@@ -0,0 +1,118 @@
+/**
+ * Farm Dashboard Config Service
+ * Handles API calls for dashboard customization (disabled cards, row order).
+ * Authenticated user required.
+ */
+
+import { apiClient } from '../client'
+import type { FarmDashboardConfig } from '@/views/dashboards/farm/farmDashboardConfig'
+
+export interface ApiResponse {
+ code: number
+ msg: string
+ data: T
+}
+
+export interface FarmDashboardConfigResponse {
+ disabled_card_ids: string[]
+ row_order: string[]
+ enable_drag_reorder?: boolean
+}
+
+const STORAGE_KEY = 'farm_dashboard_config'
+
+/**
+ * Transform API response to frontend config format
+ */
+function fromApiResponse(data: FarmDashboardConfigResponse): FarmDashboardConfig {
+ return {
+ disabledCardIds: data.disabled_card_ids ?? [],
+ rowOrder: data.row_order ?? [],
+ enableDragReorder: data.enable_drag_reorder ?? true
+ }
+}
+
+/**
+ * Transform frontend config to API request format
+ */
+function toApiRequest(config: Partial): Partial {
+ const req: Partial = {}
+ if (config.disabledCardIds !== undefined) req.disabled_card_ids = config.disabledCardIds
+ if (config.rowOrder !== undefined) req.row_order = config.rowOrder
+ if (config.enableDragReorder !== undefined) req.enable_drag_reorder = config.enableDragReorder
+ return req
+}
+
+/**
+ * localStorage fallback when backend is not ready
+ */
+function getLocalConfig(): FarmDashboardConfig | null {
+ if (typeof window === 'undefined') return null
+ try {
+ const stored = localStorage.getItem(STORAGE_KEY)
+ return stored ? (JSON.parse(stored) as FarmDashboardConfig) : null
+ } catch {
+ return null
+ }
+}
+
+function setLocalConfig(config: FarmDashboardConfig): void {
+ if (typeof window === 'undefined') return
+ try {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(config))
+ } catch (e) {
+ console.error('Failed to save farm dashboard config to localStorage', e)
+ }
+}
+
+export const farmDashboardService = {
+ /**
+ * Get farm dashboard config for current user
+ */
+ async getConfig(): Promise {
+ try {
+ const response = await apiClient.get<
+ ApiResponse | FarmDashboardConfigResponse
+ >('/api/farm-dashboard-config')
+ const raw = response && 'data' in response ? response.data : response
+ if (raw && typeof raw === 'object' && ('disabled_card_ids' in raw || 'row_order' in raw)) {
+ return fromApiResponse(raw as FarmDashboardConfigResponse)
+ }
+ throw new Error('Invalid response')
+ } catch {
+ const local = getLocalConfig()
+ if (local) return local
+ return { disabledCardIds: [], rowOrder: [], enableDragReorder: true }
+ }
+ },
+
+ /**
+ * Update farm dashboard config
+ */
+ async updateConfig(data: Partial): Promise {
+ try {
+ const response = await apiClient.patch<
+ ApiResponse | FarmDashboardConfigResponse
+ >('/api/farm-dashboard-config', toApiRequest(data))
+ const raw = response && 'data' in response ? response.data : response
+ if (raw && typeof raw === 'object' && ('disabled_card_ids' in raw || 'row_order' in raw)) {
+ const config = fromApiResponse(raw as FarmDashboardConfigResponse)
+ setLocalConfig(config)
+ return config
+ }
+ throw new Error('Update failed')
+ } catch (err) {
+ const local = getLocalConfig()
+ if (local) {
+ const merged: FarmDashboardConfig = {
+ disabledCardIds: data.disabledCardIds ?? local.disabledCardIds,
+ rowOrder: data.rowOrder ?? local.rowOrder,
+ enableDragReorder: data.enableDragReorder ?? local.enableDragReorder ?? true
+ }
+ setLocalConfig(merged)
+ return merged
+ }
+ throw err
+ }
+ }
+}
diff --git a/src/views/dashboards/farm/AnomalyDetectionCard.tsx b/src/views/dashboards/farm/AnomalyDetectionCard.tsx
new file mode 100644
index 0000000..51bdae3
--- /dev/null
+++ b/src/views/dashboards/farm/AnomalyDetectionCard.tsx
@@ -0,0 +1,66 @@
+'use client'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+import Chip from '@mui/material/Chip'
+
+// Component Imports
+import OptionMenu from '@core/components/option-menu'
+
+type AnomalyItem = {
+ sensor: string
+ value: string
+ expected: string
+ deviation: string
+ 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' }
+]
+
+const AnomalyDetectionCard = () => {
+ return (
+
+ }
+ title='Anomaly Detection'
+ subheader='Out of range values'
+ action={}
+ sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }}
+ />
+
+ {anomalies.length === 0 ? (
+
+ No anomalies detected. All sensors within optimal range.
+
+ ) : (
+ anomalies.map((item, index) => (
+
+
+
+ {item.sensor}
+
+
+
+
+ Value: {item.value} (Expected: {item.expected}) · Deviation: {item.deviation}
+
+
+ ))
+ )}
+
+
+ )
+}
+
+export default AnomalyDetectionCard
diff --git a/src/views/dashboards/farm/EconomicOverview.tsx b/src/views/dashboards/farm/EconomicOverview.tsx
new file mode 100644
index 0000000..7df9717
--- /dev/null
+++ b/src/views/dashboards/farm/EconomicOverview.tsx
@@ -0,0 +1,114 @@
+'use client'
+
+// Next Imports
+import dynamic from 'next/dynamic'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+import Grid from '@mui/material/Grid2'
+import { useTheme } from '@mui/material/styles'
+
+// Third-party Imports
+import classnames from 'classnames'
+import type { ApexOptions } from 'apexcharts'
+
+// Component Imports
+import OptionMenu from '@core/components/option-menu'
+import CustomAvatar from '@core/components/mui/Avatar'
+
+// Styled Component Imports
+const AppReactApexCharts = dynamic(() => 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
+ subtitle: string
+ avatarIcon: string
+ 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' }
+]
+
+const EconomicOverview = () => {
+ const theme = useTheme()
+
+ const options: ApexOptions = {
+ chart: {
+ stacked: true,
+ parentHeightOffset: 0,
+ toolbar: { show: false }
+ },
+ legend: { position: 'top', labels: { colors: 'var(--mui-palette-text-secondary)' } },
+ dataLabels: { enabled: false },
+ stroke: { width: 5, colors: ['var(--mui-palette-background-paper)'] },
+ colors: ['var(--mui-palette-primary-main)', 'var(--mui-palette-info-main)'],
+ plotOptions: {
+ bar: {
+ borderRadius: 7,
+ columnWidth: '50%',
+ borderRadiusApplication: 'around'
+ }
+ },
+ grid: {
+ borderColor: 'var(--mui-palette-divider)',
+ yaxis: { lines: { show: false } },
+ padding: { top: -40, left: -10, right: 0, bottom: -15 }
+ },
+ xaxis: {
+ categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
+ labels: { show: false },
+ axisTicks: { show: false },
+ axisBorder: { show: false }
+ },
+ yaxis: { labels: { show: false } }
+ }
+
+ return (
+
+ }
+ />
+
+
+ {economicData.map((item, index) => (
+
+
+
+
+
+
+ {item.value}
+
+ {item.title}
+
+
+ {item.subtitle}
+
+
+
+
+ ))}
+
+
+
+
+ )
+}
+
+export default EconomicOverview
diff --git a/src/views/dashboards/farm/FarmAlertsTimeline.tsx b/src/views/dashboards/farm/FarmAlertsTimeline.tsx
new file mode 100644
index 0000000..3f1938c
--- /dev/null
+++ b/src/views/dashboards/farm/FarmAlertsTimeline.tsx
@@ -0,0 +1,104 @@
+'use client'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+import { styled } from '@mui/material/styles'
+import TimelineDot from '@mui/lab/TimelineDot'
+import TimelineItem from '@mui/lab/TimelineItem'
+import TimelineContent from '@mui/lab/TimelineContent'
+import TimelineSeparator from '@mui/lab/TimelineSeparator'
+import TimelineConnector from '@mui/lab/TimelineConnector'
+import MuiTimeline from '@mui/lab/Timeline'
+import type { TimelineProps } from '@mui/lab/Timeline'
+
+// Component Imports
+import OptionMenu from '@core/components/option-menu'
+
+// Styled Timeline
+const Timeline = styled(MuiTimeline)({
+ paddingLeft: 0,
+ paddingRight: 0,
+ '& .MuiTimelineItem-root': {
+ width: '100%',
+ '&:before': { display: 'none' }
+ }
+})
+
+type AlertItem = {
+ title: string
+ description: string
+ time: string
+ 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'
+ }
+]
+
+const FarmAlertsTimeline = () => {
+ return (
+
+ }
+ title='AI Alerts'
+ titleTypographyProps={{ variant: 'h5' }}
+ subheader='Explainable recommendations'
+ action={}
+ sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }}
+ />
+
+
+ {alerts.map((alert, index) => (
+
+
+
+ {index < alerts.length - 1 && }
+
+
+
+
+ {alert.title}
+
+ {alert.time}
+
+
+ {alert.description}
+
+
+
+ ))}
+
+
+
+ )
+}
+
+export default FarmAlertsTimeline
diff --git a/src/views/dashboards/farm/FarmAlertsTracker.tsx b/src/views/dashboards/farm/FarmAlertsTracker.tsx
new file mode 100644
index 0000000..5365ee2
--- /dev/null
+++ b/src/views/dashboards/farm/FarmAlertsTracker.tsx
@@ -0,0 +1,128 @@
+'use client'
+
+// Next Imports
+import dynamic from 'next/dynamic'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+import { useTheme } from '@mui/material/styles'
+
+// Third Party Imports
+import classnames from 'classnames'
+import type { ApexOptions } from 'apexcharts'
+
+// Types Imports
+import type { ThemeColor } from '@core/types'
+
+// Components Imports
+import OptionMenu from '@core/components/option-menu'
+import CustomAvatar from '@core/components/mui/Avatar'
+
+// Styled Component Imports
+const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts'))
+
+type AlertStatType = {
+ title: string
+ subtitle: 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' }
+]
+
+const FarmAlertsTracker = () => {
+ const theme = useTheme()
+ const disabledText = 'var(--mui-palette-text-disabled)'
+
+ const options: ApexOptions = {
+ stroke: { dashArray: 10 },
+ labels: ['Active Alerts'],
+ colors: ['var(--mui-palette-warning-main)'],
+ states: {
+ hover: { filter: { type: 'none' } },
+ active: { filter: { type: 'none' } }
+ },
+ fill: {
+ type: 'gradient',
+ gradient: {
+ shade: 'dark',
+ opacityTo: 0.5,
+ opacityFrom: 1,
+ shadeIntensity: 0.5,
+ stops: [30, 70, 100],
+ inverseColors: false,
+ gradientToColors: ['var(--mui-palette-warning-main)']
+ }
+ },
+ plotOptions: {
+ radialBar: {
+ endAngle: 130,
+ startAngle: -140,
+ hollow: { size: '60%' },
+ track: { background: 'transparent' },
+ dataLabels: {
+ name: {
+ offsetY: -24,
+ color: disabledText,
+ fontFamily: theme.typography.fontFamily,
+ fontSize: theme.typography.body2.fontSize as string
+ },
+ value: {
+ offsetY: 8,
+ fontWeight: 500,
+ formatter: () => '3',
+ color: 'var(--mui-palette-text-primary)',
+ fontFamily: theme.typography.fontFamily,
+ fontSize: theme.typography.h2.fontSize as string
+ }
+ }
+ }
+ },
+ grid: {
+ padding: { top: -18, left: 0, right: 0, bottom: 14 }
+ }
+ }
+
+ return (
+
+ }
+ />
+
+
+
+ 3
+ Total Alerts
+
+
+ {data.map((item, index) => (
+
+
+
+
+
+
+ {item.title}
+
+ {item.subtitle}
+
+
+ ))}
+
+
+
+
+
+ )
+}
+
+export default FarmAlertsTracker
diff --git a/src/views/dashboards/farm/FarmDashboardSettingsDrawer.tsx b/src/views/dashboards/farm/FarmDashboardSettingsDrawer.tsx
new file mode 100644
index 0000000..77b4931
--- /dev/null
+++ b/src/views/dashboards/farm/FarmDashboardSettingsDrawer.tsx
@@ -0,0 +1,99 @@
+'use client'
+
+// MUI Imports
+import Drawer from '@mui/material/Drawer'
+import Typography from '@mui/material/Typography'
+import Checkbox from '@mui/material/Checkbox'
+import FormControlLabel from '@mui/material/FormControlLabel'
+import List from '@mui/material/List'
+import ListItem from '@mui/material/ListItem'
+import ListItemText from '@mui/material/ListItemText'
+import Divider from '@mui/material/Divider'
+import Box from '@mui/material/Box'
+
+// Component Imports
+import PerfectScrollbar from 'react-perfect-scrollbar'
+
+// Config
+import type { RowId, CardId } from '@views/dashboards/farm/farmDashboardConfig'
+import { ROW_IDS } from '@views/dashboards/farm/farmDashboardConfig'
+
+type RowCardsType = Record
+type RowLabelsType = Record
+type CardLabelsType = Record
+
+type FarmDashboardSettingsDrawerProps = {
+ open: boolean
+ onClose: () => void
+ disabledCardIds: string[]
+ onToggleCard: (cardId: CardId, disabled: boolean) => void
+ cardLabels: CardLabelsType
+ rowLabels: RowLabelsType
+ rowCards: RowCardsType
+}
+
+const FarmDashboardSettingsDrawer = (props: FarmDashboardSettingsDrawerProps) => {
+ const { open, onClose, disabledCardIds, onToggleCard, cardLabels, rowLabels, rowCards } = props
+ const disabledSet = new Set(disabledCardIds)
+
+ return (
+
+
+
+ Dashboard Settings
+
+ Toggle cards to show or hide on the dashboard
+
+
+
+
+
+ {ROW_IDS.map(rowId => (
+
+
+ {rowLabels[rowId]}
+
+ {rowCards[rowId].map(cardId => {
+ const isDisabled = disabledSet.has(cardId)
+ return (
+
+ onToggleCard(cardId, !e.target.checked)}
+ size='small'
+ />
+ }
+ label={}
+ sx={{ m: 0, width: '100%' }}
+ />
+
+ )
+ })}
+
+
+ ))}
+
+
+
+
+ )
+}
+
+export default FarmDashboardSettingsDrawer
diff --git a/src/views/dashboards/farm/FarmDashboardSettingsDropdown.tsx b/src/views/dashboards/farm/FarmDashboardSettingsDropdown.tsx
new file mode 100644
index 0000000..a46b5a9
--- /dev/null
+++ b/src/views/dashboards/farm/FarmDashboardSettingsDropdown.tsx
@@ -0,0 +1,152 @@
+'use client'
+
+// React Imports
+import { useRef, useState } from 'react'
+
+// MUI Imports
+import IconButton from '@mui/material/IconButton'
+import Popper from '@mui/material/Popper'
+import Fade from '@mui/material/Fade'
+import Paper from '@mui/material/Paper'
+import ClickAwayListener from '@mui/material/ClickAwayListener'
+import Typography from '@mui/material/Typography'
+import Checkbox from '@mui/material/Checkbox'
+import FormControlLabel from '@mui/material/FormControlLabel'
+import Switch from '@mui/material/Switch'
+import Divider from '@mui/material/Divider'
+import List from '@mui/material/List'
+import ListItem from '@mui/material/ListItem'
+import ListItemText from '@mui/material/ListItemText'
+import Box from '@mui/material/Box'
+import CircularProgress from '@mui/material/CircularProgress'
+
+// Hook Imports
+import { useSettings } from '@core/hooks/useSettings'
+
+// Config
+import type { RowId, CardId } from '@views/dashboards/farm/farmDashboardConfig'
+import { ROW_IDS } from '@views/dashboards/farm/farmDashboardConfig'
+
+type RowCardsType = Record
+type RowLabelsType = Record
+type CardLabelsType = Record
+
+type FarmDashboardSettingsDropdownProps = {
+ disabledCardIds: string[]
+ onToggleCard: (cardId: CardId, disabled: boolean) => void
+ enableDragReorder: boolean
+ onToggleDragReorder: (enabled: boolean) => void
+ cardLabels: CardLabelsType
+ rowLabels: RowLabelsType
+ rowCards: RowCardsType
+ saving?: boolean
+}
+
+const FarmDashboardSettingsDropdown = (props: FarmDashboardSettingsDropdownProps) => {
+ const { disabledCardIds, onToggleCard, enableDragReorder, onToggleDragReorder, cardLabels, rowLabels, rowCards, saving } = props
+ const { settings } = useSettings()
+ const [open, setOpen] = useState(false)
+ const anchorRef = useRef(null)
+ const disabledSet = new Set(disabledCardIds)
+
+ const handleClose = () => setOpen(false)
+ const handleToggle = () => setOpen(prev => !prev)
+
+ return (
+ <>
+
+ {saving && }
+
+
+
+
+
+ {({ TransitionProps, placement }) => (
+
+
+
+
+
+ Dashboard Settings
+
+ Toggle cards to show or hide
+
+
+
+ onToggleDragReorder(e.target.checked)}
+ size='small'
+ />
+ }
+ label={
+ Enable drag & reorder rows
+ }
+ sx={{ m: 0 }}
+ />
+
+
+
+ {ROW_IDS.map(rowId => (
+
+
+ {rowLabels[rowId]}
+
+ {rowCards[rowId].map(cardId => {
+ const isDisabled = disabledSet.has(cardId)
+ return (
+
+ onToggleCard(cardId, !e.target.checked)}
+ size='small'
+ />
+ }
+ label={
+
+ }
+ sx={{ m: 0, width: '100%' }}
+ />
+
+ )
+ })}
+
+ ))}
+
+
+
+
+
+ )}
+
+ >
+ )
+}
+
+export default FarmDashboardSettingsDropdown
diff --git a/src/views/dashboards/farm/FarmDashboardWrapper.tsx b/src/views/dashboards/farm/FarmDashboardWrapper.tsx
new file mode 100644
index 0000000..cfbd9ec
--- /dev/null
+++ b/src/views/dashboards/farm/FarmDashboardWrapper.tsx
@@ -0,0 +1,253 @@
+'use client'
+
+// React Imports
+import type { RefObject } from 'react'
+import { useEffect, useState, useCallback, useContext } from 'react'
+
+// Context Imports
+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'
+
+// Third-party imports
+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'
+
+// Config & Service
+import {
+ ROW_IDS,
+ ROW_CARDS,
+ ROW_LABELS,
+ CARD_GRID_SIZE,
+ CARD_LABELS,
+ 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'
+
+const cardRowSx = {
+ display: 'flex',
+ flexDirection: 'column',
+ '& > *': { flex: 1, minHeight: 0 }
+}
+
+const CARD_COMPONENTS: Record = {
+ farmOverviewKpis: FarmOverviewKPIs,
+ farmWeatherCard: FarmWeatherCard,
+ farmAlertsTracker: FarmAlertsTracker,
+ sensorValuesList: SensorValuesList,
+ sensorRadarChart: SensorRadarChart,
+ sensorComparisonChart: SensorComparisonChart,
+ anomalyDetectionCard: AnomalyDetectionCard,
+ farmAlertsTimeline: FarmAlertsTimeline,
+ waterNeedPrediction: WaterNeedPrediction,
+ harvestPredictionCard: HarvestPredictionCard,
+ yieldPredictionChart: YieldPredictionChart,
+ soilMoistureHeatmap: SoilMoistureHeatmap,
+ ndviHealthCard: NDVIHealthCard,
+ recommendationsList: RecommendationsList,
+ economicOverview: EconomicOverview
+}
+
+function mergeRowOrderAfterDrag(
+ currentRowOrder: string[],
+ newVisibleOrder: string[],
+ visibleRows: string[]
+): string[] {
+ const result = [...currentRowOrder]
+ let visibleIndex = 0
+ for (let i = 0; i < result.length; i++) {
+ if (visibleRows.includes(result[i])) {
+ result[i] = newVisibleOrder[visibleIndex++]
+ }
+ }
+ return result
+}
+
+const FarmDashboardWrapper = () => {
+ const { setSlotContent } = useContext(NavbarSlotContext)
+ const [config, setConfig] = useState(DEFAULT_FARM_DASHBOARD_CONFIG)
+ 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)),
+ [config.disabledCardIds]
+ )
+
+ const visibleRowOrder = config.rowOrder.filter(hasVisibleCard)
+
+ const [containerRef, orderedRows, setOrderedRows] = useDragAndDrop(visibleRowOrder, {
+ plugins: [animations()],
+ dragHandle: '.row-drag-handle'
+ })
+
+ useEffect(() => {
+ farmDashboardService
+ .getConfig()
+ .then(data => {
+ const merged: FarmDashboardConfig = {
+ disabledCardIds: data.disabledCardIds ?? [],
+ rowOrder: data.rowOrder?.length ? data.rowOrder : [...ROW_IDS],
+ enableDragReorder: data.enableDragReorder ?? true
+ }
+ setConfig(merged)
+ })
+ .catch(() => setConfig(DEFAULT_FARM_DASHBOARD_CONFIG))
+ .finally(() => setLoading(false))
+ }, [])
+
+ useEffect(() => {
+ setOrderedRows(visibleRowOrder)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [config.disabledCardIds])
+
+
+ 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)
+ farmDashboardService
+ .updateConfig({ rowOrder: newRowOrder })
+ .then(updated => setConfig(updated))
+ .catch(() => {})
+ .finally(() => setSaving(false))
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [orderedRows])
+
+ const handleToggleDragReorder = useCallback((enabled: boolean) => {
+ setConfig(prev => ({ ...prev, enableDragReorder: enabled }))
+ setSaving(true)
+ farmDashboardService
+ .updateConfig({ enableDragReorder: enabled })
+ .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)
+ farmDashboardService
+ .updateConfig({ disabledCardIds: next })
+ .then(updated => setConfig(updated))
+ .catch(() => setConfig(prev => ({ ...prev, disabledCardIds: next })))
+ .finally(() => setSaving(false))
+ },
+ [config.disabledCardIds]
+ )
+
+ useEffect(() => {
+ setSlotContent(
+
+ )
+ return () => setSlotContent(null)
+ }, [setSlotContent, config.disabledCardIds, config.enableDragReorder, handleToggleCard, handleToggleDragReorder, saving])
+
+ if (loading) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+ }>
+ {orderedRows.map((rowId: string) => {
+ const cards = ROW_CARDS[rowId as RowId].filter(cardId => !disabledSet.has(cardId))
+ if (cards.length === 0) return null
+
+ const isOverviewRow = rowId === 'overviewKpis'
+
+ return (
+
+ {config.enableDragReorder !== false && (
+
+
+
+ )}
+
+ {isOverviewRow && cards.includes('farmOverviewKpis') && }
+ {!isOverviewRow &&
+ cards.map((cardId: CardId) => {
+ const size = CARD_GRID_SIZE[cardId]
+ const Component = CARD_COMPONENTS[cardId]
+ if (!Component) return null
+ return (
+
+
+
+ )
+ })}
+
+
+ )
+ })}
+
+
+ )
+}
+
+export default FarmDashboardWrapper
diff --git a/src/views/dashboards/farm/FarmOverviewKPIs.tsx b/src/views/dashboards/farm/FarmOverviewKPIs.tsx
new file mode 100644
index 0000000..927a418
--- /dev/null
+++ b/src/views/dashboards/farm/FarmOverviewKPIs.tsx
@@ -0,0 +1,100 @@
+'use client'
+
+// MUI Imports
+import Grid from '@mui/material/Grid2'
+
+// Component Imports
+import CardStatsVertical from '@components/card-statistics/Vertical'
+
+const FarmOverviewKPIs = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default FarmOverviewKPIs
diff --git a/src/views/dashboards/farm/FarmWeatherCard.tsx b/src/views/dashboards/farm/FarmWeatherCard.tsx
new file mode 100644
index 0000000..1f7e5b4
--- /dev/null
+++ b/src/views/dashboards/farm/FarmWeatherCard.tsx
@@ -0,0 +1,98 @@
+'use client'
+
+// Next Imports
+import dynamic from 'next/dynamic'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import Typography from '@mui/material/Typography'
+import CardContent from '@mui/material/CardContent'
+import CardHeader from '@mui/material/CardHeader'
+import { useTheme } from '@mui/material/styles'
+
+// Third-party Imports
+import type { ApexOptions } from 'apexcharts'
+
+// Component Imports
+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] }]
+
+const FarmWeatherCard = () => {
+ const theme = useTheme()
+ const infoColor = theme.palette.info.main
+
+ const options: ApexOptions = {
+ chart: {
+ parentHeightOffset: 0,
+ toolbar: { show: false },
+ sparkline: { enabled: true }
+ },
+ tooltip: { enabled: false },
+ dataLabels: { enabled: false },
+ stroke: {
+ width: 2,
+ curve: 'smooth'
+ },
+ grid: {
+ show: false,
+ padding: { bottom: 20 }
+ },
+ fill: {
+ type: 'gradient',
+ gradient: {
+ opacityTo: 0,
+ opacityFrom: 1,
+ shadeIntensity: 1,
+ stops: [0, 100],
+ colorStops: [
+ [
+ { offset: 0, opacity: 0.4, color: infoColor },
+ { opacity: 0, offset: 100, color: 'var(--mui-palette-background-paper)' }
+ ]
+ ]
+ }
+ },
+ theme: {
+ monochrome: {
+ enabled: true,
+ shadeTo: 'light',
+ shadeIntensity: 1,
+ color: infoColor
+ }
+ },
+ xaxis: {
+ labels: { show: false },
+ axisTicks: { show: false },
+ axisBorder: { show: false }
+ },
+ yaxis: { show: false }
+ }
+
+ return (
+
+ }
+ />
+
+
+
+ 24°C
+
+ Humid: 45% | Wind: 12 km/h
+
+
+
+
+
+ )
+}
+
+export default FarmWeatherCard
diff --git a/src/views/dashboards/farm/HarvestPredictionCard.tsx b/src/views/dashboards/farm/HarvestPredictionCard.tsx
new file mode 100644
index 0000000..7f940a5
--- /dev/null
+++ b/src/views/dashboards/farm/HarvestPredictionCard.tsx
@@ -0,0 +1,40 @@
+'use client'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+import Chip from '@mui/material/Chip'
+
+// Component Imports
+import CustomAvatar from '@core/components/mui/Avatar'
+import OptionMenu from '@core/components/option-menu'
+
+const HarvestPredictionCard = () => {
+ return (
+
+
+
+
+ }
+ title='Harvest Prediction'
+ subheader='AI Estimated Date'
+ action={}
+ />
+
+
+ Oct 15, 2025
+
+
+
+ Based on current GDD accumulation and weather forecast. Optimal harvest window: Oct 12-18.
+
+
+
+ )
+}
+
+export default HarvestPredictionCard
diff --git a/src/views/dashboards/farm/NDVIHealthCard.tsx b/src/views/dashboards/farm/NDVIHealthCard.tsx
new file mode 100644
index 0000000..5b78fa3
--- /dev/null
+++ b/src/views/dashboards/farm/NDVIHealthCard.tsx
@@ -0,0 +1,112 @@
+'use client'
+
+// Next Imports
+import dynamic from 'next/dynamic'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+import Chip from '@mui/material/Chip'
+import { useTheme } from '@mui/material/styles'
+
+// Third-party Imports
+import classnames from 'classnames'
+import type { ApexOptions } from 'apexcharts'
+
+// Component Imports
+import CustomAvatar from '@core/components/mui/Avatar'
+
+// Styled Component Imports
+const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts'))
+
+const NDVIHealthCard = () => {
+ const theme = useTheme()
+ const successColor = theme.palette.success.main
+ const disabledText = 'var(--mui-palette-text-disabled)'
+
+ const options: ApexOptions = {
+ stroke: { dashArray: 10 },
+ labels: ['NDVI'],
+ colors: [successColor],
+ states: {
+ hover: { filter: { type: 'none' } },
+ active: { filter: { type: 'none' } }
+ },
+ fill: {
+ type: 'gradient',
+ gradient: {
+ shade: 'dark',
+ opacityTo: 0.5,
+ opacityFrom: 1,
+ shadeIntensity: 0.5,
+ stops: [30, 70, 100],
+ inverseColors: false,
+ gradientToColors: [successColor]
+ }
+ },
+ plotOptions: {
+ radialBar: {
+ endAngle: 130,
+ startAngle: -140,
+ hollow: { size: '60%' },
+ track: { background: 'transparent' },
+ dataLabels: {
+ name: {
+ offsetY: -24,
+ color: disabledText,
+ fontFamily: theme.typography.fontFamily,
+ fontSize: theme.typography.body2.fontSize as string
+ },
+ value: {
+ offsetY: 8,
+ fontWeight: 500,
+ formatter: (val: number) => val.toFixed(2),
+ color: 'var(--mui-palette-text-primary)',
+ fontFamily: theme.typography.fontFamily,
+ fontSize: theme.typography.h2.fontSize as string
+ }
+ }
+ }
+ }
+ }
+
+ const healthData = [
+ { title: 'Nitrogen Stress', value: 'Low', color: 'success', icon: 'tabler-leaf' },
+ { title: 'Crop Health', value: 'Good', color: 'success', icon: 'tabler-plant' }
+ ]
+
+ return (
+
+ }
+ title='NDVI Health'
+ subheader='Vegetation Index'
+ sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }}
+ />
+
+
+
0.78
+
NDVI Index (0-1)
+
+ {healthData.map((item, index) => (
+
+
+
+
+
+ {item.title}
+
+
+
+ ))}
+
+
+
+
+
+ )
+}
+
+export default NDVIHealthCard
diff --git a/src/views/dashboards/farm/RecommendationsList.tsx b/src/views/dashboards/farm/RecommendationsList.tsx
new file mode 100644
index 0000000..e2921cd
--- /dev/null
+++ b/src/views/dashboards/farm/RecommendationsList.tsx
@@ -0,0 +1,79 @@
+'use client'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+
+// Third-party Imports
+import classnames from 'classnames'
+
+// Component Imports
+import OptionMenu from '@core/components/option-menu'
+import CustomAvatar from '@core/components/mui/Avatar'
+
+type RecommendationType = {
+ title: string
+ subtitle: string
+ avatarIcon: string
+ 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'
+ }
+]
+
+const RecommendationsList = () => {
+ return (
+
+ }
+ />
+
+ {data.map((item, index) => (
+
+
+
+
+
+
+ {item.title}
+
+
+ {item.subtitle}
+
+
+
+ ))}
+
+
+ )
+}
+
+export default RecommendationsList
diff --git a/src/views/dashboards/farm/SensorComparisonChart.tsx b/src/views/dashboards/farm/SensorComparisonChart.tsx
new file mode 100644
index 0000000..18024d8
--- /dev/null
+++ b/src/views/dashboards/farm/SensorComparisonChart.tsx
@@ -0,0 +1,91 @@
+'use client'
+
+// Next Imports
+import dynamic from 'next/dynamic'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+import { useTheme } from '@mui/material/styles'
+
+// Third-party Imports
+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] }
+]
+
+const SensorComparisonChart = () => {
+ const theme = useTheme()
+
+ const options: ApexOptions = {
+ chart: {
+ parentHeightOffset: 0,
+ toolbar: { show: false },
+ zoom: { enabled: false }
+ },
+ colors: ['var(--mui-palette-primary-main)', 'var(--mui-palette-info-main)'],
+ stroke: { width: 2, curve: 'smooth' },
+ legend: {
+ position: 'top',
+ labels: { colors: 'var(--mui-palette-text-secondary)' },
+ markers: { offsetX: theme.direction === 'rtl' ? 7 : -4 }
+ },
+ dataLabels: { enabled: false },
+ grid: {
+ borderColor: 'var(--mui-palette-divider)',
+ strokeDashArray: 4,
+ xaxis: { lines: { show: false } },
+ yaxis: { lines: { show: true } }
+ },
+ xaxis: {
+ categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
+ labels: {
+ style: { colors: 'var(--mui-palette-text-disabled)' }
+ },
+ axisBorder: { show: false },
+ axisTicks: { show: false }
+ },
+ yaxis: {
+ labels: {
+ style: { colors: 'var(--mui-palette-text-disabled)' },
+ formatter: (val: number) => `${val}%`
+ }
+ },
+ fill: {
+ type: 'gradient',
+ gradient: {
+ opacityFrom: 0.4,
+ opacityTo: 0.05,
+ shadeIntensity: 0.5
+ }
+ }
+ }
+
+ return (
+
+
+
+
+ 48%
+
+ +5% vs last week
+
+
+
+
+
+ )
+}
+
+export default SensorComparisonChart
diff --git a/src/views/dashboards/farm/SensorRadarChart.tsx b/src/views/dashboards/farm/SensorRadarChart.tsx
new file mode 100644
index 0000000..eb561e1
--- /dev/null
+++ b/src/views/dashboards/farm/SensorRadarChart.tsx
@@ -0,0 +1,85 @@
+'use client'
+
+// Next Imports
+import dynamic from 'next/dynamic'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import { useTheme } from '@mui/material/styles'
+
+// Third Party Imports
+import type { ApexOptions } from 'apexcharts'
+
+// Component Imports
+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] }
+]
+
+const labels = ['Temp', 'Humidity', 'pH', 'EC', 'Light', 'Wind']
+
+const SensorRadarChart = () => {
+ const theme = useTheme()
+ const textDisabled = 'var(--mui-palette-text-disabled)'
+ const divider = 'var(--mui-palette-divider)'
+
+ const options: ApexOptions = {
+ chart: {
+ parentHeightOffset: 0,
+ toolbar: { show: false }
+ },
+ colors: ['var(--mui-palette-primary-main)', 'var(--mui-palette-success-main)'],
+ plotOptions: {
+ radar: {
+ polygons: {
+ connectorColors: divider,
+ strokeColors: divider
+ }
+ }
+ },
+ stroke: { width: 0 },
+ fill: { opacity: [1, 0.5] },
+ labels,
+ markers: { size: 0 },
+ legend: {
+ fontSize: '13px',
+ labels: { colors: 'var(--mui-palette-text-secondary)' },
+ markers: { offsetY: -1, offsetX: theme.direction === 'rtl' ? 7 : -4 },
+ itemMargin: { horizontal: 9 }
+ },
+ grid: { show: false },
+ xaxis: {
+ labels: {
+ show: true,
+ style: {
+ fontSize: '13px',
+ colors: Array(6).fill(textDisabled)
+ }
+ }
+ },
+ yaxis: { show: false }
+ }
+
+ return (
+
+ }
+ />
+
+
+
+
+ )
+}
+
+export default SensorRadarChart
diff --git a/src/views/dashboards/farm/SensorValuesList.tsx b/src/views/dashboards/farm/SensorValuesList.tsx
new file mode 100644
index 0000000..c55ec0f
--- /dev/null
+++ b/src/views/dashboards/farm/SensorValuesList.tsx
@@ -0,0 +1,72 @@
+'use client'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+
+// Third-party Imports
+import classnames from 'classnames'
+
+// Component Imports
+import OptionMenu from '@core/components/option-menu'
+
+type SensorDataType = {
+ title: string
+ subtitle: string
+ trendNumber: number
+ trend?: 'positive' | 'negative'
+ 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' }
+]
+
+const SensorValuesList = () => {
+ return (
+
+ }
+ />
+
+ {data.map((item, index) => (
+
+
+
+
+ {item.title}
+
+ {item.subtitle}
+
+
+
+ {`${item.trendNumber > 0 ? '+' : ''}${item.trendNumber}%`}
+
+
+
+ ))}
+
+
+ )
+}
+
+export default SensorValuesList
diff --git a/src/views/dashboards/farm/SoilMoistureHeatmap.tsx b/src/views/dashboards/farm/SoilMoistureHeatmap.tsx
new file mode 100644
index 0000000..66b3ed8
--- /dev/null
+++ b/src/views/dashboards/farm/SoilMoistureHeatmap.tsx
@@ -0,0 +1,81 @@
+'use client'
+
+// Next Imports
+import dynamic from 'next/dynamic'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import { useTheme } from '@mui/material/styles'
+
+// Third-party Imports
+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']
+
+const series = zones.map((zone, i) => ({
+ name: zone,
+ data: hours.map((h, j) => ({
+ x: h,
+ y: Math.floor(Math.random() * 40) + 35
+ }))
+}))
+
+const SoilMoistureHeatmap = () => {
+ const theme = useTheme()
+
+ const options: ApexOptions = {
+ chart: {
+ parentHeightOffset: 0,
+ toolbar: { show: false }
+ },
+ dataLabels: { enabled: false },
+ legend: {
+ position: 'bottom',
+ labels: { colors: 'var(--mui-palette-text-secondary)' },
+ markers: { offsetX: theme.direction === 'rtl' ? 7 : -4 }
+ },
+ plotOptions: {
+ heatmap: {
+ enableShades: false,
+ colorScale: {
+ ranges: [
+ { from: 0, to: 30, name: 'Low', color: '#ff6b6b' },
+ { from: 31, to: 50, name: 'Moderate', color: '#ffd93d' },
+ { from: 51, to: 70, name: 'Optimal', color: '#6bcb77' },
+ { from: 71, to: 100, name: 'High', color: '#4d96ff' }
+ ]
+ }
+ }
+ },
+ grid: { padding: { top: -20 } },
+ xaxis: {
+ labels: { show: true, style: { colors: 'var(--mui-palette-text-disabled)', fontSize: '11px' } }
+ },
+ yaxis: {
+ labels: {
+ style: { colors: 'var(--mui-palette-text-disabled)', fontSize: '13px' }
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+
+ )
+}
+
+export default SoilMoistureHeatmap
diff --git a/src/views/dashboards/farm/WaterNeedPrediction.tsx b/src/views/dashboards/farm/WaterNeedPrediction.tsx
new file mode 100644
index 0000000..6186438
--- /dev/null
+++ b/src/views/dashboards/farm/WaterNeedPrediction.tsx
@@ -0,0 +1,87 @@
+'use client'
+
+// Next Imports
+import dynamic from 'next/dynamic'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+import { useTheme } from '@mui/material/styles'
+
+// Third-party Imports
+import type { ApexOptions } from 'apexcharts'
+
+// Component Imports
+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] }]
+
+const WaterNeedPrediction = () => {
+ const theme = useTheme()
+ const primaryColor = theme.palette.primary.main
+
+ const options: ApexOptions = {
+ chart: {
+ parentHeightOffset: 0,
+ toolbar: { show: false }
+ },
+ stroke: { width: 2, curve: 'smooth' },
+ colors: [primaryColor],
+ fill: {
+ type: 'gradient',
+ gradient: {
+ opacityFrom: 0.5,
+ opacityTo: 0.1,
+ shadeIntensity: 0.5
+ }
+ },
+ dataLabels: { enabled: false },
+ grid: {
+ borderColor: 'var(--mui-palette-divider)',
+ strokeDashArray: 4,
+ xaxis: { lines: { show: false } }
+ },
+ xaxis: {
+ categories: ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7'],
+ labels: { style: { colors: 'var(--mui-palette-text-disabled)' } },
+ axisBorder: { show: false },
+ axisTicks: { show: false }
+ },
+ yaxis: {
+ labels: {
+ style: { colors: 'var(--mui-palette-text-disabled)' },
+ formatter: (val: number) => `${val} m³`
+ }
+ },
+ tooltip: {
+ y: { formatter: (val: number) => `${val} m³` }
+ }
+ }
+
+ return (
+
+ }
+ />
+
+
+ 3,290 m³
+
+ Total next 7 days
+
+
+
+
+
+ )
+}
+
+export default WaterNeedPrediction
diff --git a/src/views/dashboards/farm/YieldPredictionChart.tsx b/src/views/dashboards/farm/YieldPredictionChart.tsx
new file mode 100644
index 0000000..b2e39ce
--- /dev/null
+++ b/src/views/dashboards/farm/YieldPredictionChart.tsx
@@ -0,0 +1,104 @@
+'use client'
+
+// Next Imports
+import dynamic from 'next/dynamic'
+
+// MUI Imports
+import Card from '@mui/material/Card'
+import CardHeader from '@mui/material/CardHeader'
+import CardContent from '@mui/material/CardContent'
+import Typography from '@mui/material/Typography'
+import { useTheme } from '@mui/material/styles'
+
+// Third-party Imports
+import classnames from 'classnames'
+import type { ApexOptions } from 'apexcharts'
+
+// Component Imports
+import OptionMenu from '@core/components/option-menu'
+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] }
+]
+
+const YieldPredictionChart = () => {
+ const theme = useTheme()
+
+ const options: ApexOptions = {
+ chart: {
+ stacked: false,
+ parentHeightOffset: 0,
+ toolbar: { show: false }
+ },
+ legend: {
+ position: 'top',
+ labels: { colors: 'var(--mui-palette-text-secondary)' }
+ },
+ stroke: { width: 2, curve: 'smooth' },
+ colors: ['var(--mui-palette-primary-main)', 'var(--mui-palette-success-main)'],
+ dataLabels: { enabled: false },
+ grid: {
+ borderColor: 'var(--mui-palette-divider)',
+ strokeDashArray: 4
+ },
+ xaxis: {
+ categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ labels: { style: { colors: 'var(--mui-palette-text-disabled)' } }
+ },
+ yaxis: {
+ labels: {
+ style: { colors: 'var(--mui-palette-text-disabled)' },
+ formatter: (val: number) => `${val}t`
+ }
+ },
+ tooltip: {
+ y: { formatter: (val: number) => `${val} tons` }
+ }
+ }
+
+ 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}
+
+ {item.subtitle}
+
+
+ {item.amount}
+
+
+
+ ))}
+
+
+
+ )
+}
+
+export default YieldPredictionChart
diff --git a/src/views/dashboards/farm/farmDashboardConfig.ts b/src/views/dashboards/farm/farmDashboardConfig.ts
new file mode 100644
index 0000000..7c45873
--- /dev/null
+++ b/src/views/dashboards/farm/farmDashboardConfig.ts
@@ -0,0 +1,134 @@
+/**
+ * Farm Dashboard - Config constants and type definitions
+ * Row IDs, Card IDs, and mapping for disable/reorder functionality
+ */
+
+export const ROW_IDS = [
+ 'overviewKpis',
+ 'weatherAlerts',
+ 'sensorMonitoring',
+ 'sensorCharts',
+ 'alertsWater',
+ 'predictions',
+ 'soilHeatmap',
+ 'ndviRecommendations',
+ 'economic'
+] as const
+
+export type RowId = (typeof ROW_IDS)[number]
+
+export const CARD_IDS = [
+ 'farmOverviewKpis',
+ 'farmWeatherCard',
+ 'farmAlertsTracker',
+ 'sensorValuesList',
+ 'sensorRadarChart',
+ 'sensorComparisonChart',
+ 'anomalyDetectionCard',
+ 'farmAlertsTimeline',
+ 'waterNeedPrediction',
+ 'harvestPredictionCard',
+ 'yieldPredictionChart',
+ 'soilMoistureHeatmap',
+ 'ndviHealthCard',
+ 'recommendationsList',
+ 'economicOverview'
+] as const
+
+export type CardId = (typeof CARD_IDS)[number]
+
+/** Maps each card to its parent row */
+export const CARD_TO_ROW: Record = {
+ farmOverviewKpis: 'overviewKpis',
+ farmWeatherCard: 'weatherAlerts',
+ farmAlertsTracker: 'weatherAlerts',
+ sensorValuesList: 'sensorMonitoring',
+ sensorRadarChart: 'sensorMonitoring',
+ sensorComparisonChart: 'sensorCharts',
+ anomalyDetectionCard: 'sensorCharts',
+ farmAlertsTimeline: 'alertsWater',
+ waterNeedPrediction: 'alertsWater',
+ harvestPredictionCard: 'predictions',
+ yieldPredictionChart: 'predictions',
+ soilMoistureHeatmap: 'soilHeatmap',
+ ndviHealthCard: 'ndviRecommendations',
+ recommendationsList: 'ndviRecommendations',
+ economicOverview: 'economic'
+}
+
+/** Grid size for each card - matches layout in page.tsx */
+export const CARD_GRID_SIZE: Record = {
+ farmOverviewKpis: { xs: 12, sm: 6, md: 4, lg: 12 }, // Full row of 6 KPIs - parent handles
+ farmWeatherCard: { xs: 12, md: 6, lg: 4 },
+ farmAlertsTracker: { xs: 12, md: 6, lg: 8 },
+ sensorValuesList: { xs: 12, lg: 5 },
+ sensorRadarChart: { xs: 12, lg: 7 },
+ sensorComparisonChart: { xs: 12, lg: 8 },
+ anomalyDetectionCard: { xs: 12, lg: 4 },
+ farmAlertsTimeline: { xs: 12, lg: 4 },
+ waterNeedPrediction: { xs: 12, lg: 8 },
+ harvestPredictionCard: { xs: 12, md: 6, lg: 4 },
+ yieldPredictionChart: { xs: 12 },
+ soilMoistureHeatmap: { xs: 12 },
+ ndviHealthCard: { xs: 12, md: 6, lg: 4 },
+ recommendationsList: { xs: 12, md: 6, lg: 8 },
+ economicOverview: { xs: 12 }
+}
+
+/** Display label for each card (for settings UI) */
+export const CARD_LABELS: Record = {
+ farmOverviewKpis: 'Overview KPIs',
+ farmWeatherCard: 'Weather',
+ farmAlertsTracker: 'Alerts Tracker',
+ sensorValuesList: 'Sensor Values',
+ sensorRadarChart: 'Sensor Radar Chart',
+ sensorComparisonChart: 'Sensor Comparison',
+ anomalyDetectionCard: 'Anomaly Detection',
+ farmAlertsTimeline: 'Alerts Timeline',
+ waterNeedPrediction: 'Water Need Prediction',
+ harvestPredictionCard: 'Harvest Prediction',
+ yieldPredictionChart: 'Yield Prediction',
+ soilMoistureHeatmap: 'Soil Moisture Heatmap',
+ ndviHealthCard: 'NDVI Health',
+ recommendationsList: 'Recommendations',
+ economicOverview: 'Economic Overview'
+}
+
+/** Display label for each row (for drag handle / settings) */
+export const ROW_LABELS: Record = {
+ overviewKpis: 'Overview KPIs',
+ weatherAlerts: 'Weather & Alerts',
+ sensorMonitoring: 'Sensor Monitoring',
+ sensorCharts: 'Sensor Charts',
+ alertsWater: 'Alerts & Water Prediction',
+ predictions: 'Predictions',
+ soilHeatmap: 'Soil Moisture Heatmap',
+ ndviRecommendations: 'NDVI & Recommendations',
+ economic: 'Economic Overview'
+}
+
+/** Cards that belong to each row (for rendering) */
+export const ROW_CARDS: Record = {
+ overviewKpis: ['farmOverviewKpis'],
+ weatherAlerts: ['farmWeatherCard', 'farmAlertsTracker'],
+ sensorMonitoring: ['sensorValuesList', 'sensorRadarChart'],
+ sensorCharts: ['sensorComparisonChart', 'anomalyDetectionCard'],
+ alertsWater: ['farmAlertsTimeline', 'waterNeedPrediction'],
+ predictions: ['harvestPredictionCard', 'yieldPredictionChart'],
+ soilHeatmap: ['soilMoistureHeatmap'],
+ ndviRecommendations: ['ndviHealthCard', 'recommendationsList'],
+ economic: ['economicOverview']
+}
+
+export interface FarmDashboardConfig {
+ disabledCardIds: string[]
+ rowOrder: string[]
+ enableDragReorder?: boolean
+}
+
+/** Default config when no backend data */
+export const DEFAULT_FARM_DASHBOARD_CONFIG: FarmDashboardConfig = {
+ disabledCardIds: [],
+ rowOrder: [...ROW_IDS],
+ enableDragReorder: true
+}