# ๐Ÿ“˜ SAM MES ์†”๋ฃจ์…˜ ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ๋ผ์ธ ## ๐Ÿ“‹ ๋ชฉ์ฐจ 1. [์‹œ์ž‘ํ•˜๊ธฐ](#์‹œ์ž‘ํ•˜๊ธฐ) 2. [ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ](#ํ”„๋กœ์ ํŠธ-๊ตฌ์กฐ) 3. [์ฝ”๋”ฉ ๊ทœ์น™](#์ฝ”๋”ฉ-๊ทœ์น™) 4. [์ปดํฌ๋„ŒํŠธ ๊ฐœ๋ฐœ](#์ปดํฌ๋„ŒํŠธ-๊ฐœ๋ฐœ) 5. [์Šคํƒ€์ผ๋ง ๊ฐ€์ด๋“œ](#์Šคํƒ€์ผ๋ง-๊ฐ€์ด๋“œ) 6. [TypeScript ๊ฐ€์ด๋“œ](#typescript-๊ฐ€์ด๋“œ) 7. [์ƒํƒœ ๊ด€๋ฆฌ](#์ƒํƒœ-๊ด€๋ฆฌ) 8. [ํŒŒ์ผ ๋ช…๋ช… ๊ทœ์น™](#ํŒŒ์ผ-๋ช…๋ช…-๊ทœ์น™) 9. [Git ์›Œํฌํ”Œ๋กœ์šฐ](#git-์›Œํฌํ”Œ๋กœ์šฐ) 10. [ํ…Œ์ŠคํŒ…](#ํ…Œ์ŠคํŒ…) 11. [์„ฑ๋Šฅ ์ตœ์ ํ™”](#์„ฑ๋Šฅ-์ตœ์ ํ™”) 12. [์ ‘๊ทผ์„ฑ](#์ ‘๊ทผ์„ฑ) 13. [๋ณด์•ˆ](#๋ณด์•ˆ) 14. [๋ฌธ์„œํ™”](#๋ฌธ์„œํ™”) 15. [๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค](#๋ฒ ์ŠคํŠธ-ํ”„๋ž™ํ‹ฐ์Šค) --- ## ๐Ÿš€ ์‹œ์ž‘ํ•˜๊ธฐ ### **ํ”„๋กœ์ ํŠธ ๊ฐœ์š”** SAM MES๋Š” ์ค‘์†Œ ๋ฐ ์ค‘๊ฒฌ๊ธฐ์—…์„ ์œ„ํ•œ ์™„์ „ํ•œ ์ œ์กฐ์‹คํ–‰์‹œ์Šคํ…œ(MES) ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค. ### **ํ•ต์‹ฌ ๊ธฐ์ˆ ** - React 18+ (TypeScript) - Tailwind CSS v4.0 - shadcn/ui ์ปดํฌ๋„ŒํŠธ - Recharts (๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”) - Figma Make ํ”Œ๋žซํผ ### **๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •** 1. ํ”„๋กœ์ ํŠธ ํŒŒ์ผ ๊ตฌ์กฐ ํŒŒ์•… 2. `TECH_STACK.md` ๋ฌธ์„œ ์ฝ๊ธฐ 3. `DEVELOPMENT_ENVIRONMENT.md` ๋ฌธ์„œ ์ฝ๊ธฐ 4. ๊ธฐ์กด ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ ๋ถ„์„ --- ## ๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ### **๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ** ``` SAM-MES/ โ”œโ”€โ”€ App.tsx # ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—”ํŠธ๋ฆฌ โ”œโ”€โ”€ *.md # ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ๋“ค โ”œโ”€โ”€ components/ # React ์ปดํฌ๋„ŒํŠธ โ”‚ โ”œโ”€โ”€ [PageComponents].tsx # ํŽ˜์ด์ง€ ๋ ˆ๋ฒจ ์ปดํฌ๋„ŒํŠธ (30+) โ”‚ โ”œโ”€โ”€ ui/ # shadcn/ui ์ปดํฌ๋„ŒํŠธ (53๊ฐœ) โ”‚ โ”‚ โ”œโ”€โ”€ button.tsx โ”‚ โ”‚ โ”œโ”€โ”€ dialog.tsx โ”‚ โ”‚ โ”œโ”€โ”€ table.tsx โ”‚ โ”‚ โ””โ”€โ”€ ... โ”‚ โ””โ”€โ”€ figma/ # Figma ์ „์šฉ ์ปดํฌ๋„ŒํŠธ โ”‚ โ””โ”€โ”€ ImageWithFallback.tsx # ์ด๋ฏธ์ง€ ํด๋ฐฑ ์ฒ˜๋ฆฌ โ”œโ”€โ”€ styles/ โ”‚ โ””โ”€โ”€ globals.css # ๊ธ€๋กœ๋ฒŒ ์Šคํƒ€์ผ & ํ…Œ๋งˆ โ”œโ”€โ”€ guidelines/ โ”‚ โ””โ”€โ”€ Guidelines.md # ์ถ”๊ฐ€ ๊ฐ€์ด๋“œ๋ผ์ธ โ””โ”€โ”€ [scripts].js # ์œ ํ‹ธ๋ฆฌํ‹ฐ ์Šคํฌ๋ฆฝํŠธ ``` ### **์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฅ˜** #### **1. ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ** (`components/`) - `Dashboard.tsx` - CEO ๋Œ€์‹œ๋ณด๋“œ - `SalesManagement.tsx` - ํŒ๋งค๊ด€๋ฆฌ - `ProductionManagement.tsx` - ์ƒ์‚ฐ๊ด€๋ฆฌ (์ตœ๋Œ€ ๊ทœ๋ชจ) - `QualityManagement.tsx` - ํ’ˆ์งˆ๊ด€๋ฆฌ - `MaterialManagement.tsx` - ์ž์žฌ๊ด€๋ฆฌ - `ShippingManagement.tsx` - ์ถœ๊ณ ๊ด€๋ฆฌ - `HRManagement.tsx` - ์ธ์‚ฌ๊ด€๋ฆฌ - `ApprovalManagement.tsx` - ์ „์ž๊ฒฐ์žฌ - `AccountingManagement.tsx` - ํšŒ๊ณ„๊ด€๋ฆฌ - `MasterData.tsx` - ๊ธฐ์ค€์ •๋ณด - ๋“ฑ 30+ ์ปดํฌ๋„ŒํŠธ #### **2. UI ์ปดํฌ๋„ŒํŠธ** (`components/ui/`) - 53๊ฐœ์˜ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ UI ์ปดํฌ๋„ŒํŠธ - shadcn/ui ๊ธฐ๋ฐ˜ - **์ˆ˜์ • ๊ธˆ์ง€** (์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ์‹œ์—๋งŒ) #### **3. ํŠน์ˆ˜ ์ปดํฌ๋„ŒํŠธ** (`components/figma/`) - `ImageWithFallback.tsx` - ์ด๋ฏธ์ง€ ๋กœ๋”ฉ ๋ฐ ํด๋ฐฑ - **์ˆ˜์ • ๊ธˆ์ง€** (์‹œ์Šคํ…œ ๋ณดํ˜ธ ํŒŒ์ผ) --- ## ๐Ÿ“ ์ฝ”๋”ฉ ๊ทœ์น™ ### **1. ์ผ๋ฐ˜ ์›์น™** ```typescript // โœ… DO: ๋ช…ํ™•ํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ function calculateTotalPrice(items: Item[]): number { return items.reduce((sum, item) => sum + item.price, 0); } // โŒ DON'T: ๋ถˆ๋ช…ํ™•ํ•œ ๋ณ€์ˆ˜๋ช…๊ณผ ๋กœ์ง function calc(arr: any[]): any { return arr.reduce((a, b) => a + b.p, 0); } ``` ### **2. ์ฝ”๋“œ ํฌ๋งทํŒ…** ```typescript // ๋“ค์—ฌ์“ฐ๊ธฐ: 2 ์ŠคํŽ˜์ด์Šค // ์„ธ๋ฏธ์ฝœ๋ก : ์‚ฌ์šฉ // ๋”ฐ์˜ดํ‘œ: ์Œ๋”ฐ์˜ดํ‘œ ์šฐ์„  // ์ค„ ๊ธธ์ด: ์ตœ๋Œ€ 100์ž ๊ถŒ์žฅ export function MyComponent({ title, description }: Props) { const [isOpen, setIsOpen] = useState(false); return (

