import { useState, useEffect } from 'react'; import { t } from '../i18n'; import type { GoalEntry, GoalMilestone, GoalLoopState } from '../types'; interface GoalLoopConfig { schedule: string; delivery_chat_id: string; delivery_platform: 'telegram' | 'discord'; } // ── Helpers ────────────────────────────────────────────────────────────────── const PRIORITY_LABELS: Record<1 | 2 | 3, string> = { 1: 'Low', 2: 'Medium', 3: 'High', }; const PRIORITY_COLORS: Record<1 | 2 | 3, string> = { 1: 'bg-gray-700 text-gray-400', 2: 'bg-blue-900/50 text-blue-300', 3: 'bg-orange-900/50 text-orange-300', }; const STATUS_COLORS: Record = { active: 'bg-green-900/50 text-green-300', paused: 'bg-yellow-900/50 text-yellow-300', completed: 'bg-gray-700 text-gray-400', }; function formatDatetime(ts: string | null): string { if (!ts) return '—'; // Reject cron expressions or non-ISO strings (e.g. "0 8 * * *") if (!/^\d{4}-\d{2}-\d{2}/.test(ts)) return '—'; try { const d = new Date(ts); if (isNaN(d.getTime())) return '—'; return d.toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }); } catch { return ts; } } function MilestoneProgress({ milestones }: { milestones: GoalMilestone[] }) { if (milestones.length === 0) return null; const done = milestones.filter(m => m.done).length; const pct = Math.round((done / milestones.length) * 100); return (
{done}/{milestones.length} {t('goals.milestones') || 'milestones'} {pct}%
); } // ── Create Goal Form ────────────────────────────────────────────────────────── interface CreateGoalFormProps { onSave: (goal: GoalEntry) => void; onCancel: () => void; } function CreateGoalForm({ onSave, onCancel }: CreateGoalFormProps) { const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [priority, setPriority] = useState<1 | 2 | 3>(2); const [tags, setTags] = useState(''); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!title.trim()) { setError(t('goals.titleRequired') || 'Title is required'); return; } setSaving(true); setError(''); try { const res = await fetch('/api/goals', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: title.trim(), description: description.trim(), priority, tags: tags.split(',').map(s => s.trim()).filter(Boolean), }), }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const created: GoalEntry = await res.json(); onSave(created); } catch (err) { setError(`${t('goals.saveFailed') || 'Save failed'}: ${(err as Error).message}`); } finally { setSaving(false); } }; return (

{t('goals.createGoal') || 'New Goal'}

setTitle(e.target.value)} placeholder="Learn TypeScript deeply" className="input-base w-full" />