Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/views/eat-clean/components/DayCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import type { DayPlan } from '../data/meals'

defineProps<{
dayData: DayPlan
}>()

defineEmits<{
(e: 'refresh-day'): void
(e: 'refresh-meal', mealType: 'breakfast' | 'lunch' | 'dinner'): void
(e: 'view-recipe', mealId: string): void
}>()
</script>

<template>
<div class="bg-bg-surface border border-border-default hover:border-accent-coral transition-all duration-300 p-6 group">
<div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-display font-bold text-white tracking-tight">{{ dayData.day }}</h3>
<button
@click="$emit('refresh-day')"
class="opacity-0 group-hover:opacity-100 transition-opacity p-2 hover:bg-bg-elevated text-text-secondary hover:text-white"
title="Làm mới cả ngày"
>
<Icon icon="lucide:refresh-cw" class="size-4" />
</button>
</div>

<div class="space-y-4">
<div
v-for="type in (['breakfast', 'lunch', 'dinner'] as const)"
:key="type"
class="p-4 bg-bg-deep border border-border-default hover:border-accent-amber/50 transition-colors relative group/meal cursor-pointer"
@click="$emit('view-recipe', dayData[type].id)"
>
<div class="text-[10px] uppercase tracking-widest text-text-dim mb-1 font-display">
{{ type === 'breakfast' ? 'Sáng' : type === 'lunch' ? 'Trưa' : 'Tối' }}
</div>
<div class="text-sm font-medium text-text-primary pr-8 leading-snug">
{{ dayData[type].name }}
</div>
<div class="text-[10px] text-text-secondary mt-1 flex items-center gap-1">
<Icon icon="lucide:flame" class="size-3 text-accent-amber" />
{{ dayData[type].calories }} kcal
</div>

<button
@click.stop="$emit('refresh-meal', type)"
class="absolute top-4 right-4 opacity-0 group-hover/meal:opacity-100 transition-opacity p-1.5 hover:bg-bg-elevated rounded text-text-dim hover:text-accent-amber"
title="Đổi món này"
>
<Icon icon="lucide:refresh-cw" class="size-3" />
</button>
</div>
</div>
</div>
</template>
124 changes: 124 additions & 0 deletions src/views/eat-clean/components/RecipeDetail.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<script setup lang="ts">
import { computed } from 'vue'
import { Icon } from '@iconify/vue'
import { breakfasts, lunches, dinners } from '../data/meals'
import { recipes } from '../data/recipes'

const props = defineProps<{
id: string
}>()

defineEmits<{
(e: 'close'): void
}>()

const allMeals = [...breakfasts, ...lunches, ...dinners]

const meal = computed(() => {
return allMeals.find(m => m.id === props.id)
})

const recipe = computed(() => {
return recipes[props.id] || null
})

const defaultRecipe = {
ingredients: [
'100g protein (ức gà/cá/bò) sạch',
'150g rau xanh (súp lơ/xà lách/cải)',
'1/2 bát tinh bột chậm (cơm lứt/khoai lang)',
'Gia vị healthy (dầu oliu, tiêu, chanh)'
],
steps: [
'Sơ chế sạch các nguyên liệu, rau củ cắt miếng vừa ăn.',
'Chế biến protein (áp chảo/luộc/hấp) với chút dầu oliu và gia vị nhạt.',
'Rau củ luộc chín hoặc trộn salad với nước sốt mè rang.',
'Bày biện ra đĩa thật đẹp và thưởng thức thôi! ✨'
]
}
</script>

<template>
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-bg-deep/80 backdrop-blur-md animate-fade-in">
<div class="bg-bg-surface border border-border-default w-full max-w-3xl max-h-[90vh] overflow-y-auto relative animate-fade-up">
<!-- Close Button -->
<button
@click="$emit('close')"
class="absolute top-4 right-4 p-2 hover:bg-bg-elevated text-text-secondary hover:text-white transition-colors z-10"
>
<Icon icon="lucide:x" class="size-6" />
</button>

<div class="p-8 sm:p-12">
<div class="flex flex-wrap items-center gap-3 mb-6">
<span class="px-3 py-1 bg-accent-coral/10 text-accent-coral text-[10px] font-display uppercase tracking-widest border border-accent-coral/20">
Chi tiết Công thức
</span>
<span class="px-3 py-1 bg-accent-amber/10 text-accent-amber text-[10px] font-display uppercase tracking-widest border border-accent-amber/20 flex items-center gap-1">
<Icon icon="lucide:flame" class="size-3" /> {{ meal?.calories || 0 }} kcal
</span>
</div>

