Files
Frontend/src/views/dashboards/farm/pestDetection/PlantPestDetection.tsx
T

201 lines
6.9 KiB
TypeScript
Raw Normal View History

'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 { useTheme } from '@mui/material/styles'
import { alpha } from '@mui/material/styles'
import classnames from 'classnames'
// Util Imports
import { commonLayoutClasses } from '@layouts/utils/layoutClasses'
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 theme = useTheme()
const primary = theme.palette.primary
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={classnames(commonLayoutClasses.contentHeightFixed, 'flex flex-col is-full overflow-hidden rounded')}
sx={{ minHeight: '100%' }}
>
<Box className="is-full py-6 sm:py-8 flex flex-col">
{/* Header */}
<Box className="mb-8">
<Typography
variant="h4"
className="font-bold tracking-tight"
sx={{
background: `linear-gradient(135deg, ${primary.main} 0%, ${primary.dark} 50%, ${primary.dark} 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, ${theme.palette.background.paper} 0%, ${alpha(primary.main, 0.04)} 100%)`,
boxShadow: `0 4px 24px ${alpha(primary.main, 0.08)}, ${theme.shadows[1]}`,
border: `1px solid ${alpha(primary.main, 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" />
) : (
<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, ${primary.main} 0%, ${primary.dark} 50%, ${primary.dark} 100%)`,
boxShadow: `0 4px 20px ${alpha(primary.main, 0.35)}`,
'&:hover': {
background: `linear-gradient(135deg, ${primary.light} 0%, ${primary.main} 50%, ${primary.dark} 100%)`,
boxShadow: `0 6px 24px ${alpha(primary.main, 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, ${theme.palette.background.paper} 0%, ${alpha(primary.main, 0.06)} 100%)`,
boxShadow: `0 8px 32px ${alpha(primary.main, 0.1)}`,
border: `1px solid ${alpha(primary.main, 0.12)}`,
}}
>
<CardContent className="p-12 flex flex-col items-center gap-4">
<CircularProgress size={48} sx={{ color: 'primary.main' }} />
<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>
)
}