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
This commit is contained in:
parent
b6c6ea94ef
commit
132f23a6ee
255
src/app/globals.css
Normal file
255
src/app/globals.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
105
src/app/tasks/page.tsx
Normal file
105
src/app/tasks/page.tsx
Normal file
@ -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<Task[]>([]);
|
||||||
|
const [loading, setLoading] = useState<boolean>(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 (
|
||||||
|
<div className="tasks-page">
|
||||||
|
<h1>Loading tasks...</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tasks-page">
|
||||||
|
<h1>Weekly Tasks</h1>
|
||||||
|
<TaskForm onSubmit={handleAddTask} />
|
||||||
|
<WeeklyCalendarView tasks={tasks} onTaskDrop={handleTaskDrop} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TasksPage;
|
||||||
121
src/components/TaskForm.tsx
Normal file
121
src/components/TaskForm.tsx
Normal file
@ -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<TaskFormProps> = ({ onSubmit }) => {
|
||||||
|
const [formData, setFormData] = useState<TaskFormData>({
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
startTime: '',
|
||||||
|
endTime: '',
|
||||||
|
dayOfWeek: 1 // Default to Monday
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
||||||
|
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 (
|
||||||
|
<form onSubmit={handleSubmit} className="task-form">
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="title">Task Title *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="title"
|
||||||
|
name="title"
|
||||||
|
value={formData.title}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="description">Description</label>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={handleChange}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-row">
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="startTime">Start Time</label>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
id="startTime"
|
||||||
|
name="startTime"
|
||||||
|
value={formData.startTime}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="endTime">End Time</label>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
id="endTime"
|
||||||
|
name="endTime"
|
||||||
|
value={formData.endTime}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="dayOfWeek">Day of Week</label>
|
||||||
|
<select
|
||||||
|
id="dayOfWeek"
|
||||||
|
name="dayOfWeek"
|
||||||
|
value={formData.dayOfWeek}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<option value={0}>Sunday</option>
|
||||||
|
<option value={1}>Monday</option>
|
||||||
|
<option value={2}>Tuesday</option>
|
||||||
|
<option value={3}>Wednesday</option>
|
||||||
|
<option value={4}>Thursday</option>
|
||||||
|
<option value={5}>Friday</option>
|
||||||
|
<option value={6}>Saturday</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" className="submit-button">
|
||||||
|
Add Task
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskForm;
|
||||||
42
src/components/TaskList.tsx
Normal file
42
src/components/TaskList.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import WeeklyTaskItem from './WeeklyTaskItem';
|
||||||
|
|
||||||
|
interface Task {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
completed: boolean;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
dayOfWeek?: number; // 0 = Sunday, 1 = Monday, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TaskListProps {
|
||||||
|
tasks: Task[];
|
||||||
|
onTaskClick?: (task: Task) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TaskList: React.FC<TaskListProps> = ({ tasks, onTaskClick }) => {
|
||||||
|
if (tasks.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="empty-state">
|
||||||
|
<p>No tasks found</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="task-list">
|
||||||
|
{tasks.map((task) => (
|
||||||
|
<li key={task.id}>
|
||||||
|
<WeeklyTaskItem
|
||||||
|
task={task}
|
||||||
|
onClick={() => onTaskClick?.(task)}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskList;
|
||||||
138
src/components/WeeklyCalendarView.tsx
Normal file
138
src/components/WeeklyCalendarView.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import TaskList from './TaskList';
|
||||||
|
|
||||||
|
interface Task {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
completed: boolean;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
dayOfWeek?: number; // 0 = Sunday, 1 = Monday, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WeeklyCalendarViewProps {
|
||||||
|
tasks: Task[];
|
||||||
|
onTaskDrop?: (taskId: string, newDayOfWeek: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WeeklyCalendarView: React.FC<WeeklyCalendarViewProps> = ({ tasks, onTaskDrop }) => {
|
||||||
|
const [currentWeekStart, setCurrentWeekStart] = useState<Date>(new Date());
|
||||||
|
|
||||||
|
// Days of the week
|
||||||
|
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||||
|
|
||||||
|
// Function to get tasks for a specific day
|
||||||
|
const getTasksForDay = (dayIndex: number): Task[] => {
|
||||||
|
return tasks.filter(task => task.dayOfWeek === dayIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to handle drag start
|
||||||
|
const handleDragStart = (e: React.DragEvent, taskId: string) => {
|
||||||
|
e.dataTransfer.setData('taskId', taskId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to handle drag over
|
||||||
|
const handleDragOver = (e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to handle drop
|
||||||
|
const handleDrop = (e: React.DragEvent, dayIndex: number) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const taskId = e.dataTransfer.getData('taskId');
|
||||||
|
if (onTaskDrop) {
|
||||||
|
onTaskDrop(taskId, dayIndex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to goToPreviousWeek
|
||||||
|
const goToPreviousWeek = () => {
|
||||||
|
const newDate = new Date(currentWeekStart);
|
||||||
|
newDate.setDate(newDate.getDate() - 7);
|
||||||
|
setCurrentWeekStart(newDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to goToNextWeek
|
||||||
|
const goToNextWeek = () => {
|
||||||
|
const newDate = new Date(currentWeekStart);
|
||||||
|
newDate.setDate(newDate.getDate() + 7);
|
||||||
|
setCurrentWeekStart(newDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get current week range
|
||||||
|
const getWeekRange = (): { start: string; end: string } => {
|
||||||
|
const start = new Date(currentWeekStart);
|
||||||
|
const end = new Date(start);
|
||||||
|
end.setDate(end.getDate() + 6);
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
|
||||||
|
end: end.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'ArrowLeft') {
|
||||||
|
goToPreviousWeek();
|
||||||
|
} else if (e.key === 'ArrowRight') {
|
||||||
|
goToNextWeek();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [currentWeekStart]);
|
||||||
|
|
||||||
|
const weekRange = getWeekRange();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="weekly-calendar-view">
|
||||||
|
<div className="calendar-header">
|
||||||
|
<div className="week-navigation">
|
||||||
|
<button
|
||||||
|
onClick={goToPreviousWeek}
|
||||||
|
className="nav-button"
|
||||||
|
aria-label="Previous week"
|
||||||
|
>
|
||||||
|
← Previous Week
|
||||||
|
</button>
|
||||||
|
<span className="week-range">{weekRange.start} - {weekRange.end}</span>
|
||||||
|
<button
|
||||||
|
onClick={goToNextWeek}
|
||||||
|
className="nav-button"
|
||||||
|
aria-label="Next week"
|
||||||
|
>
|
||||||
|
Next Week →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="calendar-grid">
|
||||||
|
{daysOfWeek.map((day, index) => (
|
||||||
|
<div
|
||||||
|
key={day}
|
||||||
|
className="day-column"
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDrop={(e) => handleDrop(e, index)}
|
||||||
|
>
|
||||||
|
<div className="day-header">
|
||||||
|
<h3>{day}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="day-content">
|
||||||
|
<TaskList tasks={getTasksForDay(index)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WeeklyCalendarView;
|
||||||
41
src/components/WeeklyTaskItem.tsx
Normal file
41
src/components/WeeklyTaskItem.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface Task {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
completed: boolean;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
dayOfWeek?: number; // 0 = Sunday, 1 = Monday, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WeeklyTaskItemProps {
|
||||||
|
task: Task;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WeeklyTaskItem: React.FC<WeeklyTaskItemProps> = ({ task, onClick }) => {
|
||||||
|
const handleTaskClick = () => {
|
||||||
|
if (onClick) {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`task-item ${task.completed ? 'completed' : ''} weekly-task`}
|
||||||
|
onClick={handleTaskClick}
|
||||||
|
>
|
||||||
|
<h4>{task.title}</h4>
|
||||||
|
{task.description && <p>{task.description}</p>}
|
||||||
|
{task.startTime && task.endTime && (
|
||||||
|
<div className="time-indicator">
|
||||||
|
<span>{task.startTime} - {task.endTime}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WeeklyTaskItem;
|
||||||
Loading…
Reference in New Issue
Block a user