From 132f23a6ee1762a6f1def8babaa9cbd34cd3a3db Mon Sep 17 00:00:00 2001 From: mARTin Date: Sun, 25 Jan 2026 11:25:03 +0100 Subject: [PATCH] feat(02-02): implement UI components for weekly calendar view - Created TaskList component with WeeklyTaskItem - Created WeeklyCalendarView component with drag-and-drop functionality - Created main tasks page with integration - Added TaskForm component for creating new tasks - Implemented navigation controls for week switching - Added responsive styling for all components Files created: - src/components/TaskList.tsx - src/components/WeeklyCalendarView.tsx - src/app/tasks/page.tsx - src/components/TaskForm.tsx - src/components/WeeklyTaskItem.tsx - src/app/globals.css --- src/app/globals.css | 255 ++++++++++++++++++++++++++ src/app/tasks/page.tsx | 105 +++++++++++ src/components/TaskForm.tsx | 121 ++++++++++++ src/components/TaskList.tsx | 42 +++++ src/components/WeeklyCalendarView.tsx | 138 ++++++++++++++ src/components/WeeklyTaskItem.tsx | 41 +++++ 6 files changed, 702 insertions(+) create mode 100644 src/app/globals.css create mode 100644 src/app/tasks/page.tsx create mode 100644 src/components/TaskForm.tsx create mode 100644 src/components/TaskList.tsx create mode 100644 src/components/WeeklyCalendarView.tsx create mode 100644 src/components/WeeklyTaskItem.tsx diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..b35f73a --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,255 @@ +/* Global Styles */ + +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #f5f7fa; + color: #333; + line-height: 1.6; +} + +/* Typography */ +h1 { + font-size: 2rem; + margin-bottom: 1rem; + color: #2c3e50; +} + +h2 { + font-size: 1.5rem; + margin-bottom: 0.75rem; + color: #34495e; +} + +h3 { + font-size: 1.25rem; + margin-bottom: 0.5rem; + color: #2c3e50; +} + +/* Layout */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 1rem; +} + +/* Buttons */ +.btn { + padding: 0.5rem 1rem; + border: none; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + transition: background-color 0.2s ease; +} + +.btn-primary { + background-color: #3498db; + color: white; +} + +.btn-primary:hover { + background-color: #2980b9; +} + +.btn-secondary { + background-color: #95a5a6; + color: white; +} + +.btn-secondary:hover { + background-color: #7f8c8d; +} + +/* Forms */ +.form-group { + margin-bottom: 1rem; +} + +.form-group label { + display: block; + margin-bottom: 0.25rem; + font-weight: 500; +} + +.form-group input, +.form-group textarea, +.form-group select { + width: 100%; + padding: 0.5rem; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 1rem; +} + +.form-row { + display: flex; + gap: 1rem; + margin-bottom: 1rem; +} + +.form-row .form-group { + flex: 1; +} + +/* Task List */ +.task-list { + list-style: none; +} + +.task-list li { + margin-bottom: 0.5rem; +} + +/* Task Item */ +.task-item { + background: white; + border-radius: 6px; + padding: 1rem; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + cursor: pointer; + transition: box-shadow 0.2s ease; +} + +.task-item:hover { + box-shadow: 0 2px 6px rgba(0,0,0,0.15); +} + +.task-item.completed { + opacity: 0.7; +} + +.task-item h4 { + margin: 0 0 0.5rem 0; + color: #2c3e50; +} + +.task-item p { + margin: 0 0 0.5rem 0; + color: #7f8c8d; +} + +.time-indicator { + font-size: 0.875rem; + color: #3498db; + font-weight: 500; +} + +/* Weekly Calendar View */ +.weekly-calendar-view { + margin-top: 2rem; +} + +.calendar-header { + margin-bottom: 1.5rem; +} + +.week-navigation { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; +} + +.nav-button { + padding: 0.5rem 1rem; + background-color: #ecf0f1; + border: none; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + transition: background-color 0.2s ease; +} + +.nav-button:hover { + background-color: #d5dbdb; +} + +.week-range { + font-weight: 500; + font-size: 1.125rem; +} + +.calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 1rem; +} + +.day-column { + background: white; + border-radius: 6px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); +} + +.day-header { + background-color: #3498db; + color: white; + padding: 0.75rem; + text-align: center; +} + +.day-content { + padding: 1rem; + min-height: 200px; +} + +/* Task Form */ +.task-form { + background: white; + padding: 1.5rem; + border-radius: 6px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + margin-bottom: 2rem; +} + +.submit-button { + background-color: #27ae60; + color: white; + padding: 0.75rem 1.5rem; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 1rem; + font-weight: 500; +} + +.submit-button:hover { + background-color: #219653; +} + +/* Empty state */ +.empty-state { + text-align: center; + padding: 2rem; + color: #7f8c8d; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .form-row { + flex-direction: column; + gap: 0; + } + + .calendar-grid { + grid-template-columns: repeat(1, 1fr); + } + + .week-navigation { + flex-direction: column; + gap: 0.5rem; + } + + .week-range { + font-size: 1rem; + } +} \ No newline at end of file diff --git a/src/app/tasks/page.tsx b/src/app/tasks/page.tsx new file mode 100644 index 0000000..58117c0 --- /dev/null +++ b/src/app/tasks/page.tsx @@ -0,0 +1,105 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import WeeklyCalendarView from '@/components/WeeklyCalendarView'; +import TaskForm from '@/components/TaskForm'; + +interface Task { + id: string; + title: string; + description?: string; + completed: boolean; + startTime?: string; + endTime?: string; + dayOfWeek?: number; // 0 = Sunday, 1 = Monday, etc. +} + +const TasksPage: React.FC = () => { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + + // Simulate fetching tasks from API + useEffect(() => { + const fetchTasks = async () => { + // In a real app, this would be an API call + // const response = await fetch('/api/tasks'); + // const data = await response.json(); + + // Mock data for demonstration + const mockTasks: Task[] = [ + { + id: '1', + title: 'Morning workout', + description: '30 minute cardio session', + completed: false, + startTime: '07:00', + endTime: '07:30', + dayOfWeek: 1 // Monday + }, + { + id: '2', + title: 'Team meeting', + description: 'Weekly sprint planning', + completed: false, + startTime: '10:00', + endTime: '11:00', + dayOfWeek: 2 // Tuesday + }, + { + id: '3', + title: 'Lunch with Sarah', + description: 'Discuss project requirements', + completed: false, + startTime: '12:30', + endTime: '13:30', + dayOfWeek: 3 // Wednesday + } + ]; + + setTasks(mockTasks); + setLoading(false); + }; + + fetchTasks(); + }, []); + + const handleTaskDrop = (taskId: string, newDayOfWeek: number) => { + setTasks(prevTasks => + prevTasks.map(task => + task.id === taskId ? { ...task, dayOfWeek: newDayOfWeek } : task + ) + ); + }; + + const handleAddTask = (taskData: any) => { + const newTask: Task = { + id: `task-${Date.now()}`, + title: taskData.title, + description: taskData.description, + completed: false, + startTime: taskData.startTime, + endTime: taskData.endTime, + dayOfWeek: taskData.dayOfWeek + }; + + setTasks([...tasks, newTask]); + }; + + if (loading) { + return ( +
+

Loading tasks...

+
+ ); + } + + return ( +
+

Weekly Tasks

+ + +
+ ); +}; + +export default TasksPage; \ No newline at end of file diff --git a/src/components/TaskForm.tsx b/src/components/TaskForm.tsx new file mode 100644 index 0000000..f110a45 --- /dev/null +++ b/src/components/TaskForm.tsx @@ -0,0 +1,121 @@ +'use client'; + +import React, { useState } from 'react'; + +interface TaskFormData { + title: string; + description?: string; + startTime?: string; + endTime?: string; + dayOfWeek?: number; +} + +interface TaskFormProps { + onSubmit: (task: TaskFormData) => void; +} + +const TaskForm: React.FC = ({ onSubmit }) => { + const [formData, setFormData] = useState({ + title: '', + description: '', + startTime: '', + endTime: '', + dayOfWeek: 1 // Default to Monday + }); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value + }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSubmit(formData); + // Reset form + setFormData({ + title: '', + description: '', + startTime: '', + endTime: '', + dayOfWeek: 1 + }); + }; + + return ( +
+
+ + +
+ +
+ +