Add irrigation and fertilization recommendations, farm AI assistant, and pest detection features with Persian localization
- Introduced new sections in the dashboard for irrigation recommendations, fertilization recommendations, farm AI assistant, and pest detection. - Added Persian translations for new features to enhance user experience. - Updated the vertical menu to include links to the new sections. - Enhanced global styles with animations for improved UI interactions.
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import Box from '@mui/material/Box'
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Button from '@mui/material/Button'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
import Collapse from '@mui/material/Collapse'
|
||||
import UploadBox from './components/UploadBox'
|
||||
import ResultCard from './components/ResultCard'
|
||||
import type { UploadedFile } from './components/UploadBox'
|
||||
import type { PestResult } from './components/ResultCard'
|
||||
|
||||
export default function PlantPestDetection() {
|
||||
const t = useTranslations('pestDetection')
|
||||
const [file, setFile] = useState<UploadedFile | null>(null)
|
||||
const [result, setResult] = useState<PestResult | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const handleFileSelect = useCallback((newFile: UploadedFile | null) => {
|
||||
setFile(newFile)
|
||||
setResult(null)
|
||||
setError(null)
|
||||
}, [])
|
||||
|
||||
const handleAnalyze = useCallback(() => {
|
||||
if (!file) return
|
||||
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
setResult(null)
|
||||
|
||||
const delay = 1500 + Math.random() * 1000
|
||||
setTimeout(() => {
|
||||
setResult({
|
||||
pest: t('mockResult.pest'),
|
||||
confidence: 92,
|
||||
description: t('mockResult.description'),
|
||||
treatment: t('mockResult.treatment'),
|
||||
})
|
||||
setLoading(false)
|
||||
}, delay)
|
||||
}, [file, t])
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
if (file) {
|
||||
URL.revokeObjectURL(file.preview)
|
||||
}
|
||||
setFile(null)
|
||||
setResult(null)
|
||||
setError(null)
|
||||
setLoading(false)
|
||||
}, [file])
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="min-bs-screen pb-24"
|
||||
sx={{
|
||||
minHeight: '100vh',
|
||||
}}
|
||||
>
|
||||
<Box className="max-w-2xl mx-auto px-4 py-6 sm:py-8">
|
||||
{/* Header */}
|
||||
<Box className="mb-8">
|
||||
<Typography
|
||||
variant="h4"
|
||||
className="font-bold tracking-tight"
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, #22c55e 0%, #16a34a 50%, #15803d 100%)',
|
||||
backgroundClip: 'text',
|
||||
WebkitBackgroundClip: 'text',
|
||||
color: 'transparent',
|
||||
fontSize: { xs: '1.5rem', sm: '1.75rem' },
|
||||
}}
|
||||
>
|
||||
{t('title')}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" className="mt-1">
|
||||
{t('subtitle')}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Upload card */}
|
||||
<Card
|
||||
elevation={0}
|
||||
className="mb-6 overflow-hidden transition-all duration-300 hover:shadow-lg"
|
||||
sx={{
|
||||
borderRadius: '24px',
|
||||
background: 'linear-gradient(145deg, #ffffff 0%, #f8fcf8 100%)',
|
||||
boxShadow: '0 4px 24px rgba(34, 197, 94, 0.08), 0 1px 3px rgba(0,0,0,0.04)',
|
||||
border: '1px solid rgba(34, 197, 94, 0.12)',
|
||||
}}
|
||||
>
|
||||
<CardContent className="p-5 sm:p-6">
|
||||
<UploadBox
|
||||
file={file}
|
||||
onFileSelect={handleFileSelect}
|
||||
onError={setError}
|
||||
error={error ?? undefined}
|
||||
/>
|
||||
|
||||
{/* Action buttons */}
|
||||
<Box className="mt-6 flex flex-col gap-3 sm:flex-row sm:justify-center">
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!file || loading}
|
||||
onClick={handleAnalyze}
|
||||
startIcon={
|
||||
loading ? (
|
||||
<CircularProgress size={20} color="inherit" sx={{ color: 'white' }} />
|
||||
) : (
|
||||
<i className="tabler-scan text-xl" />
|
||||
)
|
||||
}
|
||||
className="rounded-xl py-3 px-8 font-semibold shadow-md transition-all duration-300 hover:shadow-lg hover:scale-[1.02] active:scale-[0.98]"
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, #22c55e 0%, #16a34a 50%, #15803d 100%)',
|
||||
boxShadow: '0 4px 20px rgba(34, 197, 94, 0.35)',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #4ade80 0%, #22c55e 50%, #16a34a 100%)',
|
||||
boxShadow: '0 6px 24px rgba(34, 197, 94, 0.45)',
|
||||
},
|
||||
'&:disabled': {
|
||||
background: 'action.disabledBackground',
|
||||
color: 'action.disabled',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{loading ? t('analyzing') : t('analyze')}
|
||||
</Button>
|
||||
|
||||
{file && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleReset}
|
||||
disabled={loading}
|
||||
startIcon={<i className="tabler-rotate-2 text-lg" />}
|
||||
className="rounded-xl py-3 px-8 font-semibold"
|
||||
sx={{
|
||||
borderColor: 'divider',
|
||||
'&:hover': {
|
||||
borderColor: 'primary.main',
|
||||
backgroundColor: 'action.hover',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t('reset')}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Loading state */}
|
||||
<Collapse in={loading}>
|
||||
<Card
|
||||
elevation={0}
|
||||
className="mb-6"
|
||||
sx={{
|
||||
borderRadius: '24px',
|
||||
background: 'linear-gradient(160deg, #ffffff 0%, #f0fdf4 100%)',
|
||||
boxShadow: '0 8px 32px rgba(34, 197, 94, 0.1)',
|
||||
border: '1px solid rgba(34, 197, 94, 0.12)',
|
||||
}}
|
||||
>
|
||||
<CardContent className="p-12 flex flex-col items-center gap-4">
|
||||
<CircularProgress size={48} sx={{ color: '#22c55e' }} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t('analyzing')}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Collapse>
|
||||
|
||||
{/* Result card */}
|
||||
<Collapse in={!!result && !loading}>
|
||||
{result && !loading && (
|
||||
<Box className="mb-6">
|
||||
<Typography variant="subtitle2" fontWeight={600} color="text.secondary" className="mbe-3">
|
||||
{t('resultTitle')}
|
||||
</Typography>
|
||||
<ResultCard result={result} />
|
||||
</Box>
|
||||
)}
|
||||
</Collapse>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user