Taiga integration

This commit is contained in:
2026-06-09 12:47:13 +03:00
parent c8599b3d13
commit 1f83dcb574
30 changed files with 1543 additions and 115 deletions
+7 -4
View File
@@ -2,7 +2,7 @@ import { FormEvent, useEffect, useRef, useState } from "react";
import ReactMarkdown from "react-markdown";
import { api, ChatMessage, ChatSession } from "../api/client";
import PomodoroWidget from "../components/PomodoroWidget";
import { usePomodoro } from "../hooks/usePomodoro";
import { usePomodoro } from "../context/PomodoroContext";
import "./Chat.css";
function shouldShowMessage(msg: ChatMessage): boolean {
@@ -58,11 +58,14 @@ export default function Chat() {
useEffect(() => {
const seq = pomodoroStatus?.cycle?.chat_notify_seq ?? 0;
if (seq > lastNotifySeq && activeId) {
if (seq > lastNotifySeq) {
setLastNotifySeq(seq);
loadMessages(activeId).catch(console.error);
refreshPomodoro().catch(console.error);
if (activeId) {
loadMessages(activeId).catch(console.error);
}
}
}, [pomodoroStatus?.cycle?.chat_notify_seq, activeId, lastNotifySeq]);
}, [pomodoroStatus?.cycle?.chat_notify_seq, activeId, lastNotifySeq, refreshPomodoro]);
const handleNewChat = async () => {
const session = await api.createSession();
+36 -24
View File
@@ -1,11 +1,12 @@
import { FormEvent, useEffect, useState } from "react";
import { api, PomodoroHistoryItem, PomodoroStatus } from "../api/client";
import { phaseLabel } from "../utils/pomodoro";
import { api, PomodoroHistoryItem } from "../api/client";
import { usePomodoro } from "../context/PomodoroContext";
import { formatCycleLabel, phaseLabel } from "../utils/pomodoro";
import { formatTime } from "../utils/time";
import "./Pomodoro.css";
export default function Pomodoro() {
const [status, setStatus] = useState<PomodoroStatus | null>(null);
const { status, refresh } = usePomodoro();
const [history, setHistory] = useState<PomodoroHistoryItem[]>([]);
const [duration, setDuration] = useState(25);
const [taskNote, setTaskNote] = useState("");
@@ -13,32 +14,31 @@ export default function Pomodoro() {
const [completed, setCompleted] = useState(false);
const [error, setError] = useState("");
const refresh = async () => {
const [current, past] = await Promise.all([api.pomodoroStatus(), api.pomodoroHistory()]);
setStatus(current);
const loadHistory = async () => {
const past = await api.pomodoroHistory();
setHistory(past);
if (current.cycle?.work_duration_min) {
setDuration(current.cycle.work_duration_min);
}
if (current.cycle?.task_note) {
setTaskNote(current.cycle.task_note);
}
};
useEffect(() => {
refresh().catch(console.error);
const timer = setInterval(() => {
api.pomodoroStatus().then(setStatus).catch(console.error);
}, 1000);
return () => clearInterval(timer);
loadHistory().catch(console.error);
}, []);
useEffect(() => {
if (status?.cycle?.work_duration_min) {
setDuration(status.cycle.work_duration_min);
}
if (status?.cycle?.task_note) {
setTaskNote(status.cycle.task_note);
}
}, [status?.cycle?.work_duration_min, status?.cycle?.task_note]);
const handleStartWork = async (e: FormEvent) => {
e.preventDefault();
setError("");
try {
const data = await api.pomodoroStart(duration, taskNote);
setStatus(data);
await api.pomodoroStart(duration, taskNote);
await refresh();
await loadHistory();
setResult("");
setCompleted(false);
} catch (err) {
@@ -49,7 +49,8 @@ export default function Pomodoro() {
const handlePause = async () => {
setError("");
try {
setStatus(await api.pomodoroPause());
await api.pomodoroPause();
await refresh();
} catch (err) {
setError(err instanceof Error ? err.message : "Ошибка");
}
@@ -58,7 +59,8 @@ export default function Pomodoro() {
const handleResume = async () => {
setError("");
try {
setStatus(await api.pomodoroResume());
await api.pomodoroResume();
await refresh();
} catch (err) {
setError(err instanceof Error ? err.message : "Ошибка");
}
@@ -69,6 +71,7 @@ export default function Pomodoro() {
try {
await api.pomodoroStop(result, completed);
await refresh();
await loadHistory();
setResult("");
setCompleted(false);
} catch (err) {
@@ -81,6 +84,7 @@ export default function Pomodoro() {
try {
await api.pomodoroSkip();
await refresh();
await loadHistory();
} catch (err) {
setError(err instanceof Error ? err.message : "Ошибка");
}
@@ -91,6 +95,7 @@ export default function Pomodoro() {
try {
await api.pomodoroResetCycle(false);
await refresh();
await loadHistory();
} catch (err) {
setError(err instanceof Error ? err.message : "Ошибка");
}
@@ -102,6 +107,7 @@ export default function Pomodoro() {
? ((status.duration_min * 60 - status.remaining_seconds) / (status.duration_min * 60)) * 100
: 0;
const cycle = status?.cycle;
const cycleLabel = formatCycleLabel(cycle, status?.phase ?? "work", !!isActive);
const ringColor = status?.phase === "work" ? "#4f7cff" : "#3dbf8f";
return (
@@ -109,7 +115,7 @@ export default function Pomodoro() {
<section className="timer-card">
{cycle && (
<div className="cycle-badge">
Цикл {cycle.completed_work_sessions}/{cycle.sessions_until_long_break}
Цикл {cycleLabel}
{cycle.auto_advance && " · авто"}
</div>
)}
@@ -152,10 +158,16 @@ export default function Pomodoro() {
<button type="submit" className="primary-btn">
Старт работы
</button>
<button type="button" onClick={() => api.pomodoroStartShortBreak().then(setStatus)}>
<button
type="button"
onClick={() => api.pomodoroStartShortBreak().then(() => refresh())}
>
Короткий перерыв
</button>
<button type="button" onClick={() => api.pomodoroStartLongBreak().then(setStatus)}>
<button
type="button"
onClick={() => api.pomodoroStartLongBreak().then(() => refresh())}
>
Длинный перерыв
</button>
</div>