Phase 2: AI Chat, Calendar, Odoo connector, PWA

- AI Chat endpoint (/api/v1/chat) with Claude API context
- Calendar page with FullCalendar.js (day/week/month)
- Odoo API connector (import/export/webhook)
- PWA manifest + service worker for offline
- SW register component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude CLI Agent
2026-03-29 10:12:53 +00:00
parent b5a6dd6d6f
commit 55993b70b2
16 changed files with 402 additions and 24 deletions

View File

@@ -0,0 +1,67 @@
'use client';
import { useState, useEffect } from 'react';
import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction';
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
interface Task {
id: string;
title: string;
scheduled_at: string | null;
due_at: string | null;
status: string;
group_name: string;
group_color: string;
}
export default function CalendarPage() {
const [tasks, setTasks] = useState<Task[]>([]);
useEffect(() => {
fetch(`${API_URL}/api/v1/tasks?limit=100`)
.then(r => r.json())
.then(d => setTasks(d.data || []));
}, []);
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 }
}));
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">Kalendar</h1>
<div className="bg-white dark:bg-gray-800 rounded-xl p-4 shadow">
<FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView="timeGridWeek"
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
}}
events={events}
editable={true}
selectable={true}
locale="cs"
firstDay={1}
height="auto"
slotMinTime="06:00:00"
slotMaxTime="23:00:00"
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,10 @@
"use client";
import { useEffect } from "react";
export default function SWRegister() {
useEffect(() => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js").catch(() => {});
}
}, []);
return null;
}