<h2 class="text-3xl sm:text-4xl font-display font-bold text-white mb-8 tracking-tight">
{{ meal?.name || 'Món ăn không tìm thấy 🤔' }}
</h2>

<div v-if="meal" class="grid grid-cols-1 md:grid-cols-2 gap-12">
<!-- Ingredients -->
<div>
<h3 class="text-xl font-display font-bold text-white mb-6 flex items-center gap-2">
<Icon icon="lucide:shopping-basket" class="size-5 text-accent-amber" />
Nguyên Liệu
</h3>
<ul class="space-y-4">
<li
v-for="(item, idx) in (recipe?.ingredients || defaultRecipe.ingredients)"
:key="idx"
class="flex gap-3 text-text-secondary text-sm p-4 bg-bg-deep border border-border-default hover:border-accent-coral/30 transition-colors"
>
<Icon icon="lucide:check-circle" class="size-4 text-accent-coral shrink-0 mt-0.5" />
<span>{{ item }}</span>
</li>
</ul>
</div>

<!-- Steps -->
<div>
<h3 class="text-xl font-display font-bold text-white mb-6 flex items-center gap-2">
<Icon icon="lucide:chef-hat" class="size-5 text-accent-sky" />
Cách Chế Biến
</h3>
<div class="space-y-6">
<div
v-for="(step, idx) in (recipe?.steps || defaultRecipe.steps)"
:key="idx"
class="flex gap-4"
>
<div class="flex flex-col items-center">
<div class="size-7 bg-bg-elevated border border-border-default flex items-center justify-center text-white font-display font-bold text-xs shrink-0">
{{ idx + 1 }}
</div>
<div v-if="idx !== (recipe?.steps?.length || defaultRecipe.steps.length) - 1" class="w-px h-full bg-border-default my-2"></div>
</div>
<div class="pb-2">
<p class="text-text-secondary text-sm leading-relaxed">{{ step }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<style scoped>
.animate-fade-in {
animation: fade-in 0.3s ease-out both;
}

@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
</style>
68 changes: 68 additions & 0 deletions src/views/eat-clean/data/meals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
export interface Meal {
id: string
name: string
calories: number
}

export interface DayPlan {
day: string
breakfast: Meal
lunch: Meal
dinner: Meal
}

export const breakfasts: Meal[] = [
{ id: 'b1', name: 'Yến mạch ngâm sữa hạt (Overnight Oats) 🥣', calories: 300 },
{ id: 'b2', name: 'Bánh mì đen & Trứng ốp la 🥚🍞', calories: 280 },
{ id: 'b3', name: 'Sinh tố xanh (Rau bina, bơ, chuối) 🥑', calories: 250 },
{ id: 'b4', name: 'Sữa chua không đường & Trái cây tươi 🍓', calories: 200 },
{ id: 'b5', name: 'Khoai lang luộc & Bơ đậu phộng 🍠', calories: 260 },
{ id: 'b6', name: 'Trứng luộc & Táo xanh 🍏', calories: 150 },
{ id: 'b7', name: 'Pancake yến mạch chuối 🥞', calories: 320 },
{ id: 'b8', name: 'Bún lứt trộn ức gà xé 🍜', calories: 350 },
{ id: 'b9', name: 'Smoothie bowl ngũ cốc trái cây 🥥', calories: 280 }
]

export const lunches: Meal[] = [
{ id: 'l1', name: 'Cơm gạo lứt & Ức gà nướng tiêu 🍗', calories: 450 },
{ id: 'l2', name: 'Salad cá ngừ ngâm nước sốt chanh dây 🥗', calories: 380 },
{ id: 'l3', name: 'Bún lứt xào thịt bò nạc & cải thìa 🍜', calories: 420 },
{ id: 'l4', name: 'Cơm lứt & Cá hồi áp chảo măng tây 🍣', calories: 500 },
{ id: 'l5', name: 'Mì rau củ xốt cà chua thịt nạc băm 🍝', calories: 400 },
{ id: 'l6', name: 'Nui lứt lườn cá hồi xốt Pesto 🥣', calories: 460 },
{ id: 'l7', name: 'Salad ức gà áp chảo sốt mè rang 🍗', calories: 350 },
{ id: 'l8', name: 'Cơm quinoa & Đậu hũ xốt nấm 🍄', calories: 380 },
{ id: 'l9', name: 'Cuốn tôm thịt luộc & bánh tráng lứt 🍤', calories: 320 },
{ id: 'l10', name: 'Cơm lứt & Thịt heo nạc xào giá hẹ 🍛', calories: 430 }
]

export const dinners: Meal[] = [
{ id: 'd1', name: 'Salad ức gà xé phay & bắp ngọt 🌽', calories: 280 },
{ id: 'd2', name: 'Bò né nạc áp chảo kèm salad giấm 🥩', calories: 350 },
{ id: 'd3', name: 'Tôm hấp & Canh bí đỏ thịt băm 🦐', calories: 300 },
{ id: 'd4', name: 'Ức gà cuộn rau củ hấp 🥕', calories: 250 },
{ id: 'd5', name: 'Súp lơ xanh luộc & Cá lóc hấp hành 🐟', calories: 280 },
{ id: 'd6', name: 'Salad dưa leo cà chua & Trứng luộc 🍅', calories: 200 },
{ id: 'd7', name: 'Đậu hũ non sốt cà chua & Nấm kim châm 🍲', calories: 220 },
{ id: 'd8', name: 'Canh rong biển đậu non & thịt băm nạc 🌿', calories: 150 },
{ id: 'd9', name: 'Bắp bắp luộc & Thịt băm xào nấm mộc nhĩ 🌽', calories: 300 }
]

export const getRandomItem = (array: Meal[], excludeId: string | null = null): Meal => {
let filtered = excludeId ? array.filter(item => item.id !== excludeId) : array
if (filtered.length === 0) filtered = array
const randomIndex = Math.floor(Math.random() * filtered.length)
return filtered[randomIndex]!
}

export const generateWeeklyMenu = (): DayPlan[] => {
const daysOfWeek = ['Thứ 2', 'Thứ 3', 'Thứ 4', 'Thứ 5', 'Thứ 6', 'Thứ 7', 'Chủ Nhật']
return daysOfWeek.map((day) => {
return {
day,
breakfast: getRandomItem(breakfasts),
lunch: getRandomItem(lunches),
dinner: getRandomItem(dinners),
}
})
}
56 changes: 56 additions & 0 deletions src/views/eat-clean/data/recipes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export interface Recipe {
ingredients: string[]
steps: string[]
}

export interface RecipeMap {
[key: string]: Recipe
}

export const recipes: RecipeMap = {
'b1': {
ingredients: [
'40g yến mạch cán dẹt',
'100ml sữa tươi/sữa hạt không đường',
'1 muỗng hạt chia',
'Trái cây tùy thích (chuối, dâu tây, việt quất)',
'1/2 muỗng mật ong (tùy chọn)'
],
steps: [
'Cho yến mạch và hạt chia vào một chiếc hũ thủy tinh sạch.',
'Đổ sữa tươi hoặc sữa hạt vào hũ sao cho ngập phần yến mạch.',
'Thêm một chút mật ong nếu thích ăn ngọt, sau đó khuấy đều.',
'Đậy nắp kín, cho vào ngăn mát tủ lạnh ngâm qua đêm (ít nhất 4 tiếng).',
'Sáng hôm sau, lấy ra thêm trái cây cắt nhỏ lên trên và thưởng thức. Cực kỳ nhanh gọn!'
]
},
'l1': {
ingredients: [
'100g ức gà tươi',
'1 bát cơm gạo lứt (chín)',
'1/2 cây súp lơ xanh',
'1 muỗng cà phê dầu oliu',
'Tiêu chanh, hạt nêm diet, xì dầu'
],
steps: [
'Ức gà rửa sạch, khía mặt để thấm gia vị. Ướp với hạt nêm diet và tiêu chanh trong 15 phút.',
'Súp lơ xanh cắt miếng vừa ăn, luộc chín tới.',
'Làm nóng chảo, cho chút dầu oliu, áp chảo ức gà mỗi mặt 3-4 phút đến khi chín vàng.',
'Thái ức gà thành lát. Bày ra đĩa cùng cơm gạo lứt và súp lơ. Ngon vô cùng!'
]
},
'd1': {
ingredients: [
'80g ức gà luộc xé phay',
'1/2 bắp ngô ngọt luộc tách hạt',
'Xà lách, cà chua bi',
'Dầu oliu, nước cốt chanh, một chút muối hồng'
],
steps: [
'Rửa sạch xà lách và cà chua bi, cắt miếng vừa ăn.',
'Pha nước xốt: 1 muỗng dầu oliu + 1 muỗng nước cốt chanh + xíu muối hồng.',
'Cho rau củ, ngô ngọt và ức gà xé vào tô lớn.',
'Rưới nước xốt lên trên trộn đều. Ăn tối nhẹ bụng nhưng vẫn đủ chất cho ngày dài năng động!'
]
}
}
Loading
Loading