7.10 פרויקטים פרויקט
פרויקט - לוח ניהול משימות¶
בפרויקט הזה תבנו אפליקציית ניהול משימות (Task Management Dashboard) מלאה שמשלבת את כל הנושאים שלמדנו בסקציה הזו: Zustand לניהול state, React Query לשליפת נתונים, React Router לניתוב, React Hook Form ו-Zod לטפסים, ודפוסי תכנון מתקדמים.
תיאור הפרויקט¶
לוח בקרה לניהול משימות צוותי, עם אותנטיקציה, פעולות CRUD, סינון ומיון, ועיצוב רספונסיבי.
דרישות טכניות¶
טכנולוגיות¶
- React + TypeScript
- React Router (ניתוב)
- Zustand (ניהול UI state + אותנטיקציה)
- React Query / TanStack Query (שליפת נתונים מ-API)
- React Hook Form + Zod (טפסים וולידציה)
- CSS Modules או Tailwind CSS (עיצוב)
מבנה הפרויקט¶
src/
components/
ui/ - קומפוננטות UI בסיסיות (Button, Input, Modal, Toast)
layout/ - Header, Sidebar, Layout
tasks/ - TaskCard, TaskList, TaskForm, TaskFilters
auth/ - LoginForm, RegisterForm
hooks/ - הוקים מותאמים אישית
stores/ - Zustand stores
api/ - פונקציות API (mock או אמיתי)
schemas/ - סכמות Zod
pages/ - דפי האפליקציה
types/ - טיפוסי TypeScript
תכונות נדרשות¶
1. אותנטיקציה - Authentication Flow¶
- דף התחברות עם טופס (אימייל + סיסמה)
- דף הרשמה עם ולידציה (שם, אימייל, סיסמה, אישור סיסמה)
- שמירת טוקן ב-localStorage עם Zustand persist
- נתיבים מוגנים (Protected Routes) - הפניה לדף התחברות
- כפתור התנתקות
- Zustand store לניהול מצב האותנטיקציה
// דוגמת מבנה Auth Store
interface AuthStore {
user: User | null;
token: string | null;
isAuthenticated: boolean;
login: (email: string, password: string) => Promise<void>;
register: (data: RegisterData) => Promise<void>;
logout: () => void;
}
2. ניהול משימות - CRUD Operations¶
- יצירת משימה חדשה (טופס עם React Hook Form + Zod)
- צפייה בפרטי משימה
- עריכת משימה
- מחיקת משימה (עם אישור במודל)
- שינוי סטטוס משימה
כל משימה כוללת:
interface Task {
id: string;
title: string;
description: string;
status: "todo" | "in-progress" | "review" | "done";
priority: "low" | "medium" | "high" | "urgent";
assignee: string;
dueDate: string;
tags: string[];
createdAt: string;
updatedAt: string;
}
סכמת Zod לולידציית טופס:
const taskSchema = z.object({
title: z.string().min(3, "כותרת חייבת להכיל לפחות 3 תווים").max(100),
description: z.string().max(500).optional(),
status: z.enum(["todo", "in-progress", "review", "done"]),
priority: z.enum(["low", "medium", "high", "urgent"]),
assignee: z.string().min(1, "יש לבחור אחראי"),
dueDate: z.string().refine(
(date) => new Date(date) > new Date(),
"תאריך יעד חייב להיות בעתיד"
),
tags: z.array(z.string()).max(5, "מקסימום 5 תגיות"),
});
3. שליפת נתונים - React Query¶
- שליפת רשימת משימות עם useQuery
- יצירה, עדכון ומחיקה עם useMutation
- Optimistic updates על שינוי סטטוס
- Invalidation אוטומטי אחרי mutations
- מטמון עם staleTime מתאים
// דוגמת hooks
function useTasks(filters: TaskFilters) {
return useQuery({
queryKey: ["tasks", filters],
queryFn: () => fetchTasks(filters),
staleTime: 2 * 60 * 1000,
});
}
function useCreateTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createTask,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["tasks"] });
},
});
}
4. סינון ומיון - Filtering and Sorting¶
- סינון לפי סטטוס (multi-select)
- סינון לפי עדיפות
- סינון לפי אחראי
- חיפוש חופשי בכותרת ותיאור
- מיון לפי: תאריך יצירה, תאריך יעד, עדיפות, כותרת
- שמירת פילטרים ב-URL עם useSearchParams
5. ניתוב - React Router¶
מבנה נתיבים:
/login - דף התחברות
/register - דף הרשמה
/ - לוח בקרה ראשי (מוגן)
/tasks - רשימת משימות (מוגן)
/tasks/:taskId - פרטי משימה (מוגן)
/tasks/new - יצירת משימה (מוגן)
/tasks/:taskId/edit - עריכת משימה (מוגן)
/board - תצוגת Kanban (מוגן)
* - דף 404
6. עיצוב רספונסיבי - Responsive Layout¶
- Layout עם Header (ניווט + פרופיל) ו-Sidebar (תפריט)
- ב-mobile: Sidebar נסגר ונפתח בלחיצה
- תצוגת משימות: רשימה במובייל, Grid בדסקטופ
- מודלים עם Portals
- מערכת Toast להודעות (Portal)
- תמיכה ב-dark mode עם Zustand persist
תכונות בונוס (אופציונלי)¶
- תצוגת Kanban עם drag & drop (ספריית dnd-kit)
- דפדוף ברשימת משימות
- גלילה אינסופית
- ייצוא משימות ל-CSV
- גרפים וסטטיסטיקות (משימות לפי סטטוס, עדיפות, אחראי)
- Undo על פעולת מחיקה
- Keyboard shortcuts (Ctrl+N ליצירת משימה, / לחיפוש)
API מדומה¶
אפשר להשתמש ב-API מדומה עם setTimeout, או ב-json-server:
קובץ db.json:
{
"users": [
{ "id": "1", "name": "דני כהן", "email": "dani@example.com", "password": "123456" }
],
"tasks": [
{
"id": "1",
"title": "עיצוב דף הבית",
"description": "לעצב את דף הבית של האפליקציה",
"status": "in-progress",
"priority": "high",
"assignee": "דני כהן",
"dueDate": "2024-04-15",
"tags": ["עיצוב", "UI"],
"createdAt": "2024-03-01",
"updatedAt": "2024-03-10"
}
]
}
הרצה:
שלבי עבודה מומלצים¶
שלב 1 - בסיס (יום 1-2)¶
- הקמת פרויקט עם Vite + TypeScript
- התקנת כל הספריות
- הגדרת מבנה תיקיות
- הגדרת React Router עם Layout בסיסי
- הגדרת QueryClient ו-Zustand store
שלב 2 - אותנטיקציה (יום 3)¶
- Auth store ב-Zustand עם persist
- טפסי התחברות והרשמה עם React Hook Form + Zod
- Protected Routes
- ניווט מותנה (מחובר/לא מחובר)
שלב 3 - CRUD משימות (יום 4-5)¶
- API functions (mock)
- React Query hooks (useTasks, useCreateTask, useUpdateTask, useDeleteTask)
- טופס יצירת/עריכת משימה
- רשימת משימות עם TaskCard
- דף פרטי משימה
- מודל אישור מחיקה
שלב 4 - סינון ומיון (יום 6)¶
- קומפוננטת TaskFilters
- שמירת פילטרים ב-URL
- חיפוש חופשי עם debounce
- מיון
שלב 5 - עיצוב ושיפורים (יום 7-8)¶
- עיצוב רספונסיבי
- Dark mode
- Toast notifications
- אנימציות
- Optimistic updates
- Error Boundaries
שלב 6 - בונוסים (יום 9-10)¶
- תצוגת Kanban
- סטטיסטיקות
- Keyboard shortcuts
- ליטוש ותיקון באגים
קריטריונים להצלחה¶
- האפליקציה עובדת ללא שגיאות
- שימוש נכון בכל הספריות שנלמדו
- קוד נקי ומאורגן עם TypeScript
- ולידציה מלאה בטפסים
- ניתוב עם נתיבים מוגנים
- עיצוב רספונסיבי בסיסי
- טיפול במצבי טעינה ושגיאה
- שמירת state ב-persist (אותנטיקציה, הגדרות)
דוגמת מבנה קובץ ראשי¶
// App.tsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { Suspense, lazy } from "react";
import { ErrorBoundary } from "react-error-boundary";
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 2 * 60 * 1000, retry: 2 },
},
});
const LoginPage = lazy(() => import("./pages/LoginPage"));
const RegisterPage = lazy(() => import("./pages/RegisterPage"));
const DashboardPage = lazy(() => import("./pages/DashboardPage"));
const TasksPage = lazy(() => import("./pages/TasksPage"));
const TaskDetailPage = lazy(() => import("./pages/TaskDetailPage"));
const BoardPage = lazy(() => import("./pages/BoardPage"));
function App() {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ToastProvider>
<Suspense fallback={<LoadingPage />}>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route element={<ProtectedRoute />}>
<Route element={<AppLayout />}>
<Route path="/" element={<DashboardPage />} />
<Route path="/tasks" element={<TasksPage />} />
<Route path="/tasks/:taskId" element={<TaskDetailPage />} />
<Route path="/board" element={<BoardPage />} />
</Route>
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Suspense>
</ToastProvider>
</ErrorBoundary>
</BrowserRouter>
<ReactQueryDevtools />
</QueryClientProvider>
);
}
בהצלחה!