{title}

{description}

); } ``` ### **3. Import ์ˆœ์„œ** ```typescript // 1. React ๊ด€๋ จ import { useState, useEffect } from "react"; // 2. UI ์ปดํฌ๋„ŒํŠธ (shadcn/ui) import { Button } from "./ui/button"; import { Dialog, DialogContent } from "./ui/dialog"; import { Card, CardContent, CardHeader } from "./ui/card"; // 3. ์•„์ด์ฝ˜ import { Plus, Edit, Trash2 } from "lucide-react"; // 4. ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ import { format } from "date-fns"; import { ko } from "date-fns/locale"; import { BarChart, Bar } from "recharts"; // 5. ํƒ€์ž… ์ •์˜ interface Props { title: string; } ``` --- ## ๐ŸŽจ ์ปดํฌ๋„ŒํŠธ ๊ฐœ๋ฐœ ### **1. ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ** ```typescript import { useState } from "react"; import { Button } from "./ui/button"; // Props ํƒ€์ž… ์ •์˜ interface MyComponentProps { title: string; onSave?: () => void; } // ์ปดํฌ๋„ŒํŠธ ์„ ์–ธ export function MyComponent({ title, onSave }: MyComponentProps) { // 1. State ์„ ์–ธ const [data, setData] = useState([]); const [isLoading, setIsLoading] = useState(false); // 2. ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜ const handleClick = () => { console.log("Clicked"); onSave?.(); }; // 3. useEffect (ํ•„์š”์‹œ) useEffect(() => { // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ }, []); // 4. ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง if (isLoading) { return
Loading...
; } // 5. JSX ๋ฐ˜ํ™˜ return (

{title}

); } ``` ### **2. ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ํ…œํ”Œ๋ฆฟ** ```typescript import { useState } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; import { Button } from "./ui/button"; import { Plus, Download } from "lucide-react"; export function MyPage() { const [activeTab, setActiveTab] = useState("list"); return (
{/* ํ—ค๋” */}

ํŽ˜์ด์ง€ ์ œ๋ชฉ

ํŽ˜์ด์ง€ ์„ค๋ช…

{/* ๋ฉ”์ธ ์ฝ˜ํ…์ธ  */} ์„น์…˜ ์ œ๋ชฉ {/* ๋‚ด์šฉ */}
); } ``` ### **3. ๋ฐ˜์‘ํ˜• ์ปดํฌ๋„ŒํŠธ ํŒจํ„ด** ```typescript // ๋ฐ์Šคํฌํ†ฑ: ํ…Œ์ด๋ธ” / ๋ชจ๋ฐ”์ผ: ์นด๋“œ export function ResponsiveList({ items }: Props) { return ( <> {/* ๋ฐ์Šคํฌํ†ฑ ํ…Œ์ด๋ธ” (hidden on mobile) */}
์ด๋ฆ„ ์ƒํƒœ {items.map((item) => ( {item.name} {item.status} ))}
{/* ๋ชจ๋ฐ”์ผ ์นด๋“œ (visible on mobile) */}
{items.map((item) => (
{item.name}
{item.status}
))}
); } ``` --- ## ๐ŸŽจ ์Šคํƒ€์ผ๋ง ๊ฐ€์ด๋“œ ### **1. Tailwind CSS ๊ธฐ๋ณธ ์›์น™** #### **โœ… DO: Tailwind ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค ์‚ฌ์šฉ** ```typescript

