feat: add social features, leaderboard, auth enhancements, and admin fixes
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Implement Social Features: Shared Tasks, Global Access, Privacy Settings (Leaderboard/Searchable). - Add Leaderboard Page and API. - Enhance Auth: Support Email/Username login, explicit duplicate registration errors. - Fix: Admin login password hash regression. - Refactor: Move to wouter for routing, add Admin Dashboard and User Management. - Add Setup Wizard. - Update UI with Sidebar and Gamification elements.
This commit is contained in:
@@ -12,6 +12,8 @@ import TimeCompletionModal from './TimeCompletionModal';
|
||||
import { addDays, format, isSameDay, startOfWeek, endOfWeek, startOfDay, endOfDay, addWeeks } from 'date-fns';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useDateLocale } from '../hooks/use-date-locale';
|
||||
|
||||
interface WeekListViewProps {
|
||||
tasks: Task[];
|
||||
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
|
||||
@@ -26,6 +28,7 @@ type FilterOption = 'all' | 'todo' | 'inProgress' | 'done' | 'overdue';
|
||||
|
||||
export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDelete, onStartTimer, onStopTimer }: WeekListViewProps) {
|
||||
const { t } = useTranslation();
|
||||
const dateLocale = useDateLocale();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sortBy, setSortBy] = useState<SortOption>('dueDate');
|
||||
const [filterBy, setFilterBy] = useState<FilterOption>('all');
|
||||
@@ -64,10 +67,10 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
|
||||
// Search filter
|
||||
const matchesSearch = task.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
(task.description?.toLowerCase()?.includes(searchQuery.toLowerCase()) ?? false);
|
||||
|
||||
(task.description?.toLowerCase()?.includes(searchQuery.toLowerCase()) ?? false);
|
||||
|
||||
if (!matchesSearch) return false;
|
||||
|
||||
|
||||
// Status filter
|
||||
switch (filterBy) {
|
||||
case 'overdue':
|
||||
@@ -85,18 +88,18 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
if (!a.dueDate) return 1;
|
||||
if (!b.dueDate) return -1;
|
||||
return a.dueDate.getTime() - b.dueDate.getTime();
|
||||
|
||||
|
||||
case 'priority':
|
||||
const priorityOrder = { high: 3, medium: 2, low: 1 };
|
||||
return priorityOrder[b.priority as keyof typeof priorityOrder] - priorityOrder[a.priority as keyof typeof priorityOrder];
|
||||
|
||||
|
||||
case 'title':
|
||||
return a.title.localeCompare(b.title);
|
||||
|
||||
|
||||
case 'status':
|
||||
const statusOrder = { todo: 1, inProgress: 2, done: 3 };
|
||||
return statusOrder[a.status as keyof typeof statusOrder] - statusOrder[b.status as keyof typeof statusOrder];
|
||||
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -115,7 +118,7 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
};
|
||||
|
||||
const getTasksForDate = (date: Date) => {
|
||||
return filteredAndSortedTasks.filter(task =>
|
||||
return filteredAndSortedTasks.filter(task =>
|
||||
task.dueDate && isSameDay(task.dueDate, date)
|
||||
);
|
||||
};
|
||||
@@ -136,7 +139,7 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
|
||||
const handleTimeLogged = (timeSpent: number) => {
|
||||
if (completionModal.task) {
|
||||
onTaskUpdate?.(completionModal.task.id, {
|
||||
onTaskUpdate?.(completionModal.task.id, {
|
||||
status: 'done',
|
||||
timeTracked: (completionModal.task.timeTracked || 0) + timeSpent,
|
||||
isTracking: false
|
||||
@@ -148,7 +151,7 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
return (
|
||||
<div className="space-y-4 pb-4">
|
||||
{/* Header and Search Filters */}
|
||||
<div className="sticky top-16 z-40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b pb-3 mb-4">
|
||||
<div className="sticky top-0 z-40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b pb-3 mb-4 pt-4">
|
||||
<div className="space-y-3">
|
||||
{/* Title Bar */}
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -175,7 +178,7 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
data-testid="input-search-week-tasks"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Select value={filterBy} onValueChange={(value: FilterOption) => {
|
||||
setFilterBy(value);
|
||||
@@ -195,7 +198,7 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
<SelectItem value="overdue">{t('weekList.filter.overdue')} ({getFilterCount('overdue')})</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
|
||||
<Select value={sortBy} onValueChange={(value: SortOption) => {
|
||||
setSortBy(value);
|
||||
console.log('Sort changed to:', value);
|
||||
@@ -223,13 +226,13 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold" data-testid="text-week-range">
|
||||
{format(weekStart, 'MMM d')} - {format(weekEnd, 'MMM d, yyyy')}
|
||||
{format(weekStart, 'MMM d', { locale: dateLocale })} - {format(weekEnd, 'MMM d, yyyy', { locale: dateLocale })}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => navigateCalendar('prev')}
|
||||
data-testid="button-prev-week-list"
|
||||
@@ -237,18 +240,18 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
{t('weekList.previous')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToCurrentWeek}
|
||||
data-testid="button-today-week-list"
|
||||
>
|
||||
{t('weekList.today')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => navigateCalendar('next')}
|
||||
data-testid="button-next-week-list"
|
||||
@@ -265,20 +268,19 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
const dayTasks = getTasksForDate(date);
|
||||
const isToday = isSameDay(date, new Date());
|
||||
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`p-2 rounded border-2 text-center ${
|
||||
isToday ? 'border-primary bg-primary/5' : isWeekend ? 'bg-muted/50' : 'border-border'
|
||||
}`}
|
||||
className={`p-2 rounded border-2 text-center ${isToday ? 'border-primary bg-primary/5' : isWeekend ? 'bg-muted/50' : 'border-border'
|
||||
}`}
|
||||
data-testid={`week-calendar-date-${format(date, 'yyyy-MM-dd')}`}
|
||||
>
|
||||
<div className={`text-xs font-medium ${isWeekend ? 'text-muted-foreground' : 'text-muted-foreground'}`}>
|
||||
{format(date, 'EEE')}
|
||||
{format(date, 'EEE', { locale: dateLocale })}
|
||||
</div>
|
||||
<div className={`text-sm font-semibold ${isToday ? 'text-primary' : ''}`}>
|
||||
{format(date, 'd')}
|
||||
{format(date, 'd', { locale: dateLocale })}
|
||||
</div>
|
||||
{dayTasks.length > 0 && (
|
||||
<Badge variant="secondary" className="mt-1 text-xs">
|
||||
@@ -296,7 +298,7 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
{filteredAndSortedTasks.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-muted-foreground" data-testid="text-no-week-tasks">
|
||||
{searchQuery || filterBy !== 'all'
|
||||
{searchQuery || filterBy !== 'all'
|
||||
? t('weekList.noMatchingTasks')
|
||||
: t('weekList.noTasks')
|
||||
}
|
||||
@@ -305,7 +307,7 @@ export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDe
|
||||
) : (
|
||||
<>
|
||||
<div className="text-sm font-medium text-muted-foreground mb-2">
|
||||
{t('weekList.tasksFor', { start: format(weekStart, 'MMM d'), end: format(weekEnd, 'MMM d') })}
|
||||
{t('weekList.tasksFor', { start: format(weekStart, 'MMM d', { locale: dateLocale }), end: format(weekEnd, 'MMM d', { locale: dateLocale }) })}
|
||||
</div>
|
||||
{filteredAndSortedTasks.map((task) => (
|
||||
<div key={task.id}>
|
||||
|
||||
Reference in New Issue
Block a user