Feature batch: Projects, Recurrence, Group settings, Bug fixes
- Projects CRUD API + invite members - Task recurrence (daily/weekly/monthly) with auto-creation - Group time zones + GPS locations settings - i18n fallback fix (no more undefined labels) - UX: action buttons in one row - Chat/Calendar: relative API URLs - DB: task_assignments, projects tables, recurrence column Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,10 +5,10 @@ import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
import { useAuth } from '@/lib/auth';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
||||
|
||||
interface Task {
|
||||
interface CalTask {
|
||||
id: string;
|
||||
title: string;
|
||||
scheduled_at: string | null;
|
||||
@@ -26,32 +26,61 @@ const LOCALE_MAP: Record<string, string> = {
|
||||
};
|
||||
|
||||
export default function CalendarPage() {
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
const [tasks, setTasks] = useState<CalTask[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { t, locale } = useTranslation();
|
||||
const { token } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_URL}/api/v1/tasks?limit=100`)
|
||||
.then(r => r.json())
|
||||
.then(d => setTasks(d.data || []));
|
||||
}, []);
|
||||
if (!token) {
|
||||
router.replace('/login');
|
||||
return;
|
||||
}
|
||||
fetch('/api/v1/tasks?limit=100', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
return r.json();
|
||||
})
|
||||
.then(d => setTasks(d.data || []))
|
||||
.catch(err => setError(err.message));
|
||||
}, [token, router]);
|
||||
|
||||
const events = tasks
|
||||
.filter((t): t is Task & { scheduled_at: string } | Task & { due_at: string } =>
|
||||
t.scheduled_at !== null || t.due_at !== null
|
||||
)
|
||||
.map(t => ({
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
start: (t.scheduled_at || t.due_at) as string,
|
||||
end: (t.due_at || t.scheduled_at) as string,
|
||||
backgroundColor: t.group_color || '#3B82F6',
|
||||
borderColor: t.group_color || '#3B82F6',
|
||||
extendedProps: { status: t.status, group: t.group_name }
|
||||
.filter((tk) => tk.scheduled_at !== null || tk.due_at !== null)
|
||||
.map(tk => ({
|
||||
id: tk.id,
|
||||
title: tk.title,
|
||||
start: (tk.scheduled_at || tk.due_at) as string,
|
||||
end: (tk.due_at || tk.scheduled_at) as string,
|
||||
backgroundColor: tk.group_color || '#3B82F6',
|
||||
borderColor: tk.group_color || '#3B82F6',
|
||||
extendedProps: { status: tk.status, group: tk.group_name },
|
||||
}));
|
||||
|
||||
// Build background events from unique groups
|
||||
const groupColors = new Map<string, string>();
|
||||
tasks.forEach(tk => {
|
||||
if (tk.group_name && tk.group_color) {
|
||||
groupColors.set(tk.group_name, tk.group_color);
|
||||
}
|
||||
});
|
||||
|
||||
if (!token) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-4">{t('calendar.title')}</h1>
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl p-4 shadow">
|
||||
<FullCalendar
|
||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||
@@ -59,7 +88,7 @@ export default function CalendarPage() {
|
||||
headerToolbar={{
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay',
|
||||
}}
|
||||
events={events}
|
||||
editable={true}
|
||||
|
||||
Reference in New Issue
Block a user