์ œ๋ชฉ

``` #### **โŒ DON'T: ์ธ๋ผ์ธ ์Šคํƒ€์ผ ์‚ฌ์šฉ** ```typescript // ํ”ผํ•˜๊ธฐ
``` ### **2. ์ƒ‰์ƒ ์‚ฌ์šฉ** ```typescript // โœ… CSS ๋ณ€์ˆ˜ ์‚ฌ์šฉ (ํ…Œ๋งˆ ๋Œ€์‘) className="bg-primary text-primary-foreground" className="bg-card text-card-foreground" className="bg-destructive text-destructive-foreground" className="border-border" className="text-muted-foreground" // โŒ ํ•˜๋“œ์ฝ”๋”ฉ๋œ ์ƒ‰์ƒ (ํ”ผํ•˜๊ธฐ) className="bg-blue-500 text-white" ``` ### **3. ๋ฐ˜์‘ํ˜• ๋””์ž์ธ** ```typescript // Mobile First ์ ‘๊ทผ className=" p-4 // ๋ชจ๋ฐ”์ผ: 16px padding md:p-6 // ํƒœ๋ธ”๋ฆฟ: 24px padding lg:p-8 // ๋ฐ์Šคํฌํ†ฑ: 32px padding flex-col // ๋ชจ๋ฐ”์ผ: ์„ธ๋กœ ๋ฐฉํ–ฅ md:flex-row // ํƒœ๋ธ”๋ฆฟ+: ๊ฐ€๋กœ ๋ฐฉํ–ฅ text-sm // ๋ชจ๋ฐ”์ผ: ์ž‘์€ ํ…์ŠคํŠธ md:text-base // ํƒœ๋ธ”๋ฆฟ+: ๊ธฐ๋ณธ ํฌ๊ธฐ " ``` ### **4. ๊ฐ„๊ฒฉ ์‹œ์Šคํ…œ** ```typescript // Spacing Scale (4px ๋‹จ์œ„) className="p-2" // 8px className="p-4" // 16px className="p-6" // 24px className="p-8" // 32px className="gap-2" // 8px className="gap-4" // 16px className="gap-6" // 24px ``` ### **5. ํฐํŠธ ํฌ๊ธฐ/๊ตต๊ธฐ ์‚ฌ์šฉ ์ œํ•œ** ```typescript // โš ๏ธ ์ฃผ์˜: ํฐํŠธ ํฌ๊ธฐ, ๊ตต๊ธฐ, ๋ผ์ธ ๋†’์ด๋Š” ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ // globals.css์— ์ •์˜๋œ ๊ธฐ๋ณธ ์Šคํƒ€์ผ ํ™œ์šฉ // โŒ ํ”ผํ•˜๊ธฐ (ํŠน๋ณ„ํ•œ ์š”๊ตฌ์‚ฌํ•ญ ์—†์œผ๋ฉด) className="text-2xl font-bold leading-tight" // โœ… ๊ถŒ์žฅ (๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ)

์ œ๋ชฉ

// globals.css์˜ h1 ์Šคํƒ€์ผ ์ž๋™ ์ ์šฉ

๋‚ด์šฉ

