Compare commits
2 Commits
feat/meals
...
fix/meals-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad000c722e | ||
|
|
c0827f887f |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "laude",
|
"name": "laude",
|
||||||
"version": "0.5.8",
|
"version": "0.6.0",
|
||||||
"description": "Exercise reminder — Windows desktop app",
|
"description": "Exercise reminder — Windows desktop app",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"author": "AnRil",
|
"author": "AnRil",
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export const ru: Dict = {
|
|||||||
'meals.kicker': 'Режим питания',
|
'meals.kicker': 'Режим питания',
|
||||||
'meals.title': 'Питание',
|
'meals.title': 'Питание',
|
||||||
'meals.presets': 'Быстрое добавление',
|
'meals.presets': 'Быстрое добавление',
|
||||||
|
'meals.schedule': 'Расписание',
|
||||||
'meals.section.active': 'Активные · {n}',
|
'meals.section.active': 'Активные · {n}',
|
||||||
'meals.section.disabled': 'Выключенные · {n}',
|
'meals.section.disabled': 'Выключенные · {n}',
|
||||||
'meals.empty': 'Пока нет приёмов пищи — добавь первый или выбери пресет',
|
'meals.empty': 'Пока нет приёмов пищи — добавь первый или выбери пресет',
|
||||||
@@ -460,6 +461,7 @@ export const en: Dict = {
|
|||||||
'meals.kicker': 'Eating schedule',
|
'meals.kicker': 'Eating schedule',
|
||||||
'meals.title': 'Meals',
|
'meals.title': 'Meals',
|
||||||
'meals.presets': 'Quick add',
|
'meals.presets': 'Quick add',
|
||||||
|
'meals.schedule': 'Schedule',
|
||||||
'meals.section.active': 'Active · {n}',
|
'meals.section.active': 'Active · {n}',
|
||||||
'meals.section.disabled': 'Disabled · {n}',
|
'meals.section.disabled': 'Disabled · {n}',
|
||||||
'meals.empty': 'No meals yet — add one or pick a preset',
|
'meals.empty': 'No meals yet — add one or pick a preset',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Plus, ChevronRight, UtensilsCrossed } from 'lucide-react'
|
import { Plus, ChevronRight, UtensilsCrossed } from 'lucide-react'
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
import { useAppStore } from '../store/appStore'
|
import { useAppStore } from '../store/appStore'
|
||||||
import { MealEditor, type MealDraft } from '../components/MealEditor'
|
import { MealEditor, type MealDraft } from '../components/MealEditor'
|
||||||
import { Button } from '../components/ui/Button'
|
import { Button } from '../components/ui/Button'
|
||||||
@@ -27,8 +28,15 @@ export default function Meals(): JSX.Element {
|
|||||||
const [editing, setEditing] = useState<Meal | null>(null)
|
const [editing, setEditing] = useState<Meal | null>(null)
|
||||||
const { t } = useT()
|
const { t } = useT()
|
||||||
|
|
||||||
const enabled = meals.filter((m) => m.enabled)
|
// Единый список (включённые сверху). Важно: НЕ разбиваем на два <Card>, иначе
|
||||||
const disabled = meals.filter((m) => !m.enabled)
|
// при переключении строка переезжает между списками → её Switch
|
||||||
|
// размонтируется/монтируется заново, и анимация ползунка есть только при
|
||||||
|
// включении (mount с x:0→20), а при выключении нет. В одном keyed-списке
|
||||||
|
// компонент остаётся смонтированным → ползунок плавно ездит в обе стороны,
|
||||||
|
// а строка «переезжает» в свою группу через layout-анимацию.
|
||||||
|
const ordered = [...meals].sort((a, b) =>
|
||||||
|
a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1
|
||||||
|
)
|
||||||
|
|
||||||
async function addPreset(
|
async function addPreset(
|
||||||
preset: (typeof MEAL_PRESETS)[number]
|
preset: (typeof MEAL_PRESETS)[number]
|
||||||
@@ -82,46 +90,32 @@ export default function Meals(): JSX.Element {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{enabled.length > 0 && (
|
{meals.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<SectionHeader
|
<SectionHeader title={t('meals.schedule')} />
|
||||||
title={t('meals.section.active', { n: enabled.length })}
|
|
||||||
/>
|
|
||||||
<Card className="mb-6">
|
|
||||||
{enabled.map((m, i) => (
|
|
||||||
<MealRow
|
|
||||||
key={m.id}
|
|
||||||
meal={m}
|
|
||||||
last={i === enabled.length - 1}
|
|
||||||
meta={`${m.time} · ${daysLabel(m.days, t)}`}
|
|
||||||
onEdit={() => {
|
|
||||||
setEditing(m)
|
|
||||||
setEditorOpen(true)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{disabled.length > 0 && (
|
|
||||||
<>
|
|
||||||
<SectionHeader
|
|
||||||
title={t('meals.section.disabled', { n: disabled.length })}
|
|
||||||
/>
|
|
||||||
<Card>
|
<Card>
|
||||||
{disabled.map((m, i) => (
|
<AnimatePresence initial={false}>
|
||||||
<MealRow
|
{ordered.map((m, i) => (
|
||||||
|
<motion.div
|
||||||
key={m.id}
|
key={m.id}
|
||||||
|
layout
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.18, ease: 'easeOut' }}
|
||||||
|
>
|
||||||
|
<MealRow
|
||||||
meal={m}
|
meal={m}
|
||||||
last={i === disabled.length - 1}
|
last={i === ordered.length - 1}
|
||||||
meta={`${m.time} · ${daysLabel(m.days, t)}`}
|
meta={`${m.time} · ${daysLabel(m.days, t)}`}
|
||||||
onEdit={() => {
|
onEdit={() => {
|
||||||
setEditing(m)
|
setEditing(m)
|
||||||
setEditorOpen(true)
|
setEditorOpen(true)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -169,7 +163,7 @@ function MealRow({
|
|||||||
<Row last={last}>
|
<Row last={last}>
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
'w-9 h-9 rounded-lg grid place-items-center shrink-0',
|
'w-9 h-9 rounded-lg grid place-items-center shrink-0 transition-colors duration-200',
|
||||||
meal.enabled ? 'bg-accent text-white' : 'bg-text/15 text-text/45'
|
meal.enabled ? 'bg-accent text-white' : 'bg-text/15 text-text/45'
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user