Skip to content

Commit

Permalink
Merge pull request #143 from hussaino03/develop
Browse files Browse the repository at this point in the history
add labels & sorting
  • Loading branch information
hussaino03 authored Dec 14, 2024
2 parents bc1d31c + 8288065 commit dbd8c63
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 69 deletions.
3 changes: 2 additions & 1 deletion client/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ const addTask = async (taskData) => {
const newTasks = tasksToAdd.map(task => ({
...task,
id: uuidv4(),
createdAt: new Date().toISOString()
createdAt: new Date().toISOString(),
label: task.label || null
}));

const updatedTasks = [...tasks, ...newTasks];
Expand Down
37 changes: 36 additions & 1 deletion client/src/components/Modal Management/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ const TaskForm = ({ addTask }) => {
difficulty: 50,
importance: 50,
deadline: '',
collaborative: false
collaborative: false,
label: ''
};

const MAX_LABEL_LENGTH = 15;

const [formState, setFormState] = useState(defaultFormState);
const [selectedDeadline, setSelectedDeadline] = useState(null);
const [isProjectView, setIsProjectView] = useState(false);
Expand Down Expand Up @@ -43,6 +46,7 @@ const TaskForm = ({ addTask }) => {
importance: formState.importance,
deadline: formState.deadline || null,
collaborative: formState.collaborative,
label: formState.label || null,
experience: (
(parseInt(formState.difficulty) + parseInt(formState.importance) + 20) * 5 +
parseInt(parseInt(formState.difficulty) * parseInt(formState.importance) / 20) +
Expand Down Expand Up @@ -413,6 +417,37 @@ const TaskForm = ({ addTask }) => {
/>
</div>

{/* Label Input */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Label <span className="text-xs text-gray-400">{MAX_LABEL_LENGTH - formState.label.length} characters remaining</span>
</label>
<div className="relative">
<input
type="text"
placeholder="Label your tasks for easy sorting"
value={formState.label}
onChange={(e) => {
const newValue = e.target.value.slice(0, MAX_LABEL_LENGTH);
updateFormState('label', newValue);
}}
maxLength={MAX_LABEL_LENGTH}
className="w-full px-4 py-2 bg-white dark:bg-gray-700 border border-gray-300
dark:border-gray-600 rounded-lg text-gray-900 dark:text-gray-200
placeholder-gray-500 dark:placeholder-gray-400"
/>
{formState.label && (
<div className="absolute right-2 top-1/2 -translate-y-1/2">
<span className="px-1.5 py-0.5 bg-blue-50 dark:bg-blue-500/10
text-blue-600 dark:text-blue-400 rounded-full border
border-blue-200 dark:border-blue-800 text-xs">
{formState.label}
</span>
</div>
)}
</div>
</div>

{/* Description section */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Expand Down
158 changes: 104 additions & 54 deletions client/src/components/Modal Management/List.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useState } from 'react';
import Task from './View';
import CalendarView from '../../utils/CalendarView';
import { LayoutList, Calendar, PlusCircle, FolderPlus } from 'lucide-react';
import { LayoutList, Calendar, PlusCircle, FolderPlus, ArrowUpDown } from 'lucide-react';

const TaskList = ({ tasks = [], removeTask, completeTask, isCompleted, addTask, updateTask }) => {
const [quickTaskInput, setQuickTaskInput] = useState('');
const [isCalendarView, setIsCalendarView] = useState(false);
const [activeTab, setActiveTab] = useState('tasks');
const [showAllCompleted, setShowAllCompleted] = useState(false);
const [sortMethod, setSortMethod] = useState('date'); // 'date' or 'label'

const handleQuickAdd = (e) => {
if (e.key === 'Enter' && quickTaskInput.trim()) {
Expand All @@ -34,48 +35,76 @@ const TaskList = ({ tasks = [], removeTask, completeTask, isCompleted, addTask,
return acc;
}, { regularTasks: [], projects: [] });

const getSortedTasks = (tasksToSort) => {
if (sortMethod === 'label') {
// When sorting by label, only consider labels - ignore deadlines completely
return [...tasksToSort].sort((a, b) => {
if (a.label && !b.label) return -1; // a has label, b doesn't -> a goes first
if (!a.label && b.label) return 1; // b has label, a doesn't -> b goes first
if (a.label && b.label) return a.label.localeCompare(b.label); // both have labels -> alphabetical
return 0; // neither has labels -> keep original order
});
}

return [...tasksToSort].sort((a, b) => {
if (!a.deadline && !b.deadline) return 0;
if (!a.deadline) return 1;
if (!b.deadline) return -1;
return new Date(a.deadline) - new Date(b.deadline);
});
};

// Sort tasks based on active tab
const itemsToDisplay = activeTab === 'tasks' ? regularTasks : projects;
const sortedTasks = [...itemsToDisplay].sort((a, b) => {
// Tasks without deadlines go last
if (!a.deadline && !b.deadline) return 0;
if (!a.deadline) return 1;
if (!b.deadline) return -1;
return new Date(a.deadline) - new Date(b.deadline);
});
const sortedTasks = getSortedTasks(itemsToDisplay);

// Group tasks by date
// Group tasks by date OR label depending on sort method
const groupedTasks = sortedTasks.reduce((groups, task) => {
if (!task.deadline) {
if (!groups['No due date']) groups['No due date'] = [];
groups['No due date'].push(task);
if (sortMethod === 'label') {
if (task.label) {
// Group labeled tasks by their label
const groupKey = task.label;
if (!groups[groupKey]) groups[groupKey] = [];
groups[groupKey].push(task);
} else {
// For unlabeled tasks, use "Unlabeled" as group key
if (!groups['Unlabeled']) groups['Unlabeled'] = [];
groups['Unlabeled'].push(task);
}
} else {
const dateObj = new Date(task.deadline);
const userTimezoneOffset = dateObj.getTimezoneOffset() * 60000;
const adjustedDate = new Date(dateObj.getTime() + userTimezoneOffset);

const date = adjustedDate.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
year: 'numeric'
});
if (!groups[date]) groups[date] = [];
groups[date].push(task);
if (!task.deadline) {
if (!groups['No due date']) groups['No due date'] = [];
groups['No due date'].push(task);
} else {
const dateObj = new Date(task.deadline);
const userTimezoneOffset = dateObj.getTimezoneOffset() * 60000;
const adjustedDate = new Date(dateObj.getTime() + userTimezoneOffset);

const date = adjustedDate.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
year: 'numeric'
});
if (!groups[date]) groups[date] = [];
groups[date].push(task);
}
}
return groups;
}, {});

const sortedGroups = Object.entries(groupedTasks).sort(([dateA], [dateB]) => {
if (dateA === 'No due date') return 1;
if (dateB === 'No due date') return -1;
if (dateA !== 'No due date' && dateB !== 'No due date') {
// Convert the formatted dates back to timestamps for comparison
const dateAObj = new Date(dateA);
const dateBObj = new Date(dateB);
return dateAObj - dateBObj;
const sortedGroups = Object.entries(groupedTasks).sort(([keyA], [keyB]) => {
if (sortMethod === 'label') {
// Put "Unlabeled" group last, sort other labels alphabetically
if (keyA === 'Unlabeled') return 1;
if (keyB === 'Unlabeled') return -1;
return keyA.localeCompare(keyB);
} else {
// Original date sorting logic
if (keyA === 'No due date') return 1;
if (keyB === 'No due date') return -1;
return new Date(keyA) - new Date(keyB);
}
return 0;
});

const COMPLETED_TASKS_LIMIT = 3;
Expand Down Expand Up @@ -161,27 +190,48 @@ const TaskList = ({ tasks = [], removeTask, completeTask, isCompleted, addTask,
</button>
</div>

<button
onClick={() => setIsCalendarView(!isCalendarView)}
className="px-2 sm:px-4 py-1.5 sm:py-2 text-xs sm:text-sm rounded-md
bg-gray-100 dark:bg-gray-700
text-gray-600 dark:text-gray-400
hover:text-gray-900 dark:hover:text-gray-200
transition-all duration-200 ease-in-out
flex items-center gap-1.5 sm:gap-2"
>
{isCalendarView ? (
<>
<LayoutList className="w-4 h-4" />
<span className="hidden xs:inline text-xs sm:text-sm">List</span>
</>
) : (
<>
<Calendar className="w-4 h-4" />
<span className="hidden xs:inline text-xs sm:text-sm">Calendar</span>
</>
)}
</button>
<div className="flex items-center gap-2">
{/* Sort Dropdown */}
<div className="relative">
<button
onClick={() => setSortMethod(sortMethod === 'date' ? 'label' : 'date')}
className="px-2 sm:px-4 py-1.5 sm:py-2 text-xs sm:text-sm rounded-md
bg-gray-100 dark:bg-gray-700
text-gray-600 dark:text-gray-400
hover:text-gray-900 dark:hover:text-gray-200
transition-all duration-200 ease-in-out
flex items-center gap-1.5 sm:gap-2"
>
<ArrowUpDown className="w-4 h-4" />
<span className="hidden xs:inline">
Sort by {sortMethod === 'date' ? 'Label' : 'Due Date'}
</span>
</button>
</div>

{/* Calendar/List View Toggle */}
<button
onClick={() => setIsCalendarView(!isCalendarView)}
className="px-2 sm:px-4 py-1.5 sm:py-2 text-xs sm:text-sm rounded-md
bg-gray-100 dark:bg-gray-700
text-gray-600 dark:text-gray-400
hover:text-gray-900 dark:hover:text-gray-200
transition-all duration-200 ease-in-out
flex items-center gap-1.5 sm:gap-2"
>
{isCalendarView ? (
<>
<LayoutList className="w-4 h-4" />
<span className="hidden xs:inline text-xs sm:text-sm">List</span>
</>
) : (
<>
<Calendar className="w-4 h-4" />
<span className="hidden xs:inline text-xs sm:text-sm">Calendar</span>
</>
)}
</button>
</div>
</div>
</>
) : (
Expand Down
37 changes: 24 additions & 13 deletions client/src/components/Modal Management/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,31 @@ const Task = ({ task, removeTask, completeTask, isCompleted, updateTask }) => {
/>
</form>
) : (
<span className="flex-1 min-w-0 text-center text-gray-700 dark:text-gray-200 mx-2 break-words">
{task.name}
{task.subtasks && (
<span className="ml-2 text-xs px-1.5 py-0.5 bg-blue-50 dark:bg-blue-500/10
<span className="flex-1 min-w-0 text-center text-gray-700 dark:text-gray-200 mx-2 flex items-center justify-center gap-2 flex-wrap">
<span className="truncate max-w-[200px] sm:max-w-none">{task.name}</span>
<div className="inline-flex items-center gap-1.5 flex-shrink-0">
{task.subtasks && (
<span className="inline-flex text-xs px-1.5 py-0.5 bg-blue-50 dark:bg-blue-500/10
text-blue-600 dark:text-blue-400 rounded-full border
border-blue-200 dark:border-blue-800">
Project
</span>
)}
{!isCompleted && task.deadline && isOverdue(task.deadline) && (
<span className="ml-2 text-red-500 text-sm">
OVERDUE ({calculateOverduePenalty(task.deadline)} XP)
</span>
)}
border-blue-200 dark:border-blue-800 whitespace-nowrap">
Project
</span>
)}
{task.label && (
<span className="inline-flex text-xs px-1.5 py-0.5 bg-blue-50 dark:bg-blue-500/10
text-blue-600 dark:text-blue-400 rounded-full border
border-blue-200 dark:border-blue-800 whitespace-nowrap">
{task.label}
</span>
)}
{!isCompleted && task.deadline && isOverdue(task.deadline) && (
<span className="inline-flex text-xs px-1.5 py-0.5 bg-red-50 dark:bg-red-500/10
text-red-600 dark:text-red-400 rounded-full border
border-red-200 dark:border-red-800 whitespace-nowrap">
OVERDUE ({calculateOverduePenalty(task.deadline)} XP)
</span>
)}
</div>
</span>
)}

Expand Down

0 comments on commit dbd8c63

Please sign in to comment.