// globals.css์˜ p ์Šคํƒ€์ผ ์ž๋™ ์ ์šฉ ``` --- ## ๐Ÿ“˜ TypeScript ๊ฐ€์ด๋“œ ### **1. ํƒ€์ž… ์ •์˜** #### **Interface vs Type** ```typescript // โœ… Props๋Š” interface ์‚ฌ์šฉ (ํ™•์žฅ ๊ฐ€๋Šฅ) interface ButtonProps { label: string; onClick?: () => void; variant?: "primary" | "secondary"; } // โœ… ์œ ๋‹ˆ์˜จ ํƒ€์ž…์€ type ์‚ฌ์šฉ type Status = "active" | "inactive" | "pending"; // โœ… ๋ณต์žกํ•œ ํƒ€์ž…์€ type ์‚ฌ์šฉ type ComplexType = { id: string; data: Record; } & BaseType; ``` #### **ํƒ€์ž… ์žฌ์‚ฌ์šฉ** ```typescript // ๊ณตํ†ต ํƒ€์ž… ์ •์˜ interface BaseEntity { id: string; createdAt: string; updatedAt: string; } interface Product extends BaseEntity { name: string; price: number; } interface Order extends BaseEntity { productId: string; quantity: number; } ``` ### **2. ํƒ€์ž… ์•ˆ์ •์„ฑ** #### **โœ… DO: ๋ช…์‹œ์  ํƒ€์ž… ์ง€์ •** ```typescript // State ํƒ€์ž… ๋ช…์‹œ const [items, setItems] = useState([]); const [selectedId, setSelectedId] = useState(null); // ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ํƒ€์ž… ๋ช…์‹œ function calculateTotal(items: Product[]): number { return items.reduce((sum, item) => sum + item.price, 0); } // ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํƒ€์ž… const handleClick = (event: React.MouseEvent) => { console.log(event.currentTarget); }; ``` #### **โŒ DON'T: any ํƒ€์ž… ์‚ฌ์šฉ** ```typescript // โŒ ํ”ผํ•˜๊ธฐ const [data, setData] = useState([]); function process(input: any): any { } // โœ… ๋Œ€์‹  ์‚ฌ์šฉ const [data, setData] = useState([]); function process(input: unknown): Result { } ``` ### **3. ์˜ต์…”๋„ ์ฒด์ด๋‹ & Nullish Coalescing** ```typescript // โœ… ์˜ต์…”๋„ ์ฒด์ด๋‹ const userName = user?.profile?.name; // โœ… Nullish Coalescing const displayName = user?.name ?? "Guest"; // โœ… ์˜ต์…”๋„ ์ฝœ๋ฐฑ onSave?.(); ``` --- ## ๐Ÿ”„ ์ƒํƒœ ๊ด€๋ฆฌ ### **1. useState ํŒจํ„ด** ```typescript // โœ… ๋‹จ์ˆœ ์ƒํƒœ const [isOpen, setIsOpen] = useState(false); const [count, setCount] = useState(0); // โœ… ๊ฐ์ฒด ์ƒํƒœ (๋ถˆ๋ณ€์„ฑ ์œ ์ง€) const [user, setUser] = useState({ name: "", email: "" }); const updateName = (newName: string) => { setUser(prev => ({ ...prev, name: newName })); }; // โœ… ๋ฐฐ์—ด ์ƒํƒœ const [items, setItems] = useState([]); const addItem = (item: Item) => { setItems(prev => [...prev, item]); }; const removeItem = (id: string) => { setItems(prev => prev.filter(item => item.id !== id)); }; ``` ### **2. useEffect ํŒจํ„ด** ```typescript // โœ… ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ 1ํšŒ ์‹คํ–‰ useEffect(() => { // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ fetchData(); }, []); // ๋นˆ ์˜์กด์„ฑ ๋ฐฐ์—ด // โœ… ํŠน์ • ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ์‹คํ–‰ useEffect(() => { if (selectedId) { loadDetails(selectedId); } }, [selectedId]); // selectedId ๋ณ€๊ฒฝ ๊ฐ์ง€ // โœ… ํด๋ฆฐ์—… ํ•จ์ˆ˜ useEffect(() => { const timer = setInterval(() => { // ์ฃผ๊ธฐ์  ์ž‘์—… }, 1000); return () => { clearInterval(timer); // ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ์ •๋ฆฌ }; }, []); ``` ### **3. ๋กœ์ปฌ ์ƒํƒœ vs Props** ```typescript // โœ… ๋กœ์ปฌ์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋Š” UI ์ƒํƒœ const [isHovered, setIsHovered] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false); // โœ… ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ๋ฐ›๋Š” ๋ฐ์ดํ„ฐ interface Props { data: Product[]; // ์ฝ๊ธฐ ์ „์šฉ ๋ฐ์ดํ„ฐ onUpdate: () => void; // ์ƒํƒœ ๋ณ€๊ฒฝ์€ ๋ถ€๋ชจ๊ฐ€ ์ฒ˜๋ฆฌ } ``` --- ## ๐Ÿ“‚ ํŒŒ์ผ ๋ช…๋ช… ๊ทœ์น™ ### **1. ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ** ``` PascalCase.tsx โ”œโ”€โ”€ Dashboard.tsx โ”œโ”€โ”€ SalesManagement.tsx โ”œโ”€โ”€ ProductionManagement.tsx โ””โ”€โ”€ UserProfile.tsx ``` ### **2. UI ์ปดํฌ๋„ŒํŠธ (shadcn/ui)** ``` kebab-case.tsx โ”œโ”€โ”€ button.tsx โ”œโ”€โ”€ dialog.tsx โ”œโ”€โ”€ table.tsx โ””โ”€โ”€ dropdown-menu.tsx ``` ### **3. ์œ ํ‹ธ๋ฆฌํ‹ฐ ํŒŒ์ผ** ``` camelCase.ts / kebab-case.ts โ”œโ”€โ”€ utils.ts โ”œโ”€โ”€ helpers.ts โ””โ”€โ”€ use-mobile.ts ``` ### **4. ๋ฌธ์„œ ํŒŒ์ผ** ``` UPPER_CASE.md / PascalCase.md โ”œโ”€โ”€ README.md โ”œโ”€โ”€ TECH_STACK.md โ”œโ”€โ”€ DEVELOPMENT_ENVIRONMENT.md โ””โ”€โ”€ Guidelines.md ``` ### **5. ์Šคํƒ€์ผ ํŒŒ์ผ** ``` kebab-case.css / lowercase.css โ”œโ”€โ”€ globals.css โ””โ”€โ”€ custom-styles.css ``` --- ## ๐Ÿ”€ Git ์›Œํฌํ”Œ๋กœ์šฐ ### **1. ๋ธŒ๋žœ์น˜ ์ „๋žต** ```bash main # ํ”„๋กœ๋•์…˜ ๋ธŒ๋žœ์น˜ โ”œโ”€โ”€ develop # ๊ฐœ๋ฐœ ๋ธŒ๋žœ์น˜ โ”œโ”€โ”€ feature/* # ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ โ”œโ”€โ”€ bugfix/* # ๋ฒ„๊ทธ ์ˆ˜์ • โ””โ”€โ”€ hotfix/* # ๊ธด๊ธ‰ ์ˆ˜์ • ``` ### **2. ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ๊ทœ์น™** ```bash # ํ˜•์‹: <ํƒ€์ž…>: <์ œ๋ชฉ> feat: ํŒ๋งค๊ด€๋ฆฌ ๊ฒฌ์  ์‚ฐ์ถœ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ fix: ์ƒ์‚ฐ๊ด€๋ฆฌ ๋ชจ๋‹ฌ ์Šคํฌ๋กค ์˜ค๋ฅ˜ ์ˆ˜์ • style: Dashboard ๋ ˆ์ด์•„์›ƒ ๊ฐœ์„  refactor: MaterialManagement ์ปดํฌ๋„ŒํŠธ ๋ฆฌํŒฉํ† ๋ง docs: README ์—…๋ฐ์ดํŠธ test: QualityManagement ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ chore: ์˜์กด์„ฑ ์—…๋ฐ์ดํŠธ ``` ### **3. ์ปค๋ฐ‹ ํƒ€์ž…** - `feat`: ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ - `fix`: ๋ฒ„๊ทธ ์ˆ˜์ • - `style`: UI/์Šคํƒ€์ผ ๋ณ€๊ฒฝ - `refactor`: ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง - `docs`: ๋ฌธ์„œ ์ˆ˜์ • - `test`: ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€/์ˆ˜์ • - `chore`: ๋นŒ๋“œ, ์„ค์ • ๋“ฑ --- ## ๐Ÿงช ํ…Œ์ŠคํŒ… ### **1. ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ์ฒดํฌ๋ฆฌ์ŠคํŠธ** ```typescript // ์ˆ˜๋™ ํ…Œ์ŠคํŠธ ์ฒดํฌ๋ฆฌ์ŠคํŠธ // [ ] ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋Š”๊ฐ€? // [ ] Props๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ „๋‹ฌ๋˜๋Š”๊ฐ€? // [ ] ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ž‘๋™ํ•˜๋Š”๊ฐ€? // [ ] ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์ด ์˜ฌ๋ฐ”๋ฅธ๊ฐ€? // [ ] ์—๋Ÿฌ ์ƒํƒœ๊ฐ€ ์ฒ˜๋ฆฌ๋˜๋Š”๊ฐ€? // [ ] ๋กœ๋”ฉ ์ƒํƒœ๊ฐ€ ํ‘œ์‹œ๋˜๋Š”๊ฐ€? ``` ### **2. ๋ฐ˜์‘ํ˜• ํ…Œ์ŠคํŠธ** ``` ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ ํ…Œ์ŠคํŠธ: [ ] Mobile (< 768px) [ ] Tablet (768px - 1024px) [ ] Desktop (> 1024px) ๊ธฐ๊ธฐ๋ณ„ ํ…Œ์ŠคํŠธ: [ ] iPhone (Safari) [ ] Android (Chrome) [ ] iPad (Safari) [ ] Desktop (Chrome, Firefox, Safari) ``` ### **3. ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ** ``` [ ] Chrome (์ตœ์‹ ) [ ] Firefox (์ตœ์‹ ) [ ] Safari (์ตœ์‹ ) [ ] Edge (์ตœ์‹ ) ``` --- ## โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ### **1. ๋ฆฌ๋ Œ๋”๋ง ์ตœ์ ํ™”** ```typescript // โœ… useMemo - ๋น„์šฉ์ด ํฐ ๊ณ„์‚ฐ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ const expensiveValue = useMemo(() => { return items.reduce((sum, item) => sum + item.price, 0); }, [items]); // โœ… useCallback - ํ•จ์ˆ˜ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ const handleClick = useCallback((id: string) => { console.log("Clicked:", id); }, []); // โœ… React.memo - ์ปดํฌ๋„ŒํŠธ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ export const MemoizedComponent = memo(function MyComponent({ data }: Props) { return
{data}
; }); ``` ### **2. ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง** ```typescript // โœ… ๋ถˆํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ๋ฐฉ์ง€ {isVisible && } // โœ… ๋กœ๋”ฉ ์ƒํƒœ {isLoading ? : } ``` ### **3. ์ด๋ฏธ์ง€ ์ตœ์ ํ™”** ```typescript // โœ… ImageWithFallback ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ import { ImageWithFallback } from "./components/figma/ImageWithFallback"; ``` --- ## โ™ฟ ์ ‘๊ทผ์„ฑ (Accessibility) ### **1. ์‹œ๋งจํ‹ฑ HTML** ```typescript // โœ… ์˜๋ฏธ์žˆ๋Š” HTML ํƒœ๊ทธ ์‚ฌ์šฉ

