Add plant simulator feature with Persian localization and UI enhancements
- Introduced a new Plant Simulator section in the dashboard with a dedicated menu item. - Added Persian translations for plant growth metrics and chart titles. - Enhanced the GrowthChart component to utilize localized labels for better user experience. - Updated PlantSimulator component to display plant statistics and growth visualization. - Improved UI layout with Material-UI components for a more polished appearance.
This commit is contained in:
@@ -37,6 +37,7 @@
|
|||||||
"waterData": "دیتاهای آب",
|
"waterData": "دیتاهای آب",
|
||||||
"soilData": "اطلاعات خاک",
|
"soilData": "اطلاعات خاک",
|
||||||
"cropZoning": "زونبندی کشت",
|
"cropZoning": "زونبندی کشت",
|
||||||
|
"plantSimulator": "شبیهساز رشد گیاه",
|
||||||
"dataSection": "بخش دادهها",
|
"dataSection": "بخش دادهها",
|
||||||
"crm": "مدیریت ارتباط با مشتری",
|
"crm": "مدیریت ارتباط با مشتری",
|
||||||
"analytics": "تحلیلها",
|
"analytics": "تحلیلها",
|
||||||
@@ -561,5 +562,35 @@
|
|||||||
"forecastChart": "پیشبینی هوا",
|
"forecastChart": "پیشبینی هوا",
|
||||||
"forecastSubheader": "دما و رطوبت در ۷ روز آینده"
|
"forecastSubheader": "دما و رطوبت در ۷ روز آینده"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"plantSimulator": {
|
||||||
|
"title": "شبیهساز رشد گیاه",
|
||||||
|
"height": "ارتفاع",
|
||||||
|
"leaves": "برگ",
|
||||||
|
"branches": "شاخه",
|
||||||
|
"fruits": "میوه",
|
||||||
|
"yield": "محصول (g)",
|
||||||
|
"yieldRate": "سرعت (g/s)",
|
||||||
|
"maxGrowthReached": "گیاه به حداکثر رشد رسید!",
|
||||||
|
"controls": "کنترلها",
|
||||||
|
"stop": "توقف",
|
||||||
|
"start": "شروع",
|
||||||
|
"reset": "ریست",
|
||||||
|
"growthSpeed": "سرعت رشد",
|
||||||
|
"light": "نور",
|
||||||
|
"water": "آب",
|
||||||
|
"effectiveRate": "نرخ رشد مؤثر",
|
||||||
|
"chartTitle": "نمودار رشد گیاه",
|
||||||
|
"chartHeight": "ارتفاع",
|
||||||
|
"chartLeaves": "برگ",
|
||||||
|
"chartHeightPx": "ارتفاع (px)",
|
||||||
|
"chartLeafCount": "تعداد برگ",
|
||||||
|
"chartYield": "محصول (g)",
|
||||||
|
"chartYieldRate": "سرعت محصول (g/s)",
|
||||||
|
"progressGrowth": "پیشرفت رشد",
|
||||||
|
"lightStatus": "وضعیت نور",
|
||||||
|
"waterStatus": "وضعیت آب",
|
||||||
|
"yieldStatus": "محصول دهی",
|
||||||
|
"description": "این شبیهساز رشد گیاه را بر اساس سرعت پایه، میزان نور خورشید و آب دریافتی محاسبه میکند. هر برگ به صورت تدریجی روی ساقه ظاهر شده و با حرکت طبیعی در باد نمایش داده میشود. محصولدهی (g) پس از ۲۰٪ رشد شروع شده و با تعداد برگ، نور و آب شتاب میگیرد. سرعت محصول (g/s) نشاندهنده نرخ لحظهای تولید است. نمودار تغییرات همه شاخصها را در طول زمان ثبت میکند."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,9 @@ const VerticalMenu = ({ scrollMenu }: Props) => {
|
|||||||
<MenuItem href="/dashboard/crop-zoning" icon={<i className="tabler-map-2" />}>
|
<MenuItem href="/dashboard/crop-zoning" icon={<i className="tabler-map-2" />}>
|
||||||
{t('cropZoning')}
|
{t('cropZoning')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem href="/dashboard/plant-simulator" icon={<i className="tabler-flower" />}>
|
||||||
|
{t('plantSimulator')}
|
||||||
|
</MenuItem>
|
||||||
</MenuSection>
|
</MenuSection>
|
||||||
|
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useRef, useState, useCallback, memo } from 'react'
|
import { useEffect, useRef, useState, useCallback, memo } from 'react'
|
||||||
|
import { useTranslations } from 'next-intl'
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
LineElement,
|
LineElement,
|
||||||
@@ -13,6 +14,12 @@ import {
|
|||||||
Filler
|
Filler
|
||||||
} from 'chart.js'
|
} from 'chart.js'
|
||||||
import { Line } from 'react-chartjs-2'
|
import { Line } from 'react-chartjs-2'
|
||||||
|
import Card from '@mui/material/Card'
|
||||||
|
import CardContent from '@mui/material/CardContent'
|
||||||
|
import Typography from '@mui/material/Typography'
|
||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import Grid from '@mui/material/Grid2'
|
||||||
|
import Button from '@mui/material/Button'
|
||||||
|
|
||||||
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Title, Tooltip, Legend, Filler)
|
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Title, Tooltip, Legend, Filler)
|
||||||
|
|
||||||
@@ -393,40 +400,48 @@ function PlantSVG({ plant, tick, running }: { plant: PlantState; tick: number; r
|
|||||||
|
|
||||||
// ─── Growth Chart ─────────────────────────────────────────────────────────────
|
// ─── Growth Chart ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const CHART_OPTIONS = {
|
const GrowthChart = memo(function GrowthChart({
|
||||||
|
heightHistory,
|
||||||
|
leafHistory,
|
||||||
|
yieldHistory,
|
||||||
|
yieldRateHistory
|
||||||
|
}: {
|
||||||
|
heightHistory: number[]
|
||||||
|
leafHistory: number[]
|
||||||
|
yieldHistory: number[]
|
||||||
|
yieldRateHistory: number[]
|
||||||
|
}) {
|
||||||
|
const t = useTranslations('plantSimulator')
|
||||||
|
|
||||||
|
const chartOptions = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
animation: { duration: 0 },
|
animation: { duration: 0 },
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { labels: { color: '#e2e8f0', font: { size: 11 } } },
|
legend: { labels: { font: { size: 11 } } },
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: 'نمودار رشد گیاه',
|
text: t('chartTitle'),
|
||||||
color: '#e2e8f0',
|
|
||||||
font: { size: 14 }
|
font: { size: 14 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
ticks: { color: '#94a3b8', maxTicksLimit: 8 },
|
ticks: { maxTicksLimit: 8 }
|
||||||
grid: { color: 'rgba(148,163,184,0.1)' }
|
|
||||||
},
|
},
|
||||||
yHeight: {
|
yHeight: {
|
||||||
type: 'linear' as const,
|
type: 'linear' as const,
|
||||||
position: 'left' as const,
|
position: 'left' as const,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: MAX_HEIGHT,
|
max: MAX_HEIGHT,
|
||||||
ticks: { color: '#4a7c59' },
|
title: { display: true, text: t('chartHeight') }
|
||||||
grid: { color: 'rgba(148,163,184,0.1)' },
|
|
||||||
title: { display: true, text: 'ارتفاع', color: '#4a7c59' }
|
|
||||||
},
|
},
|
||||||
yLeaf: {
|
yLeaf: {
|
||||||
type: 'linear' as const,
|
type: 'linear' as const,
|
||||||
position: 'right' as const,
|
position: 'right' as const,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: MAX_LEAVES,
|
max: MAX_LEAVES,
|
||||||
ticks: { color: '#f9c74f' },
|
|
||||||
grid: { display: false },
|
grid: { display: false },
|
||||||
title: { display: true, text: 'برگ', color: '#f9c74f' }
|
title: { display: true, text: t('chartLeaves') }
|
||||||
},
|
},
|
||||||
yYield: {
|
yYield: {
|
||||||
type: 'linear' as const,
|
type: 'linear' as const,
|
||||||
@@ -446,24 +461,13 @@ const CHART_OPTIONS = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const GrowthChart = memo(function GrowthChart({
|
|
||||||
heightHistory,
|
|
||||||
leafHistory,
|
|
||||||
yieldHistory,
|
|
||||||
yieldRateHistory
|
|
||||||
}: {
|
|
||||||
heightHistory: number[]
|
|
||||||
leafHistory: number[]
|
|
||||||
yieldHistory: number[]
|
|
||||||
yieldRateHistory: number[]
|
|
||||||
}) {
|
|
||||||
const labels = heightHistory.map((_, i) => `${i}s`)
|
const labels = heightHistory.map((_, i) => `${i}s`)
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
labels,
|
labels,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'ارتفاع (px)',
|
label: t('chartHeightPx'),
|
||||||
data: heightHistory,
|
data: heightHistory,
|
||||||
borderColor: '#4a7c59',
|
borderColor: '#4a7c59',
|
||||||
backgroundColor: 'rgba(74,124,89,0.10)',
|
backgroundColor: 'rgba(74,124,89,0.10)',
|
||||||
@@ -473,7 +477,7 @@ const GrowthChart = memo(function GrowthChart({
|
|||||||
yAxisID: 'yHeight'
|
yAxisID: 'yHeight'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'تعداد برگ',
|
label: t('chartLeafCount'),
|
||||||
data: leafHistory,
|
data: leafHistory,
|
||||||
borderColor: '#f9c74f',
|
borderColor: '#f9c74f',
|
||||||
backgroundColor: 'rgba(249,199,79,0.10)',
|
backgroundColor: 'rgba(249,199,79,0.10)',
|
||||||
@@ -483,7 +487,7 @@ const GrowthChart = memo(function GrowthChart({
|
|||||||
yAxisID: 'yLeaf'
|
yAxisID: 'yLeaf'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'محصول (g)',
|
label: t('chartYield'),
|
||||||
data: yieldHistory,
|
data: yieldHistory,
|
||||||
borderColor: '#f97316',
|
borderColor: '#f97316',
|
||||||
backgroundColor: 'rgba(249,115,22,0.10)',
|
backgroundColor: 'rgba(249,115,22,0.10)',
|
||||||
@@ -493,7 +497,7 @@ const GrowthChart = memo(function GrowthChart({
|
|||||||
yAxisID: 'yYield'
|
yAxisID: 'yYield'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'سرعت محصول (g/s)',
|
label: t('chartYieldRate'),
|
||||||
data: yieldRateHistory,
|
data: yieldRateHistory,
|
||||||
borderColor: '#a78bfa',
|
borderColor: '#a78bfa',
|
||||||
backgroundColor: 'rgba(167,139,250,0.10)',
|
backgroundColor: 'rgba(167,139,250,0.10)',
|
||||||
@@ -506,7 +510,7 @@ const GrowthChart = memo(function GrowthChart({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Line data={data} options={CHART_OPTIONS} />
|
return <Line data={data} options={chartOptions} />
|
||||||
})
|
})
|
||||||
|
|
||||||
// ─── Main Component ───────────────────────────────────────────────────────────
|
// ─── Main Component ───────────────────────────────────────────────────────────
|
||||||
@@ -698,135 +702,144 @@ export default function PlantSimulator() {
|
|||||||
}
|
}
|
||||||
}, [running])
|
}, [running])
|
||||||
|
|
||||||
|
const t = useTranslations('plantSimulator')
|
||||||
const isFinished = plant.height >= MAX_HEIGHT
|
const isFinished = plant.height >= MAX_HEIGHT
|
||||||
|
|
||||||
|
const statItems: { value: string | number; label: string; color: 'success' | 'warning' | 'error' | 'secondary' }[] = [
|
||||||
|
{ value: Math.round(plant.height), label: t('height'), color: 'success' },
|
||||||
|
{ value: plant.leaves.length, label: t('leaves'), color: 'warning' },
|
||||||
|
{ value: plant.branches.length, label: t('branches'), color: 'success' },
|
||||||
|
{ value: plant.fruits.length, label: t('fruits'), color: 'error' },
|
||||||
|
{ value: plant.yield.toFixed(1), label: t('yield'), color: 'warning' },
|
||||||
|
{ value: plant.yieldRate.toFixed(2), label: t('yieldRate'), color: 'secondary' }
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-slate-100 p-6'>
|
<Box className='flex flex-col gap-6 is-full'>
|
||||||
<h1 className='text-3xl font-bold text-center mb-8 tracking-tight'>
|
<Typography variant='h4' className='text-center font-bold'>
|
||||||
🌱 شبیهساز رشد گیاه
|
🌱 {t('title')}
|
||||||
</h1>
|
</Typography>
|
||||||
|
|
||||||
<div className='max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-3 gap-6'>
|
|
||||||
|
|
||||||
|
<Grid container spacing={6} className='max-w-6xl mx-auto'>
|
||||||
{/* ── Left: Plant visualization ── */}
|
{/* ── Left: Plant visualization ── */}
|
||||||
<div className='lg:col-span-1 flex flex-col items-center gap-4'>
|
<Grid size={{ xs: 12, lg: 4 }} className='flex flex-col items-center gap-4'>
|
||||||
<div className='bg-slate-800 border border-slate-700 rounded-2xl p-6 w-full flex flex-col items-center shadow-xl'>
|
<Card className='is-full flex flex-col items-center p-6'>
|
||||||
|
<CardContent className='flex flex-col items-center gap-4 is-full p-0'>
|
||||||
<PlantSVG plant={plant} tick={plant.tick} running={running} />
|
<PlantSVG plant={plant} tick={plant.tick} running={running} />
|
||||||
|
|
||||||
{/* Stats */}
|
<Grid container spacing={2} className='is-full'>
|
||||||
<div className='mt-4 grid grid-cols-3 gap-2 w-full text-sm'>
|
{statItems.map((item, idx) => (
|
||||||
<div className='bg-green-900/40 border border-green-700/40 rounded-xl p-2.5 text-center'>
|
<Grid key={idx} size={{ xs: 4 }}>
|
||||||
<div className='text-green-400 font-semibold text-base'>{Math.round(plant.height)}</div>
|
<Card variant='outlined' className='text-center p-2.5'>
|
||||||
<div className='text-slate-400 text-[10px]'>ارتفاع</div>
|
<Typography variant='h6' color={item.color}>
|
||||||
</div>
|
{item.value}
|
||||||
<div className='bg-yellow-900/40 border border-yellow-700/40 rounded-xl p-2.5 text-center'>
|
</Typography>
|
||||||
<div className='text-yellow-400 font-semibold text-base'>{plant.leaves.length}</div>
|
<Typography variant='caption' color='text.secondary'>
|
||||||
<div className='text-slate-400 text-[10px]'>برگ</div>
|
{item.label}
|
||||||
</div>
|
</Typography>
|
||||||
<div className='bg-emerald-900/40 border border-emerald-700/40 rounded-xl p-2.5 text-center'>
|
</Card>
|
||||||
<div className='text-emerald-400 font-semibold text-base'>{plant.branches.length}</div>
|
</Grid>
|
||||||
<div className='text-slate-400 text-[10px]'>شاخه</div>
|
))}
|
||||||
</div>
|
</Grid>
|
||||||
<div className='bg-red-900/40 border border-red-700/40 rounded-xl p-2.5 text-center'>
|
|
||||||
<div className='text-red-400 font-semibold text-base'>{plant.fruits.length}</div>
|
|
||||||
<div className='text-slate-400 text-[10px]'>میوه</div>
|
|
||||||
</div>
|
|
||||||
<div className='bg-orange-900/40 border border-orange-700/40 rounded-xl p-2.5 text-center'>
|
|
||||||
<div className='text-orange-400 font-semibold text-base'>{plant.yield.toFixed(1)}</div>
|
|
||||||
<div className='text-slate-400 text-[10px]'>محصول (g)</div>
|
|
||||||
</div>
|
|
||||||
<div className='bg-violet-900/40 border border-violet-700/40 rounded-xl p-2.5 text-center'>
|
|
||||||
<div className='text-violet-400 font-semibold text-base'>{plant.yieldRate.toFixed(2)}</div>
|
|
||||||
<div className='text-slate-400 text-[10px]'>سرعت (g/s)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isFinished && (
|
{isFinished && (
|
||||||
<div className='mt-3 text-yellow-300 text-sm font-medium animate-pulse'>
|
<Typography variant='body2' color='warning.main' className='font-medium animate-pulse'>
|
||||||
🌼 گیاه به حداکثر رشد رسید!
|
🌼 {t('maxGrowthReached')}
|
||||||
</div>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Controls */}
|
{/* Controls */}
|
||||||
<div className='bg-slate-800 border border-slate-700 rounded-2xl p-5 w-full shadow-xl space-y-4'>
|
<Card className='is-full p-5'>
|
||||||
<h2 className='font-semibold text-slate-300 text-base mb-1'>کنترلها</h2>
|
<CardContent className='space-y-4 p-0'>
|
||||||
|
<Typography variant='subtitle1' component='h2' className='font-semibold'>
|
||||||
|
{t('controls')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
{/* Start / Stop / Reset */}
|
<Box className='flex gap-2'>
|
||||||
<div className='flex gap-2'>
|
<Button
|
||||||
<button
|
variant='contained'
|
||||||
|
color={running ? 'error' : 'success'}
|
||||||
onClick={() => setRunning(r => !r)}
|
onClick={() => setRunning(r => !r)}
|
||||||
disabled={isFinished}
|
disabled={isFinished}
|
||||||
className={`flex-1 py-2 rounded-xl font-semibold transition-all text-sm
|
fullWidth
|
||||||
${running
|
|
||||||
? 'bg-red-600 hover:bg-red-500'
|
|
||||||
: 'bg-green-600 hover:bg-green-500'}
|
|
||||||
disabled:opacity-40 disabled:cursor-not-allowed`}
|
|
||||||
>
|
>
|
||||||
{running ? '⏸ توقف' : '▶ شروع'}
|
{running ? `⏸ ${t('stop')}` : `▶ ${t('start')}`}
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
|
variant='outlined'
|
||||||
|
color='secondary'
|
||||||
onClick={() => { setRunning(false); reset() }}
|
onClick={() => { setRunning(false); reset() }}
|
||||||
className='px-4 py-2 rounded-xl bg-slate-600 hover:bg-slate-500 text-sm font-semibold transition-all'
|
|
||||||
>
|
>
|
||||||
↺ ریست
|
↺ {t('reset')}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</Box>
|
||||||
|
|
||||||
{/* Speed slider */}
|
<Box>
|
||||||
<div>
|
<Typography variant='body2' color='text.secondary' className='flex justify-between mbe-1'>
|
||||||
<label className='flex justify-between text-sm text-slate-400 mb-1'>
|
<span>{t('growthSpeed')}</span>
|
||||||
<span>سرعت رشد</span>
|
<span className='font-medium'>{speed.toFixed(1)}×</span>
|
||||||
<span className='text-white font-medium'>{speed.toFixed(1)}×</span>
|
</Typography>
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type='range' min={0.5} max={5} step={0.5}
|
type='range'
|
||||||
|
min={0.5}
|
||||||
|
max={5}
|
||||||
|
step={0.5}
|
||||||
value={speed}
|
value={speed}
|
||||||
onChange={e => setSpeed(Number(e.target.value))}
|
onChange={e => setSpeed(Number(e.target.value))}
|
||||||
className='w-full accent-green-500'
|
className='w-full'
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
|
|
||||||
{/* Light */}
|
<Box>
|
||||||
<div>
|
<Typography variant='body2' color='text.secondary' className='flex justify-between mbe-1'>
|
||||||
<label className='flex justify-between text-sm text-slate-400 mb-1'>
|
<span>☀️ {t('light')}</span>
|
||||||
<span>☀️ نور</span>
|
<span className='font-medium'>{env.light}%</span>
|
||||||
<span className='text-yellow-400 font-medium'>{env.light}%</span>
|
</Typography>
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type='range' min={0} max={100} step={5}
|
type='range'
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={5}
|
||||||
value={env.light}
|
value={env.light}
|
||||||
onChange={e => setEnv(prev => ({ ...prev, light: Number(e.target.value) }))}
|
onChange={e => setEnv(prev => ({ ...prev, light: Number(e.target.value) }))}
|
||||||
className='w-full accent-yellow-400'
|
className='w-full'
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
|
|
||||||
{/* Water */}
|
<Box>
|
||||||
<div>
|
<Typography variant='body2' color='text.secondary' className='flex justify-between mbe-1'>
|
||||||
<label className='flex justify-between text-sm text-slate-400 mb-1'>
|
<span>💧 {t('water')}</span>
|
||||||
<span>💧 آب</span>
|
<span className='font-medium'>{env.water}%</span>
|
||||||
<span className='text-blue-400 font-medium'>{env.water}%</span>
|
</Typography>
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type='range' min={0} max={100} step={5}
|
type='range'
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={5}
|
||||||
value={env.water}
|
value={env.water}
|
||||||
onChange={e => setEnv(prev => ({ ...prev, water: Number(e.target.value) }))}
|
onChange={e => setEnv(prev => ({ ...prev, water: Number(e.target.value) }))}
|
||||||
className='w-full accent-blue-400'
|
className='w-full'
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
|
|
||||||
{/* Effective rate indicator */}
|
<Card variant='outlined' className='p-3'>
|
||||||
<div className='bg-slate-700/50 rounded-xl p-3 text-xs text-slate-400'>
|
<Typography variant='caption' color='text.secondary'>
|
||||||
نرخ رشد مؤثر:{' '}
|
{t('effectiveRate')}{' '}
|
||||||
<span className='text-green-400 font-semibold'>
|
<Typography component='span' variant='caption' color='success.main' fontWeight='bold'>
|
||||||
{growthRate(env, speed).toFixed(2)}×
|
{growthRate(env, speed).toFixed(2)}×
|
||||||
</span>
|
</Typography>
|
||||||
</div>
|
</Typography>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
{/* ── Right: Chart ── */}
|
{/* ── Right: Chart ── */}
|
||||||
<div className='lg:col-span-2 bg-slate-800 border border-slate-700 rounded-2xl p-6 shadow-xl'>
|
<Grid size={{ xs: 12, lg: 8 }}>
|
||||||
|
<Card className='p-6'>
|
||||||
|
<CardContent>
|
||||||
<GrowthChart
|
<GrowthChart
|
||||||
heightHistory={heightHistory}
|
heightHistory={heightHistory}
|
||||||
leafHistory={leafHistory}
|
leafHistory={leafHistory}
|
||||||
@@ -834,71 +847,87 @@ export default function PlantSimulator() {
|
|||||||
yieldRateHistory={yieldRateHistory}
|
yieldRateHistory={yieldRateHistory}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Info cards */}
|
<Grid container spacing={4} className='mt-6'>
|
||||||
<div className='mt-6 grid grid-cols-2 lg:grid-cols-4 gap-4 text-sm'>
|
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
|
||||||
<div className='bg-slate-700/60 rounded-xl p-4 border border-slate-600'>
|
<Card variant='outlined' className='p-4'>
|
||||||
<div className='text-slate-400 mb-1'>پیشرفت رشد</div>
|
<Typography variant='body2' color='text.secondary' className='mbe-1'>
|
||||||
<div className='w-full bg-slate-600 rounded-full h-2 mb-1'>
|
{t('progressGrowth')}
|
||||||
<div
|
</Typography>
|
||||||
className='bg-green-500 h-2 rounded-full transition-all'
|
<Box className='w-full rounded-full overflow-hidden bg-action-hover mbe-1' sx={{ height: 8 }}>
|
||||||
style={{ width: `${(plant.height / MAX_HEIGHT) * 100}%` }}
|
<Box
|
||||||
|
className='bg-success rounded-full h-full transition-all'
|
||||||
|
sx={{ width: `${(plant.height / MAX_HEIGHT) * 100}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<div className='text-green-400 font-semibold'>
|
<Typography variant='body2' color='success.main' fontWeight='bold'>
|
||||||
{Math.round((plant.height / MAX_HEIGHT) * 100)}%
|
{Math.round((plant.height / MAX_HEIGHT) * 100)}%
|
||||||
</div>
|
</Typography>
|
||||||
</div>
|
</Card>
|
||||||
|
</Grid>
|
||||||
<div className='bg-slate-700/60 rounded-xl p-4 border border-slate-600'>
|
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
|
||||||
<div className='text-slate-400 mb-1'>وضعیت نور</div>
|
<Card variant='outlined' className='p-4'>
|
||||||
<div className='w-full bg-slate-600 rounded-full h-2 mb-1'>
|
<Typography variant='body2' color='text.secondary' className='mbe-1'>
|
||||||
<div
|
{t('lightStatus')}
|
||||||
className='bg-yellow-400 h-2 rounded-full transition-all'
|
</Typography>
|
||||||
style={{ width: `${env.light}%` }}
|
<Box className='w-full rounded-full overflow-hidden bg-action-hover mbe-1' sx={{ height: 8 }}>
|
||||||
|
<Box
|
||||||
|
className='bg-warning rounded-full h-full transition-all'
|
||||||
|
sx={{ width: `${env.light}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<div className='text-yellow-400 font-semibold'>{env.light}%</div>
|
<Typography variant='body2' color='warning.main' fontWeight='bold'>
|
||||||
</div>
|
{env.light}%
|
||||||
|
</Typography>
|
||||||
<div className='bg-slate-700/60 rounded-xl p-4 border border-slate-600'>
|
</Card>
|
||||||
<div className='text-slate-400 mb-1'>وضعیت آب</div>
|
</Grid>
|
||||||
<div className='w-full bg-slate-600 rounded-full h-2 mb-1'>
|
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
|
||||||
<div
|
<Card variant='outlined' className='p-4'>
|
||||||
className='bg-blue-400 h-2 rounded-full transition-all'
|
<Typography variant='body2' color='text.secondary' className='mbe-1'>
|
||||||
style={{ width: `${env.water}%` }}
|
{t('waterStatus')}
|
||||||
|
</Typography>
|
||||||
|
<Box className='w-full rounded-full overflow-hidden bg-action-hover mbe-1' sx={{ height: 8 }}>
|
||||||
|
<Box
|
||||||
|
className='bg-info rounded-full h-full transition-all'
|
||||||
|
sx={{ width: `${env.water}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<div className='text-blue-400 font-semibold'>{env.water}%</div>
|
<Typography variant='body2' color='info.main' fontWeight='bold'>
|
||||||
</div>
|
{env.water}%
|
||||||
|
</Typography>
|
||||||
<div className='bg-slate-700/60 rounded-xl p-4 border border-slate-600'>
|
</Card>
|
||||||
<div className='text-slate-400 mb-1'>محصول دهی</div>
|
</Grid>
|
||||||
<div className='w-full bg-slate-600 rounded-full h-2 mb-1'>
|
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
|
||||||
<div
|
<Card variant='outlined' className='p-4'>
|
||||||
className='bg-orange-400 h-2 rounded-full transition-all'
|
<Typography variant='body2' color='text.secondary' className='mbe-1'>
|
||||||
style={{ width: `${(plant.yield / MAX_YIELD) * 100}%` }}
|
{t('yieldStatus')}
|
||||||
|
</Typography>
|
||||||
|
<Box className='w-full rounded-full overflow-hidden bg-action-hover mbe-1' sx={{ height: 8 }}>
|
||||||
|
<Box
|
||||||
|
className='bg-warning rounded-full h-full transition-all'
|
||||||
|
sx={{ width: `${(plant.yield / MAX_YIELD) * 100}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<div className='flex justify-between items-center'>
|
<Box className='flex justify-between items-center'>
|
||||||
<span className='text-orange-400 font-semibold'>{plant.yield.toFixed(1)}g</span>
|
<Typography variant='body2' color='warning.main' fontWeight='bold'>
|
||||||
<span className='text-violet-400 text-xs'>{plant.yieldRate.toFixed(3)} g/s</span>
|
{plant.yield.toFixed(1)}g
|
||||||
</div>
|
</Typography>
|
||||||
</div>
|
<Typography variant='caption' color='secondary.main'>
|
||||||
</div>
|
{plant.yieldRate.toFixed(3)} g/s
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
{/* Description */}
|
<Card variant='outlined' className='mt-6 p-4'>
|
||||||
<div className='mt-6 bg-slate-700/30 border border-slate-600/50 rounded-xl p-4 text-xs text-slate-400 leading-6'>
|
<Typography variant='body2' color='text.secondary' className='leading-6'>
|
||||||
<p>
|
{t('description')}
|
||||||
این شبیهساز رشد گیاه را بر اساس سرعت پایه، میزان نور خورشید و آب دریافتی
|
</Typography>
|
||||||
محاسبه میکند. هر برگ به صورت تدریجی روی ساقه ظاهر شده و با حرکت طبیعی
|
</Card>
|
||||||
در باد نمایش داده میشود. <strong className='text-slate-300'>محصولدهی (g)</strong> پس از ۲۰٪ رشد شروع شده
|
</CardContent>
|
||||||
و با تعداد برگ، نور و آب شتاب میگیرد. <strong className='text-slate-300'>سرعت محصول (g/s)</strong> نشاندهنده
|
</Card>
|
||||||
نرخ لحظهای تولید است. نمودار تغییرات همه شاخصها را در طول زمان ثبت میکند.
|
</Grid>
|
||||||
</p>
|
</Grid>
|
||||||
</div>
|
</Box>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user