Kiosk Practice App for Seniors โ ํ๋ฒ๊ฑฐ / ์นดํ / ์ํ๊ด / ์๋น
- ์ด๊ธฐ ์ ์ ํ๋กฌํํธ
- ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ์
- ๋๋ฉ์ธ(์ ์ข )๋ณ ์๋๋ฆฌ์ค
- ์ปดํฌ๋ํธ ์ฌ์์
- ๋ฐ์ดํฐ ๊ตฌ์กฐ
- ์ ๋ฌด ํ๋ฆ & ๋ชจ๋ ๋ก์ง
- ๋์์ธ ์์คํ & ์ ๊ทผ์ฑ
- ํ ์คํธ ์๋๋ฆฌ์ค
- ์ค์น & ์คํ
- ๊ฐ์ ํ๋กฌํํธ ์์
- ๋ก๋๋งต
- ํ๋ก์ ํธ ๋งฅ๋ฝ
๋ ธ์ธ๋ถ๋ค์ ์ํ ํค์ค์คํฌ ์ฐ์ต ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด์ฃผ์ธ์.
์ฃผ์ ๊ธฐ๋ฅ ์ฐ์ต ๋ชจ๋: ๋จ๊ณ๋ณ ์๋ด๊ฐ ์๋ ๊ฐ์ด๋ ๋ชจ๋
์ค์ ๋ชจ๋: ๋๋ค ๋ฏธ์ ๊ธฐ๋ฐ ์ฃผ๋ฌธ
ํ์ต ๊ธฐ๋ก: localStorage์ ์ค์ ๊ฒฐ๊ณผ ์ ์ฅ(์ฑ๊ณต/์คํจ, ์ฃผ๋ฌธ๋ด์ญ, ์๊ฐ)
๋์๋ง: ์ฌ์ฉ ๋ฐฉ๋ฒ/ํ/FAQ
์ ์ข (๋๋ฉ์ธ) ํ๋ฒ๊ฑฐ, ์นดํ, ์ํ๊ด, ์๋น(์ผ๋ฐ ํ์ยท๋ฐฑ๋ฐ)
์ ์ข ๋ง๋ค ์ค์ ํ๋ฆ์ด ๋ค๋ฆ
๋์์ธ ์๊ตฌ์ฌํญ ๋ชจ๋ฐ์ผ ์ต์ ํ(max-w-md)
ํฐ ๊ธ์จ, ํฐ ๋ฒํผ, ๋ช ํํ ์์ ๋๋น
Material Design ๋๋ + ๋ชจ์ ๊ฐ์ด๋
์์: ์ฐ์ต(ํ๋), ์ค์ (์ด๋ก), ์ ์ข ๋ณ ํ ๋ง
๊ธฐ์ ์คํ React + TypeScript + Vite
Tailwind CSS
shadcn/ui, lucide-react
motion/react (์ ๋๋ฉ์ด์ )
localStorage (ํ์ต ๊ธฐ๋ก ์ ์ฅ)
yaml ์ฝ๋ ๋ณต์ฌ
| ๊ตฌ๋ถ | ์ค๋ช |
|---|---|
| ๋ชฉํ ์ฌ์ฉ์ | 60๋ ์ด์, ํค์ค์คํฌ์ ์ต์ํ์ง ์์ ๋ ธ์ธ |
| ํต์ฌ ๊ฐ์น | ์์ ํ ์ฐ์ต ํ๊ฒฝ, ๋จ๊ณ๋ณ ํ์ต, ์ค์ ๋ฏธ์ , ํ์ต ํผ๋๋ฐฑ |
| ์ ๊ทผ์ฑ | ํฐ ํฐํธ, ํฐ ํฐ์น ์์ญ, ์์ ๋๋น, ์์ฑ/์๊ฐ ํผ๋๋ฐฑ |
- ์นดํ ๊ณ ๋ฆฌ: ๋ฒ๊ฑฐ / ์ฌ์ด๋ / ์๋ฃ
- ์ต์ : ๋จํ/์ธํธ, ์ฌ์ด์ฆ, ์ผ์/๋นจ๋
- ๋ฏธ์ ์์: "์์ฐ๋ฒ๊ฑฐ ์ธํธ 1๊ฐ, ์ฝ๋ผ ๋ผ์ง 1์"
- ์นดํ ๊ณ ๋ฆฌ: ์ปคํผ / ์ฐจ / ๋์ ํธ
- ์ต์ : HOT/ICE, ์ท์ถ๊ฐ, ๋น๋, ์ผ์๋, ํ ์ดํฌ์์
- ๋ฏธ์ ์์: "์๋ฉ๋ฆฌ์นด๋ ธ ICE ํจ 2์, ๋ฒ ์ด๊ธ 1๊ฐ(ํฌ๋ฆผ์น์ฆ)"
- ๋จ๊ณ: ์ํ ์ ํ โ ์๊ฐ/์ข์ โ ํ์ฝ/์๋ฃ โ ๊ฒฐ์
- ์ต์ : 2D/3D, ์ข์ ๊ตฌ์ญ, ํ์ฝ ์ฌ์ด์ฆ
- ๋ฏธ์ ์์: "์ค๋ 19:10 '๊ตญ๋ฏผ์ํ' 2๋งค, ํ์ฝM, ์ฝ๋ผ 1์"
- ์นดํ ๊ณ ๋ฆฌ: ๋ฐฑ๋ฐ / ์ฐ๊ฐ / ๋ฉด
- ์ต์ : ๋งต๊ธฐ, ๊ณต๊ธฐ๋ฐฅ, ๋ฐ์ฐฌ ์ถ๊ฐ
- ๋ฏธ์ ์์: "๊น์น์ฐ๊ฐ 2(๋งต1 ์1), ๊ณต๊ธฐ๋ฐฅ 1 ์ถ๊ฐ"
- ์ ์ฒด ์ํ/๋ชจ๋ ๊ด๋ฆฌ
type AppMode = 'menu' | 'practice' | 'real';
type Domain = 'burger' | 'cafe' | 'cinema' | 'restaurant';
mode, domain, helpOpen, historyOpen ์ํ ํฌํจ
๋ชจ๋ ์ ํ ๋ฐ ์กฐ๊ฑด๋ถ ๋ ๋
2๏ธโฃ MainMenu.tsx
Props ์ค๋ช
onSelectDomain(domain) ์
์ข
์ ํ
onSelectMode(mode) ์ฐ์ต / ์ค์ ๋ชจ๋ ์ ํ
onOpenHelp() ๋์๋ง ๋ค์ด์ผ๋ก๊ทธ ์ด๊ธฐ
onOpenHistory() ํ์ต ๊ธฐ๋ก ๋ณด๊ธฐ
์
์ข
์นด๋ (์์ด์ฝ + ์ค๋ช
)
๋ชจ๋ ์ ํ ๋ฒํผ
๊ธฐ๋ก / ๋์๋ง ๋ฒํผ
3๏ธโฃ KioskSimulator.tsx
Props ์ค๋ช
domain ํ์ฌ ์
์ข
isPracticeMode ์ฐ์ต๋ชจ๋ ์ฌ๋ถ
onExit() ํ์ผ๋ก ๋์๊ฐ๊ธฐ
๋ด๋ถ์์ ์
์ข
๋ณ ์ปดํฌ๋ํธ ๋ก๋
/domains/Burger/, /domains/Cafe/, /domains/Cinema/, /domains/Restaurant/
4๏ธโฃ ์
์ข
๋ณ ํ์ ์ปดํฌ๋ํธ ์์
Burger
BurgerCategoryTabs.tsx
BurgerMenuList.tsx
BurgerCart.tsx
BurgerCheckout.tsx
5๏ธโฃ LearningHistory.tsx
localStorage ๊ธฐ๋ฐ ๊ธฐ๋ก / ํต๊ณ
์ต๋ 50๊ฑด, ์ต์ ์
์ฑ๊ณต๋ฅ , ์๋ ํ์, ์ต๊ทผ ๋ฏธ์
ํ์
6๏ธโฃ HelpDialog.tsx
์ฌ์ฉ๋ฒ / ํ / FAQ
์
์ข
๋ณ ์ฐจ์ด์ ์กฐ์ ๋ฐฉ๋ฒ ์ค๋ช
์์ ํ
๋ง: blue-600
๐ ๋ฐ์ดํฐ ๊ตฌ์กฐ
ts
์ฝ๋ ๋ณต์ฌ
interface MenuItem {
id: string;
name: string;
price: number;
category: string;
options?: Record<string, string | number | boolean>;
}
interface CartItem extends MenuItem {
quantity: number;
}
interface Mission {
domain: Domain;
text: string;
required: { name: string; quantity: number }[];
}
interface HistoryRecord {
id: string;
date: string;
mission: string;
domain: Domain;
success: boolean;
userOrder: { name: string; quantity: number }[];
timestamp: number;
}
localStorage ํค: kioskLearningHistory
์ ์ฑ
: ์ต์ ์์ผ๋ก ์ต๋ 50๊ฑด ์ ์ฅ
๐ ์
๋ฌด ํ๋ฆ & ๋ชจ๋ ๋ก์ง
๐งญ ์ฐ์ต ๋ชจ๋
์์ โ ์นดํ
๊ณ ๋ฆฌ โ ๋ฉ๋ด โ ์ฅ๋ฐ๊ตฌ๋ โ ๊ฒฐ์
๊ฐ ๋จ๊ณ์์ ๋ฒํผ์ ์ ๋๋ฉ์ด์
๊ฐ์ด๋
tsx
์ฝ๋ ๋ณต์ฌ
<motion.button
animate={{ scale: [1, 1.05, 1] }}
transition={{ repeat: Infinity, duration: 1.5 }}
/>
๐ฏ ์ค์ ๋ชจ๋
์
์ข
๋ณ ๋๋ค ๋ฏธ์
์์ฑ
checkMission() ์ผ๋ก ๊ฒฐ๊ณผ ํ์
saveToHistory() ๋ก ์ ์ฅ
์ฑ๊ณต/์คํจ ํ๋ฉด ํ์
๐จ ๋์์ธ ์์คํ
& ์ ๊ทผ์ฑ
ํญ๋ชฉ ๊ฐ์ด๋
ํฐํธ text-xl, text-2xl ์ค์ฌ
๋ฒํผ ํฌ๊ธฐ ์ต์ h-14, ์ค์ ๋ฒํผ h-16
์์ ์ฐ์ต: ํ๋ / ์ค์ : ์ด๋ก / ์
์ข
๋ณ ํ
๋ง
ํผ๋๋ฐฑ hover, active, disabled ๋ช
ํ
๋ชจ์
fade, slide, pulse (๋จ์ ํจ๊ณผ๋ง)
์ ๊ทผ์ฑ aria-label, ํฌ์ปค์ค ์ด๋, ๊ณ ๋๋น ์์
๐งช ํ
์คํธ ์๋๋ฆฌ์ค
๊ณตํต
์
์ข
์ ํ โ ์ฐ์ต ๋ชจ๋ ๋จ๊ณ๋ณ ์งํ
์ค์ ๋ชจ๋ โ ๋๋ค ๋ฏธ์
์ฃผ๋ฌธ โ ์ฑ๊ณต ํ์ธ
์๋ชป๋ ์ฃผ๋ฌธ โ ์คํจ ๊ธฐ๋ก ์ ์ฅ
ํ์ต ๊ธฐ๋ก ํต๊ณ ํ์ธ
50๊ฑด ์ด๊ณผ ์ ์ค๋๋ ๊ธฐ๋ก ์ญ์
์
์ข
๋ณ ํฌ์ธํธ
ํ๋ฒ๊ฑฐ: ์ธํธ/์ฌ์ด์ฆ/๋นจ๋/์ผ์ ์ต์
์นดํ: ICE/HOT, ์ท์ถ๊ฐ, ๋น๋ ์กฐ์
์ํ๊ด: ์๊ฐํ, ์ข์, ํ์ฝ
์๋น: ๋งต๊ธฐ/๊ณต๊ธฐ๋ฐฅ ์ถ๊ฐ
โ๏ธ ์ค์น & ์คํ
bash
์ฝ๋ ๋ณต์ฌ
# 1) ์ค์น
npm install
# 2) ๊ฐ๋ฐ ์๋ฒ ์คํ
npm run dev
# 3) ๋น๋
npm run build
npm run preview
๊ถ์ฅ ํ๊ฒฝ
Node 18+
์ต์ Chrome ๋๋ Android WebView
๋ฐฐํฌ: Vercel / Netlify / GitHub Pages (PWA ๊ถ์ฅ)
๐ ๊ฐ์ ํ๋กฌํํธ ์์
๊ธฐ๋ฅ ๊ฐ์
์์ฑ ์๋ด: Web Speech API๋ก ๊ฐ ๋จ๊ณ ์๋ด ์ฝ์ด์ฃผ๊ธฐ
๋ค๊ตญ์ด ์ง์: ํ๊ตญ์ด/์์ด/์ค๊ตญ์ด/์ผ๋ณธ์ด
๋์ด๋ ์ถ๊ฐ: ์ฌ์(1~2), ๋ณดํต(2~3), ์ด๋ ค์(3~5)
UX ๊ฐ์
๋คํฌ๋ชจ๋ ์ง์
์ ๋๋ฉ์ด์
ON/OFF ์ค์
ํค๋ณด๋ ์ ๊ทผ์ฑ ๊ฐํ
๋ฐ์ดํฐ ๊ด๋ฆฌ
์งํ์ํฉ ์ ์ฅ(๋ง์ง๋ง ๋ชจ๋/๋จ๊ณ ๋ณต์)
๊ธฐ๋ก ๋ด๋ณด๋ด๊ธฐ(CSV/์ธ์์ฉ)
์ถ๊ฐ ๋ชจ๋
์ํ ๋ชจ๋: ์ฐ์ 5๊ฐ ๋ฏธ์
, ์ ํ์๊ฐ, ์ ์
์์ ์ฃผ๋ฌธ ๋ชจ๋: ๋ฏธ์
์์ด ์์ ์ฒดํ
๐ ๋ก๋๋งต
๋ฒ์ ์ฃผ์ ๋ด์ฉ
v1.0 4๊ฐ ์
์ข
๊ธฐ๋ณธ ์๋๋ฆฌ์ค + ์ฐ์ต/์ค์ + ๊ธฐ๋ก
v1.1 ์์ฑ ์๋ด / ๋์ด๋ / ๋คํฌ๋ชจ๋
v1.2 ์ํ ๋ชจ๋ / ๋ฐ์ดํฐ ๋ด๋ณด๋ด๊ธฐ
v2.0 PWA ๋ฐฐํฌ (์คํ๋์ / ์์ด์ฝ / ์คํ๋ผ์ธ ์บ์)
v2.1 Android ํจํค์ง (Expo ๋๋ Capacitor)
๐ ํ๋ก์ ํธ ๋งฅ๋ฝ
๋ณธ ์ ํ๋ฆฌ์ผ์ด์
์ ๋
ธ์ธ ๋์ ํค์ค์คํฌ ์๋ด ์ฑ์ผ๋ก์
ํ๋ฒ๊ฑฐ, ์นดํ, ์ํ๊ด, ์๋น ๋ฑ ์ค์ ํ๊ฒฝ์ ์๋๋ฆฌ์ค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก
๋
ธ์ธ ์ฌ์ฉ์์๊ฒ ์ค์ ํค์ค์คํฌ ์กฐ์ ์ฐ์ต๊ณผ ๋จ๊ณ๋ณ ํ์ต ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
์ฐ์ต ๋ชจ๋์ ์ค์ ๋ชจ๋๋ฅผ ๊ตฌ๋ถํ์ฌ ํ์ต์ ๋ถ๋ด์ ์ค์ด๊ณ ,
๊ธฐ๋ก/ํผ๋๋ฐฑ ๊ธฐ๋ฅ์ผ๋ก ํ์ต ์ฑ์ทจ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.