์ œ๋ชฉ

๋‚ด์šฉ

Copyright 2025

``` ### **2. ARIA ์†์„ฑ** ```typescript // โœ… ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋ฅผ ์œ„ํ•œ ๋ ˆ์ด๋ธ” // โœ… ์ƒํƒœ ํ‘œ์‹œ
์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
// โœ… ์ˆจ๊น€ ์ฝ˜ํ…์ธ  ๋Œ€ํ™”์ƒ์ž ์ œ๋ชฉ ``` ### **3. ํ‚ค๋ณด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜** ```typescript // โœ… Tab ํ‚ค๋กœ ์ด๋™ ๊ฐ€๋Šฅ ๋งํฌ // โœ… Enter/Space๋กœ ํ™œ์„ฑํ™” const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" || e.key === " ") { handleClick(); } }; ``` --- ## ๐Ÿ”’ ๋ณด์•ˆ ### **1. XSS ๋ฐฉ์ง€** ```typescript // โœ… React๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ XSS ๋ฐฉ์ง€
{userInput}
// ์ž๋™ ์ด์Šค์ผ€์ดํ”„ // โŒ dangerouslySetInnerHTML ์‚ฌ์šฉ ๊ธˆ์ง€ (ํŠน๋ณ„ํ•œ ๊ฒฝ์šฐ ์ œ์™ธ)
``` ### **2. ๋ฏผ๊ฐ ์ •๋ณด ์ฒ˜๋ฆฌ** ```typescript // โŒ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์— ๋ฏผ๊ฐ ์ •๋ณด ํ•˜๋“œ์ฝ”๋”ฉ ๊ธˆ์ง€ const API_KEY = "secret-key-123"; // ์ ˆ๋Œ€ ๊ธˆ์ง€! // โœ… ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ ๋˜๋Š” ์„œ๋ฒ„์‚ฌ์ด๋“œ ์ฒ˜๋ฆฌ const API_URL = process.env.VITE_API_URL; ``` ### **3. ์ž…๋ ฅ ๊ฒ€์ฆ** ```typescript // โœ… ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฒ€์ฆ const validateEmail = (email: string): boolean => { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email); }; const handleSubmit = (email: string) => { if (!validateEmail(email)) { alert("์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”."); return; } // ์ฒ˜๋ฆฌ }; ``` --- ## ๐Ÿ“š ๋ฌธ์„œํ™” ### **1. ์ปดํฌ๋„ŒํŠธ ๋ฌธ์„œํ™”** ```typescript /** * ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์นด๋“œ ์ปดํฌ๋„ŒํŠธ * * @param {string} name - ์‚ฌ์šฉ์ž ์ด๋ฆ„ * @param {string} email - ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ * @param {() => void} onEdit - ํŽธ์ง‘ ๋ฒ„ํŠผ ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ * * @example * console.log("edit")} * /> */ export function UserProfileCard({ name, email, onEdit }: UserProfileCardProps) { // ... } ``` ### **2. ๋ณต์žกํ•œ ๋กœ์ง ์ฃผ์„** ```typescript // โœ… ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ฃผ์„ ์ถ”๊ฐ€ function calculateDiscount(price: number, quantity: number): number { // 10๊ฐœ ์ด์ƒ ๊ตฌ๋งค ์‹œ 10% ํ• ์ธ // 50๊ฐœ ์ด์ƒ ๊ตฌ๋งค ์‹œ 20% ํ• ์ธ // 100๊ฐœ ์ด์ƒ ๊ตฌ๋งค ์‹œ 30% ํ• ์ธ if (quantity >= 100) { return price * 0.7; } else if (quantity >= 50) { return price * 0.8; } else if (quantity >= 10) { return price * 0.9; } return price; } ``` ### **3. TODO ์ฃผ์„** ```typescript // TODO: ์„ฑ๋Šฅ ์ตœ์ ํ™” ํ•„์š” // FIXME: ํŠน์ • ์กฐ๊ฑด์—์„œ ๋ฒ„๊ทธ ๋ฐœ์ƒ // HACK: ์ž„์‹œ ํ•ด๊ฒฐ์ฑ…, ์ถ”ํ›„ ๊ฐœ์„  ํ•„์š” // NOTE: ์ค‘์š”ํ•œ ์ •๋ณด ๋˜๋Š” ์ฃผ์˜์‚ฌํ•ญ ``` --- ## โœจ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค ### **1. DRY (Don't Repeat Yourself)** ```typescript // โŒ ์ค‘๋ณต ์ฝ”๋“œ function formatUserName1(name: string) { return name.trim().toUpperCase(); } function formatProductName(name: string) { return name.trim().toUpperCase(); } // โœ… ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ•จ์ˆ˜ function formatName(name: string): string { return name.trim().toUpperCase(); } const userName = formatName(user.name); const productName = formatName(product.name); ``` ### **2. ๋‹จ์ผ ์ฑ…์ž„ ์›์น™** ```typescript // โŒ ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์€ ์—ญํ•  function ComplexComponent() { // ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ // ํผ ์ฒ˜๋ฆฌ // ์ฐจํŠธ ๋ Œ๋”๋ง // ํ…Œ์ด๋ธ” ๋ Œ๋”๋ง // ... 1000+ ์ค„ } // โœ… ์ฑ…์ž„ ๋ถ„๋ฆฌ function DataContainer() { return ( <> ); } ``` ### **3. ๋ช…ํ™•ํ•œ ๋ณ€์ˆ˜๋ช…** ```typescript // โŒ ๋ถˆ๋ช…ํ™•ํ•œ ์ด๋ฆ„ const d = new Date(); const arr = [...items]; const temp = x * y; // โœ… ๋ช…ํ™•ํ•œ ์ด๋ฆ„ const currentDate = new Date(); const sortedItems = [...items]; const totalPrice = quantity * unitPrice; ``` ### **4. Early Return ํŒจํ„ด** ```typescript // โŒ ์ค‘์ฒฉ๋œ ์กฐ๊ฑด๋ฌธ function processUser(user: User) { if (user) { if (user.isActive) { if (user.hasPermission) { // ์ฒ˜๋ฆฌ } } } } // โœ… Early Return function processUser(user: User) { if (!user) return; if (!user.isActive) return; if (!user.hasPermission) return; // ์ฒ˜๋ฆฌ } ``` ### **5. ์—๋Ÿฌ ์ฒ˜๋ฆฌ** ```typescript // โœ… Try-Catch๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ async function fetchData() { try { const response = await fetch("/api/data"); const data = await response.json(); return data; } catch (error) { console.error("๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:", error); return null; } } // โœ… ์—๋Ÿฌ ์ƒํƒœ ํ‘œ์‹œ const [error, setError] = useState(null); {error && ( ์˜ค๋ฅ˜ {error} )} ``` --- ## ๐Ÿšจ ์ผ๋ฐ˜์ ์ธ ์‹ค์ˆ˜ & ํ•ด๊ฒฐ์ฑ… ### **1. Key Props ๋ˆ„๋ฝ** ```typescript // โŒ key ์—†์ด ๋ฆฌ์ŠคํŠธ ๋ Œ๋”๋ง {items.map(item =>
{item.name}
)} // โœ… ๊ณ ์œ ํ•œ key ์‚ฌ์šฉ {items.map(item => (
{item.name}
))} ``` ### **2. State ์ง์ ‘ ์ˆ˜์ •** ```typescript // โŒ State ์ง์ ‘ ์ˆ˜์ • const [items, setItems] = useState([]); items.push(newItem); // ๊ธˆ์ง€! // โœ… ์ƒˆ๋กœ์šด ๋ฐฐ์—ด ์ƒ์„ฑ setItems([...items, newItem]); setItems(prev => [...prev, newItem]); ``` ### **3. useEffect ์˜์กด์„ฑ ๋ฐฐ์—ด ๋ˆ„๋ฝ** ```typescript // โŒ ์˜์กด์„ฑ ๋ฐฐ์—ด ๋ˆ„๋ฝ useEffect(() => { fetchData(userId); }); // ๋ฌดํ•œ ๋ฃจํ”„! // โœ… ์˜์กด์„ฑ ๋ช…์‹œ useEffect(() => { fetchData(userId); }, [userId]); ``` ### **4. ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง** ```typescript // โŒ ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ๊ฐ์ฒด ์ƒ์„ฑ // โœ… ์ƒ์ˆ˜๋กœ ๋ถ„๋ฆฌ const componentStyle = { margin: 10 }; ``` --- ## ๐Ÿ“‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ### **์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ „ ์ฒดํฌ๋ฆฌ์ŠคํŠธ** ``` ์ฝ”๋“œ ํ’ˆ์งˆ: [ ] TypeScript ํƒ€์ž… ์˜ค๋ฅ˜ ์—†์Œ [ ] ESLint ๊ฒฝ๊ณ  ์—†์Œ [ ] ๋ถˆํ•„์š”ํ•œ console.log ์ œ๊ฑฐ [ ] ์ฃผ์„ ์ž‘์„ฑ (๋ณต์žกํ•œ ๋กœ์ง) [ ] ๋ณ€์ˆ˜๋ช…์ด ๋ช…ํ™•ํ•จ ๊ธฐ๋Šฅ: [ ] ์š”๊ตฌ์‚ฌํ•ญ ๋ชจ๋‘ ๊ตฌํ˜„ [ ] ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ตฌํ˜„ [ ] ๋กœ๋”ฉ ์ƒํƒœ ๊ตฌํ˜„ [ ] ์—ฃ์ง€ ์ผ€์ด์Šค ์ฒ˜๋ฆฌ UI/UX: [ ] ๋ฐ˜์‘ํ˜• ๋””์ž์ธ ๋™์ž‘ [ ] ๋ชจ๋ฐ”์ผ/๋ฐ์Šคํฌํ†ฑ ํ…Œ์ŠคํŠธ [ ] ์ ‘๊ทผ์„ฑ ์ค€์ˆ˜ [ ] ํ‚ค๋ณด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜ ๊ฐ€๋Šฅ ์„ฑ๋Šฅ: [ ] ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง ์—†์Œ [ ] ํฐ ๋ฆฌ์ŠคํŠธ ์ตœ์ ํ™” [ ] ์ด๋ฏธ์ง€ ์ตœ์ ํ™” [ ] ๋ฒˆ๋“ค ํฌ๊ธฐ ํ™•์ธ ๋ฌธ์„œํ™”: [ ] README ์—…๋ฐ์ดํŠธ (ํ•„์š”์‹œ) [ ] ๋ณ€๊ฒฝ ์‚ฌํ•ญ ๊ธฐ๋ก [ ] API ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ (ํ•„์š”์‹œ) ``` ### **๋ฐฐํฌ ์ „ ์ฒดํฌ๋ฆฌ์ŠคํŠธ** ``` [ ] ๋ชจ๋“  ํ…Œ์ŠคํŠธ ํ†ต๊ณผ [ ] ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ํ™•์ธ [ ] ์„ฑ๋Šฅ ์ธก์ • ์™„๋ฃŒ [ ] ๋ณด์•ˆ ์ทจ์•ฝ์  ์ ๊ฒ€ [ ] ๋ฐฑ์—… ์™„๋ฃŒ [ ] ๋กค๋ฐฑ ๊ณ„ํš ์ˆ˜๋ฆฝ ``` --- ## ๐ŸŽ“ ํ•™์Šต ๋ฆฌ์†Œ์Šค ### **ํ•„์ˆ˜ ๋ฌธ์„œ** 1. `TECH_STACK.md` - ๊ธฐ์ˆ  ์Šคํƒ 2. `DEVELOPMENT_ENVIRONMENT.md` - ๊ฐœ๋ฐœ ํ™˜๊ฒฝ 3. `guidelines/Guidelines.md` - ์ถ”๊ฐ€ ๊ฐ€์ด๋“œ ### **๊ณต์‹ ๋ฌธ์„œ** - [React ๊ณต์‹ ๋ฌธ์„œ](https://react.dev/) - [TypeScript ํ•ธ๋“œ๋ถ](https://www.typescriptlang.org/docs/) - [Tailwind CSS ๋ฌธ์„œ](https://tailwindcss.com/docs) - [shadcn/ui ๋ฌธ์„œ](https://ui.shadcn.com/) ### **์ถ”์ฒœ ํ•™์Šต ๊ฒฝ๋กœ** 1. **1์ฃผ์ฐจ**: React & TypeScript ๊ธฐ์ดˆ 2. **2์ฃผ์ฐจ**: Tailwind CSS & shadcn/ui 3. **3์ฃผ์ฐจ**: ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ํŒŒ์•… 4. **4์ฃผ์ฐจ**: ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ ๊ฐœ๋ฐœ --- ## ๐ŸŽฏ ํ•ต์‹ฌ ์›์น™ ์š”์•ฝ ### **์ฝ”๋“œ ์ž‘์„ฑ ์›์น™** 1. **ํƒ€์ž… ์•ˆ์ •์„ฑ**: ๋ชจ๋“  ์ฝ”๋“œ์— TypeScript ํƒ€์ž… ์ ์šฉ 2. **์žฌ์‚ฌ์šฉ์„ฑ**: DRY ์›์น™ ์ค€์ˆ˜, ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ ํ™œ์šฉ 3. **๊ฐ€๋…์„ฑ**: ๋ช…ํ™•ํ•œ ๋ณ€์ˆ˜๋ช…, ์ ์ ˆํ•œ ์ฃผ์„ 4. **์„ฑ๋Šฅ**: ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง ๋ฐฉ์ง€ 5. **์ ‘๊ทผ์„ฑ**: ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋„๋ก ### **UI/UX ์›์น™** 1. **๋ฐ˜์‘ํ˜•**: ๋ชจ๋“  ํ™”๋ฉด ํฌ๊ธฐ ๋Œ€์‘ 2. **์ผ๊ด€์„ฑ**: ๋””์ž์ธ ์‹œ์Šคํ…œ ์ค€์ˆ˜ 3. **ํ”ผ๋“œ๋ฐฑ**: ๋กœ๋”ฉ, ์—๋Ÿฌ, ์„ฑ๊ณต ์ƒํƒœ ํ‘œ์‹œ 4. **์ ‘๊ทผ์„ฑ**: ํ‚ค๋ณด๋“œ, ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์ง€์› ### **ํ˜‘์—… ์›์น™** 1. **๋ฌธ์„œํ™”**: ์ฝ”๋“œ์™€ ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋ฌธ์„œํ™” 2. **์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜**: ๋ถˆํ™•์‹คํ•œ ๋ถ€๋ถ„ ์งˆ๋ฌธ 3. **์ฝ”๋“œ ๋ฆฌ๋ทฐ**: ๊ฑด์„ค์ ์ธ ํ”ผ๋“œ๋ฐฑ 4. **์ง€์‹ ๊ณต์œ **: ํ•™์Šต ๋‚ด์šฉ ํŒ€๊ณผ ๊ณต์œ  --- ## ๐Ÿ“ž ๋„์›€ ๋ฐ›๊ธฐ ### **์งˆ๋ฌธํ•˜๊ธฐ ์ „ ์ฒดํฌ** 1. ๊ณต์‹ ๋ฌธ์„œ ํ™•์ธ 2. ๊ธฐ์กด ์ฝ”๋“œ ์ฐธ๊ณ  3. ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๊ฒ€์ƒ‰ ### **์งˆ๋ฌธ ํ˜•์‹** ``` ์ œ๋ชฉ: [์ปดํฌ๋„ŒํŠธ๋ช…] ๊ฐ„๋‹จํ•œ ๋ฌธ์ œ ์„ค๋ช… ํ™˜๊ฒฝ: - ํŒŒ์ผ: components/MyComponent.tsx - ๋ธŒ๋ผ์šฐ์ €: Chrome 120 ๋ฌธ์ œ: [์ƒ์„ธํ•œ ๋ฌธ์ œ ์„ค๋ช…] ์‹œ๋„ํ•œ ํ•ด๊ฒฐ์ฑ…: 1. ... 2. ... ์—๋Ÿฌ ๋ฉ”์‹œ์ง€: [์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋ณต์‚ฌ] ``` --- ## ๐ŸŽ‰ ๊ฒฐ๋ก  ์ด ๊ฐ€์ด๋“œ๋ผ์ธ์€ **SAM MES ์†”๋ฃจ์…˜** ๊ฐœ๋ฐœ์˜ ๊ธฐ์ค€์ด ๋ฉ๋‹ˆ๋‹ค. **ํ•ต์‹ฌ ํฌ์ธํŠธ**: โœ… TypeScript๋กœ ํƒ€์ž… ์•ˆ์ •์„ฑ ํ™•๋ณด โœ… Tailwind CSS๋กœ ์ผ๊ด€๋œ ์Šคํƒ€์ผ๋ง โœ… ๋ฐ˜์‘ํ˜• ๋””์ž์ธ์œผ๋กœ ๋ชจ๋“  ๊ธฐ๊ธฐ ์ง€์› โœ… shadcn/ui๋กœ ๋น ๋ฅธ ๊ฐœ๋ฐœ โœ… ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค ์ค€์ˆ˜ ์ด ๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ฅด๋ฉด ๋†’์€ ํ’ˆ์งˆ์˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์šด ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. --- **๋ฌธ์„œ ์ž‘์„ฑ์ผ**: 2025๋…„ 10์›” 17์ผ **๋ฒ„์ „**: 1.0.0 **์ž‘์„ฑ์ž**: SAM MES ๊ฐœ๋ฐœํŒ€ **Happy Coding! ๐Ÿš€**