Remove API documentation file and update navigation constants with new entries for farm dashboard, water data, soil data, and data section. Enhance sensor hub functionality by adding new sensor payload structure and integrating plant type and name selection in the sensor form. Refactor calendar components to streamline code and improve maintainability.
This commit is contained in:
@@ -1,90 +1,58 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import { useMediaQuery } from '@mui/material'
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
|
||||
// Third-party Imports
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
// Type Imports
|
||||
import type { CalendarColors, CalendarType } from '@/types/apps/calendarTypes'
|
||||
|
||||
// Redux Imports
|
||||
import { useAppDispatch } from '@/redux-store'
|
||||
|
||||
// Slice Imports
|
||||
import { fetchEvents } from '@/redux-store/slices/calendar'
|
||||
|
||||
// Component Imports
|
||||
import Calendar from './Calendar'
|
||||
import SidebarLeft from './SidebarLeft'
|
||||
import AddEventSidebar from './AddEventSidebar'
|
||||
|
||||
// CalendarColors Object
|
||||
const calendarsColor: CalendarColors = {
|
||||
Personal: 'error',
|
||||
Business: 'primary',
|
||||
Family: 'warning',
|
||||
Holiday: 'success',
|
||||
ETC: 'info'
|
||||
}
|
||||
|
||||
const AppCalendar = () => {
|
||||
// States
|
||||
const [calendarApi, setCalendarApi] = useState<null | any>(null)
|
||||
const [leftSidebarOpen, setLeftSidebarOpen] = useState<boolean>(false)
|
||||
const [addEventSidebarOpen, setAddEventSidebarOpen] = useState<boolean>(false)
|
||||
return <></>
|
||||
// // States
|
||||
// const [calendarApi, setCalendarApi] = useState<null | any>(null)
|
||||
// const [leftSidebarOpen, setLeftSidebarOpen] = useState<boolean>(false)
|
||||
// const [addEventSidebarOpen, setAddEventSidebarOpen] = useState<boolean>(false)
|
||||
|
||||
// Hooks
|
||||
const dispatch = useAppDispatch()
|
||||
const calendarStore = useSelector((state: { calendarReducer: CalendarType & { loading: boolean; error: string | null } }) => state.calendarReducer)
|
||||
const mdAbove = useMediaQuery((theme: Theme) => theme.breakpoints.up('md'))
|
||||
// // Hooks
|
||||
// const dispatch = useAppDispatch()
|
||||
// const calendarStore = useSelector((state: { calendarReducer: CalendarType & { loading: boolean; error: string | null } }) => state.calendarReducer)
|
||||
// const mdAbove = useMediaQuery((theme: Theme) => theme.breakpoints.up('md'))
|
||||
|
||||
// Fetch events on mount
|
||||
useEffect(() => {
|
||||
dispatch(fetchEvents())
|
||||
}, [dispatch])
|
||||
// // Fetch events on mount
|
||||
// useEffect(() => {
|
||||
// dispatch(fetchEvents())
|
||||
// }, [dispatch])
|
||||
|
||||
const handleLeftSidebarToggle = () => setLeftSidebarOpen(!leftSidebarOpen)
|
||||
// const handleLeftSidebarToggle = () => setLeftSidebarOpen(!leftSidebarOpen)
|
||||
|
||||
const handleAddEventSidebarToggle = () => setAddEventSidebarOpen(!addEventSidebarOpen)
|
||||
// const handleAddEventSidebarToggle = () => setAddEventSidebarOpen(!addEventSidebarOpen)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarLeft
|
||||
mdAbove={mdAbove}
|
||||
dispatch={dispatch}
|
||||
calendarApi={calendarApi}
|
||||
calendarStore={calendarStore}
|
||||
calendarsColor={calendarsColor}
|
||||
leftSidebarOpen={leftSidebarOpen}
|
||||
handleLeftSidebarToggle={handleLeftSidebarToggle}
|
||||
handleAddEventSidebarToggle={handleAddEventSidebarToggle}
|
||||
/>
|
||||
<div className='p-6 pbe-0 flex-grow overflow-visible bg-backgroundPaper rounded'>
|
||||
<Calendar
|
||||
dispatch={dispatch}
|
||||
calendarApi={calendarApi}
|
||||
calendarStore={calendarStore}
|
||||
setCalendarApi={setCalendarApi}
|
||||
calendarsColor={calendarsColor}
|
||||
handleLeftSidebarToggle={handleLeftSidebarToggle}
|
||||
handleAddEventSidebarToggle={handleAddEventSidebarToggle}
|
||||
/>
|
||||
</div>
|
||||
<AddEventSidebar
|
||||
dispatch={dispatch}
|
||||
calendarApi={calendarApi}
|
||||
calendarStore={calendarStore}
|
||||
addEventSidebarOpen={addEventSidebarOpen}
|
||||
handleAddEventSidebarToggle={handleAddEventSidebarToggle}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
// return (
|
||||
// <>
|
||||
// <SidebarLeft
|
||||
// mdAbove={mdAbove}
|
||||
// dispatch={dispatch}
|
||||
// calendarApi={calendarApi}
|
||||
// calendarStore={calendarStore}
|
||||
// calendarsColor={calendarsColor}
|
||||
// leftSidebarOpen={leftSidebarOpen}
|
||||
// handleLeftSidebarToggle={handleLeftSidebarToggle}
|
||||
// handleAddEventSidebarToggle={handleAddEventSidebarToggle}
|
||||
// />
|
||||
// <div className='p-6 pbe-0 flex-grow overflow-visible bg-backgroundPaper rounded'>
|
||||
// <Calendar
|
||||
// dispatch={dispatch}
|
||||
// calendarApi={calendarApi}
|
||||
// calendarStore={calendarStore}
|
||||
// setCalendarApi={setCalendarApi}
|
||||
// calendarsColor={calendarsColor}
|
||||
// handleLeftSidebarToggle={handleLeftSidebarToggle}
|
||||
// handleAddEventSidebarToggle={handleAddEventSidebarToggle}
|
||||
// />
|
||||
// </div>
|
||||
// <AddEventSidebar
|
||||
// dispatch={dispatch}
|
||||
// calendarApi={calendarApi}
|
||||
// calendarStore={calendarStore}
|
||||
// addEventSidebarOpen={addEventSidebarOpen}
|
||||
// handleAddEventSidebarToggle={handleAddEventSidebarToggle}
|
||||
// />
|
||||
// </>
|
||||
// )
|
||||
}
|
||||
|
||||
export default AppCalendar
|
||||
|
||||
@@ -1,23 +1,6 @@
|
||||
// MUI Imports
|
||||
import Button from '@mui/material/Button'
|
||||
import Drawer from '@mui/material/Drawer'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import Checkbox from '@mui/material/Checkbox'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import FormControlLabel from '@mui/material/FormControlLabel'
|
||||
|
||||
// Third-party imports
|
||||
import classnames from 'classnames'
|
||||
|
||||
// Types Imports
|
||||
import type { SidebarLeftProps, CalendarFiltersType } from '@/types/apps/calendarTypes'
|
||||
import type { ThemeColor } from '@core/types'
|
||||
|
||||
// Styled Component Imports
|
||||
import AppJalaliDatepicker from '@/libs/styles/AppJalaliDatepicker'
|
||||
|
||||
// Slice Imports
|
||||
import { filterAllCalendarLabels, filterCalendarLabel, selectedEvent } from '@/redux-store/slices/calendar'
|
||||
|
||||
const SidebarLeft = (props: SidebarLeftProps) => {
|
||||
// Props
|
||||
@@ -32,105 +15,205 @@ const SidebarLeft = (props: SidebarLeftProps) => {
|
||||
handleAddEventSidebarToggle
|
||||
} = props
|
||||
|
||||
// Vars
|
||||
const colorsArr = calendarsColor ? Object.entries(calendarsColor) : []
|
||||
// // Vars
|
||||
// const colorsArr = calendarsColor ? Object.entries(calendarsColor) : []
|
||||
|
||||
const renderFilters = colorsArr.length
|
||||
? colorsArr.map(([key, value]: string[]) => {
|
||||
return (
|
||||
<FormControlLabel
|
||||
className='mbe-1'
|
||||
key={key}
|
||||
label={key}
|
||||
control={
|
||||
<Checkbox
|
||||
color={value as ThemeColor}
|
||||
checked={calendarStore.selectedCalendars.indexOf(key as CalendarFiltersType) > -1}
|
||||
onChange={() => dispatch(filterCalendarLabel(key as CalendarFiltersType))}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null
|
||||
// const renderFilters = colorsArr.length
|
||||
// ? colorsArr.map(([key, value]: string[]) => {
|
||||
// return (
|
||||
// <FormControlLabel
|
||||
// className='mbe-1'
|
||||
// key={key}
|
||||
// label={key}
|
||||
// control={
|
||||
// <Checkbox
|
||||
// color={value as ThemeColor}
|
||||
// checked={calendarStore.selectedCalendars.indexOf(key as CalendarFiltersType) > -1}
|
||||
// onChange={() => dispatch(filterCalendarLabel(key as CalendarFiltersType))}
|
||||
// />
|
||||
// }
|
||||
// />
|
||||
// )
|
||||
// })
|
||||
// : null
|
||||
|
||||
const handleSidebarToggleSidebar = () => {
|
||||
dispatch(selectedEvent(null))
|
||||
handleAddEventSidebarToggle()
|
||||
}
|
||||
// const handleSidebarToggleSidebar = () => {
|
||||
// dispatch(selectedEvent(null))
|
||||
// handleAddEventSidebarToggle()
|
||||
// }
|
||||
|
||||
if (renderFilters) {
|
||||
return (
|
||||
<Drawer
|
||||
open={leftSidebarOpen}
|
||||
onClose={handleLeftSidebarToggle}
|
||||
variant={mdAbove ? 'permanent' : 'temporary'}
|
||||
ModalProps={{
|
||||
disablePortal: true,
|
||||
disableAutoFocus: true,
|
||||
disableScrollLock: true,
|
||||
keepMounted: true // Better open performance on mobile.
|
||||
}}
|
||||
className={classnames('block', { static: mdAbove, absolute: !mdAbove })}
|
||||
PaperProps={{
|
||||
className: classnames('items-start is-[280px] shadow-none rounded rounded-se-none rounded-ee-none', {
|
||||
static: mdAbove,
|
||||
absolute: !mdAbove
|
||||
})
|
||||
}}
|
||||
sx={{
|
||||
zIndex: 3,
|
||||
'& .MuiDrawer-paper': {
|
||||
zIndex: mdAbove ? 2 : 'drawer'
|
||||
},
|
||||
'& .MuiBackdrop-root': {
|
||||
borderRadius: 1,
|
||||
position: 'absolute'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className='is-full p-6'>
|
||||
<Button
|
||||
fullWidth
|
||||
variant='contained'
|
||||
onClick={handleSidebarToggleSidebar}
|
||||
startIcon={<i className='tabler-plus' />}
|
||||
>
|
||||
Add Event
|
||||
</Button>
|
||||
</div>
|
||||
<Divider className='is-full' />
|
||||
<AppJalaliDatepicker
|
||||
onChange={date => calendarApi?.gotoDate(date)}
|
||||
boxProps={{
|
||||
className: 'flex justify-center is-full',
|
||||
sx: { '& .react-datepicker': { boxShadow: 'none !important', border: 'none !important' } }
|
||||
}}
|
||||
/>
|
||||
<Divider className='is-full' />
|
||||
// if (renderFilters) {
|
||||
// return (
|
||||
// <Drawer
|
||||
// open={leftSidebarOpen}
|
||||
// onClose={handleLeftSidebarToggle}
|
||||
// variant={mdAbove ? 'permanent' : 'temporary'}
|
||||
// ModalProps={{
|
||||
// disablePortal: true,
|
||||
// disableAutoFocus: true,
|
||||
// disableScrollLock: true,
|
||||
// keepMounted: true // Better open performance on mobile.
|
||||
// }}
|
||||
// className={classnames('block', { static: mdAbove, absolute: !mdAbove })}
|
||||
// PaperProps={{
|
||||
// className: classnames('items-start is-[280px] shadow-none rounded rounded-se-none rounded-ee-none', {
|
||||
// static: mdAbove,
|
||||
// absolute: !mdAbove
|
||||
// })
|
||||
// }}
|
||||
// sx={{
|
||||
// zIndex: 3,
|
||||
// '& .MuiDrawer-paper': {
|
||||
// zIndex: mdAbove ? 2 : 'drawer'
|
||||
// },
|
||||
// '& .MuiBackdrop-root': {
|
||||
// borderRadius: 1,
|
||||
// position: 'absolute'
|
||||
// }
|
||||
// }}
|
||||
// >
|
||||
// <div className='is-full p-6'>
|
||||
// <Button
|
||||
// fullWidth
|
||||
// variant='contained'
|
||||
// onClick={handleSidebarToggleSidebar}
|
||||
// startIcon={<i className='tabler-plus' />}
|
||||
// >
|
||||
// Add Event
|
||||
// </Button>
|
||||
// </div>
|
||||
// <Divider className='is-full' />
|
||||
// <AppJalaliDatepicker
|
||||
// onChange={date => calendarApi?.gotoDate(date)}
|
||||
// boxProps={{
|
||||
// className: 'flex justify-center is-full',
|
||||
// sx: { '& .react-datepicker': { boxShadow: 'none !important', border: 'none !important' } }
|
||||
// }}
|
||||
// />
|
||||
// <Divider className='is-full' />
|
||||
|
||||
<div className='flex flex-col p-6 is-full'>
|
||||
<Typography variant='h5' className='mbe-4'>
|
||||
Event Filters
|
||||
</Typography>
|
||||
<FormControlLabel
|
||||
className='mbe-1'
|
||||
label='View All'
|
||||
control={
|
||||
<Checkbox
|
||||
color='secondary'
|
||||
checked={calendarStore.selectedCalendars.length === colorsArr.length}
|
||||
onChange={e => dispatch(filterAllCalendarLabels(e.target.checked))}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{renderFilters}
|
||||
</div>
|
||||
</Drawer>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
// <div className='flex flex-col p-6 is-full'>
|
||||
// <Typography variant='h5' className='mbe-4'>
|
||||
// Event Filters
|
||||
// </Typography>
|
||||
// <FormControlLabel
|
||||
// className='mbe-1'
|
||||
// label='View All'
|
||||
// control={
|
||||
// <Checkbox
|
||||
// color='secondary'
|
||||
// checked={calendarStore.selectedCalendars.length === colorsArr.length}
|
||||
// onChange={e => dispatch(filterAllCalendarLabels(e.target.checked))}
|
||||
// />
|
||||
// }
|
||||
// />
|
||||
// {renderFilters}
|
||||
// </div>
|
||||
// </Drawer>
|
||||
// )
|
||||
// } else {
|
||||
// return null
|
||||
// }
|
||||
// // Vars
|
||||
// const colorsArr = calendarsColor ? Object.entries(calendarsColor) : []
|
||||
|
||||
// const renderFilters = colorsArr.length
|
||||
// ? colorsArr.map(([key, value]: string[]) => {
|
||||
// return (
|
||||
// <FormControlLabel
|
||||
// className='mbe-1'
|
||||
// key={key}
|
||||
// label={key}
|
||||
// control={
|
||||
// <Checkbox
|
||||
// color={value as ThemeColor}
|
||||
// checked={calendarStore.selectedCalendars.indexOf(key as CalendarFiltersType) > -1}
|
||||
// onChange={() => dispatch(filterCalendarLabel(key as CalendarFiltersType))}
|
||||
// />
|
||||
// }
|
||||
// />
|
||||
// )
|
||||
// })
|
||||
// : null
|
||||
|
||||
// const handleSidebarToggleSidebar = () => {
|
||||
// dispatch(selectedEvent(null))
|
||||
// handleAddEventSidebarToggle()
|
||||
// }
|
||||
|
||||
// if (renderFilters) {
|
||||
// return (
|
||||
// <Drawer
|
||||
// open={leftSidebarOpen}
|
||||
// onClose={handleLeftSidebarToggle}
|
||||
// variant={mdAbove ? 'permanent' : 'temporary'}
|
||||
// ModalProps={{
|
||||
// disablePortal: true,
|
||||
// disableAutoFocus: true,
|
||||
// disableScrollLock: true,
|
||||
// keepMounted: true // Better open performance on mobile.
|
||||
// }}
|
||||
// className={classnames('block', { static: mdAbove, absolute: !mdAbove })}
|
||||
// PaperProps={{
|
||||
// className: classnames('items-start is-[280px] shadow-none rounded rounded-se-none rounded-ee-none', {
|
||||
// static: mdAbove,
|
||||
// absolute: !mdAbove
|
||||
// })
|
||||
// }}
|
||||
// sx={{
|
||||
// zIndex: 3,
|
||||
// '& .MuiDrawer-paper': {
|
||||
// zIndex: mdAbove ? 2 : 'drawer'
|
||||
// },
|
||||
// '& .MuiBackdrop-root': {
|
||||
// borderRadius: 1,
|
||||
// position: 'absolute'
|
||||
// }
|
||||
// }}
|
||||
// >
|
||||
// <div className='is-full p-6'>
|
||||
// <Button
|
||||
// fullWidth
|
||||
// variant='contained'
|
||||
// onClick={handleSidebarToggleSidebar}
|
||||
// startIcon={<i className='tabler-plus' />}
|
||||
// >
|
||||
// Add Event
|
||||
// </Button>
|
||||
// </div>
|
||||
// <Divider className='is-full' />
|
||||
// <AppJalaliDatepicker
|
||||
// onChange={date => calendarApi?.gotoDate(date)}
|
||||
// boxProps={{
|
||||
// className: 'flex justify-center is-full',
|
||||
// sx: { '& .react-datepicker': { boxShadow: 'none !important', border: 'none !important' } }
|
||||
// }}
|
||||
// />
|
||||
// <Divider className='is-full' />
|
||||
|
||||
// <div className='flex flex-col p-6 is-full'>
|
||||
// <Typography variant='h5' className='mbe-4'>
|
||||
// Event Filters
|
||||
// </Typography>
|
||||
// <FormControlLabel
|
||||
// className='mbe-1'
|
||||
// label='View All'
|
||||
// control={
|
||||
// <Checkbox
|
||||
// color='secondary'
|
||||
// checked={calendarStore.selectedCalendars.length === colorsArr.length}
|
||||
// onChange={e => dispatch(filterAllCalendarLabels(e.target.checked))}
|
||||
// />
|
||||
// }
|
||||
// />
|
||||
// {renderFilters}
|
||||
// </div>
|
||||
// </Drawer>
|
||||
// )
|
||||
// } else {
|
||||
// return null
|
||||
// }
|
||||
return <></>
|
||||
}
|
||||
|
||||
export default SidebarLeft
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import Box from '@mui/material/Box'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
|
||||
// Component Imports
|
||||
import SoilMoistureHeatmap from '@views/dashboards/farm/SoilMoistureHeatmap'
|
||||
import SensorValuesList from '@views/dashboards/farm/SensorValuesList'
|
||||
import SensorComparisonChart from '@views/dashboards/farm/SensorComparisonChart'
|
||||
import SensorRadarChart from '@views/dashboards/farm/SensorRadarChart'
|
||||
import AnomalyDetectionCard from '@views/dashboards/farm/AnomalyDetectionCard'
|
||||
|
||||
// Service
|
||||
import { farmDashboardService } from '@/libs/api/services/farmDashboardService'
|
||||
import type { CardId } from '@views/dashboards/farm/farmDashboardConfig'
|
||||
import { CARD_GRID_SIZE } from '@views/dashboards/farm/farmDashboardConfig'
|
||||
|
||||
const SOIL_CARD_IDS: CardId[] = [
|
||||
'soilMoistureHeatmap',
|
||||
'sensorValuesList',
|
||||
'sensorRadarChart',
|
||||
'sensorComparisonChart',
|
||||
'anomalyDetectionCard'
|
||||
]
|
||||
|
||||
const cardRowSx = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
'& > *': { flex: 1, minHeight: 0 }
|
||||
}
|
||||
|
||||
const CARD_COMPONENTS: Partial<Record<CardId, React.ComponentType<{ data?: Record<string, unknown> }>>> = {
|
||||
soilMoistureHeatmap: SoilMoistureHeatmap,
|
||||
sensorValuesList: SensorValuesList,
|
||||
sensorComparisonChart: SensorComparisonChart,
|
||||
sensorRadarChart: SensorRadarChart,
|
||||
anomalyDetectionCard: AnomalyDetectionCard
|
||||
}
|
||||
|
||||
const SoilDataDashboardWrapper = () => {
|
||||
const [cardsData, setCardsData] = useState<Partial<Record<CardId, Record<string, unknown>>>>({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
farmDashboardService
|
||||
.getAllCards()
|
||||
.then(cards => setCardsData(cards ?? {}))
|
||||
.catch(() => setCardsData({}))
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box display='flex' justifyContent='center' alignItems='center' minHeight={200}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box position='relative'>
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={12} container spacing={6} sx={{ display: 'flex', alignItems: 'flex-start' }}>
|
||||
{SOIL_CARD_IDS.map(cardId => {
|
||||
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>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default SoilDataDashboardWrapper
|
||||
@@ -0,0 +1,82 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import Box from '@mui/material/Box'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
|
||||
// Component Imports
|
||||
import FarmWeatherCard from '@views/dashboards/farm/FarmWeatherCard'
|
||||
import FarmAlertsTimeline from '@views/dashboards/farm/FarmAlertsTimeline'
|
||||
import WaterNeedPrediction from '@views/dashboards/farm/WaterNeedPrediction'
|
||||
import SensorValuesList from '@views/dashboards/farm/SensorValuesList'
|
||||
|
||||
// Service
|
||||
import { farmDashboardService } from '@/libs/api/services/farmDashboardService'
|
||||
import type { CardId } from '@views/dashboards/farm/farmDashboardConfig'
|
||||
import { CARD_GRID_SIZE } from '@views/dashboards/farm/farmDashboardConfig'
|
||||
|
||||
const WATER_CARD_IDS: CardId[] = [
|
||||
'farmWeatherCard',
|
||||
'farmAlertsTimeline',
|
||||
'waterNeedPrediction',
|
||||
'sensorValuesList'
|
||||
]
|
||||
|
||||
const cardRowSx = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
'& > *': { flex: 1, minHeight: 0 }
|
||||
}
|
||||
|
||||
const CARD_COMPONENTS: Partial<Record<CardId, React.ComponentType<{ data?: Record<string, unknown> }>>> = {
|
||||
farmWeatherCard: FarmWeatherCard,
|
||||
farmAlertsTimeline: FarmAlertsTimeline,
|
||||
waterNeedPrediction: WaterNeedPrediction,
|
||||
sensorValuesList: SensorValuesList
|
||||
}
|
||||
|
||||
const WaterDataDashboardWrapper = () => {
|
||||
const [cardsData, setCardsData] = useState<Partial<Record<CardId, Record<string, unknown>>>>({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
farmDashboardService
|
||||
.getAllCards()
|
||||
.then(cards => setCardsData(cards ?? {}))
|
||||
.catch(() => setCardsData({}))
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box display='flex' justifyContent='center' alignItems='center' minHeight={200}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box position='relative'>
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={12} container spacing={6} sx={{ display: 'flex', alignItems: 'flex-start' }}>
|
||||
{WATER_CARD_IDS.map(cardId => {
|
||||
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>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default WaterDataDashboardWrapper
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import { useState, useCallback } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
@@ -9,30 +10,72 @@ import Grid from '@mui/material/Grid2'
|
||||
import Button from '@mui/material/Button'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
import Alert from '@mui/material/Alert'
|
||||
import Typography from '@mui/material/Typography'
|
||||
|
||||
// API Imports
|
||||
import { sensorHubService } from '@/libs/api'
|
||||
|
||||
// Component Imports
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
import CustomAutocomplete from '@core/components/mui/Autocomplete'
|
||||
import type { MapDrawGeoJSON } from '@/components/MapDraw'
|
||||
|
||||
const MapDraw = dynamic(() => import('@/components/MapDraw').then(mod => mod.MapDraw), {
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className='flex items-center justify-center h-[400px] rounded-lg bg-action-hover'>
|
||||
<CircularProgress size={32} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
type FormSensorHubProps = {
|
||||
onBack: () => void
|
||||
}
|
||||
|
||||
const PLANT_TYPES = ['گندم', 'جو', 'ذرت', 'برنج', 'پنبه', 'چغندر قند', 'سیبزمینی', 'گوجهفرنگی', 'پیاز', 'سبزیجات']
|
||||
|
||||
const PLANT_NAMES_BY_TYPE: Record<string, string[]> = {
|
||||
گندم: ['رقم آذر', 'رقم شریف', 'رقم مروارید', 'رقم بهار', 'رقم الوند'],
|
||||
جو: ['رقم سرداری', 'رقم زاگرس', 'رقم کرج', 'رقم ریجاب'],
|
||||
ذرت: ['رقم سینگل کراس ۷۰۴', 'رقم سینگل کراس ۷۰۷', 'رقم ماکزیما'],
|
||||
برنج: ['رقم فجر', 'رقم خزر', 'رقم طارم'],
|
||||
پنبه: ['رقم ورامین', 'رقم ساحل', 'رقم سپید'],
|
||||
'چغندر قند': ['رقم اکباتان', 'رقم شیرین', 'رقم پاییزه'],
|
||||
سیبزمینی: ['رقم آگریا', 'رقم مارفونا', 'رقم ساوالان'],
|
||||
گوجهفرنگی: ['رقم چری', 'رقم روتگرز', 'رقم پامیس'],
|
||||
پیاز: ['رقم قرمز آذرشهر', 'رقم سفید قم', 'رقم زرد'],
|
||||
سبزیجات: ['کاهو', 'جعفری', 'شوید', 'تره', 'ریحان']
|
||||
}
|
||||
|
||||
const FormSensorHub = ({ onBack }: FormSensorHubProps) => {
|
||||
const t = useTranslations('sensorHub')
|
||||
const [name, setName] = useState('')
|
||||
const [uuidSensor, setUuidSensor] = useState('')
|
||||
const [plantType, setPlantType] = useState<string | null>(null)
|
||||
const [plantName, setPlantName] = useState<string | null>(null)
|
||||
const [areaGeoJson, setAreaGeoJson] = useState<MapDrawGeoJSON>(null)
|
||||
const [areaM2, setAreaM2] = useState<number | undefined>(undefined)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const handleAreaChange = useCallback((geojson: MapDrawGeoJSON, area?: number) => {
|
||||
setAreaGeoJson(geojson)
|
||||
setAreaM2(area)
|
||||
}, [])
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
console.log(areaGeoJson)
|
||||
try {
|
||||
await sensorHubService.addSensor({ name, uuid_sensor: uuidSensor })
|
||||
await sensorHubService.addSensor({
|
||||
name,
|
||||
uuid_sensor: uuidSensor,
|
||||
...(areaGeoJson && { area_geojson: areaGeoJson }),
|
||||
...(areaM2 !== undefined && { area_m2: areaM2 })
|
||||
})
|
||||
onBack()
|
||||
} catch (err: unknown) {
|
||||
const message = err && typeof err === 'object' && 'message' in err ? String((err as { message: string }).message) : t('errorSave')
|
||||
@@ -83,6 +126,45 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => {
|
||||
onChange={e => setUuidSensor(e.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<CustomAutocomplete
|
||||
fullWidth
|
||||
options={PLANT_TYPES}
|
||||
value={plantType}
|
||||
onChange={(_, v) => {
|
||||
setPlantType(v)
|
||||
setPlantName(null)
|
||||
}}
|
||||
renderInput={params => <CustomTextField {...params} label={t('plantType')} />}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<CustomAutocomplete
|
||||
fullWidth
|
||||
disabled={!plantType}
|
||||
options={plantType ? PLANT_NAMES_BY_TYPE[plantType] ?? [] : []}
|
||||
value={plantName}
|
||||
onChange={(_, v) => setPlantName(v)}
|
||||
renderInput={params => <CustomTextField {...params} label={t('plantName')} />}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Typography variant='body2' color='text.secondary' className='mbe-2'>
|
||||
{t('mapAreaDescription')}
|
||||
</Typography>
|
||||
<MapDraw
|
||||
center={[35.6892, 51.389]}
|
||||
zoom={13}
|
||||
height={400}
|
||||
singleShape
|
||||
onAreaChange={handleAreaChange}
|
||||
/>
|
||||
{areaM2 !== undefined && areaM2 > 0 && (
|
||||
<Typography variant='caption' color='text.secondary' className='mts-2 block'>
|
||||
مساحت تقریبی: {areaM2 >= 10000 ? `${(areaM2 / 10000).toFixed(2)} هکتار` : `${areaM2.toFixed(0)} متر مربع`}
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }} className='flex gap-2'>
|
||||
<Button variant='tonal' color='secondary' onClick={onBack} disabled={loading}>
|
||||
{t('cancel')}
|
||||
|
||||
Reference in New Issue
Block a user