fix: remove services from main, compact calendar + large events panel
All checks were successful
Build & Deploy Dashboard / deploy (push) Successful in 1m11s

This commit is contained in:
Cosmo
2026-04-16 09:15:55 +00:00
parent 8321fc9252
commit 254ecbfd14
2 changed files with 142 additions and 143 deletions

View File

@@ -4,7 +4,6 @@ import { WeatherWidget } from "@/components/widgets/WeatherWidget";
import { CalendarWidget } from "@/components/widgets/CalendarWidget";
import { ClaudeUsageWidget } from "@/components/widgets/ClaudeUsageWidget";
import { ClaudeApiWidget } from "@/components/widgets/ClaudeApiWidget";
import { ServicesGrid } from "@/components/widgets/ServicesGrid";
import { DashboardHeader } from "@/components/widgets/DashboardHeader";
export default function DashboardPage() {
@@ -21,15 +20,10 @@ export default function DashboardPage() {
<CalendarWidget />
</div>
<div className="space-y-5">
<div className="grid grid-cols-2 gap-5 lg:grid-cols-1">
<ClaudeUsageWidget />
<ClaudeApiWidget />
</div>
</div>
</div>
{/* Services */}
<ServicesGrid />
</div>
);
}

View File

@@ -27,7 +27,6 @@ export function CalendarWidget() {
const [newEvent, setNewEvent] = useState({ title: "", startTime: "09:00", endTime: "10:00", allDay: false });
const [creating, setCreating] = useState(false);
const [createError, setCreateError] = useState("");
const [calError, setCalError] = useState<string | null>(null);
const monthStr = `${year}-${String(month + 1).padStart(2, "0")}`;
@@ -35,8 +34,6 @@ export function CalendarWidget() {
try {
const res = await fetch(`/api/calendar?month=${monthStr}`);
const data = await res.json();
if (data.error && data.events?.length === 0) setCalError(data.error);
else setCalError(null);
setMonthEvents(data.events ?? []);
} catch { setMonthEvents([]); }
}, [monthStr]);
@@ -68,7 +65,6 @@ export function CalendarWidget() {
const daysInMonth = new Date(year, month + 1, 0).getDate();
const startOffset = firstDay === 0 ? 6 : firstDay - 1;
const cells = Array(startOffset).fill(null).concat(Array.from({ length: daysInMonth }, (_, i) => i + 1));
const datesWithEvents = new Set(monthEvents.map(e => (e.start || "").split("T")[0]));
const createEvent = async () => {
@@ -86,42 +82,36 @@ export function CalendarWidget() {
setNewEvent({ title: "", startTime: "09:00", endTime: "10:00", allDay: false });
fetchDay(selectedDate);
fetchMonth();
} catch (e: unknown) { setCreateError(e instanceof Error ? e.message : "Ошибка создания"); }
} catch (e: unknown) { setCreateError(e instanceof Error ? e.message : "Ошибка"); }
finally { setCreating(false); }
};
const todayStr = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,"0")}-${String(today.getDate()).padStart(2,"0")}`;
if (calError === "Google Calendar not configured") {
return (
<div className="card card-accent-emerald p-5 flex flex-col gap-4 items-center justify-center min-h-[200px]">
<div className="text-4xl">📅</div>
<div className="text-sm font-medium text-white">Google Calendar</div>
<div className="text-xs text-slate-400 text-center">Не настроен. Добавь переменные окружения в Coolify.</div>
</div>
);
}
<div className="card card-violet flex flex-col gap-0" style={{ borderTop: "2px solid #8b5cf6" }}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-0 divide-x divide-white/5">
return (
<div className="card card-accent-violet p-5 flex flex-col gap-4">
{/* Header */}
{/* LEFT: compact calendar */}
<div className="p-4 flex flex-col gap-3">
{/* Month nav */}
<div className="flex items-center justify-between">
<button onClick={prevMonth} className="p-1.5 rounded-lg hover:bg-white/5 text-slate-500 hover:text-white transition-colors">
<button onClick={prevMonth} className="p-1 rounded-lg hover:bg-white/5 text-slate-500 hover:text-white transition-colors">
<ChevronLeft className="w-4 h-4" />
</button>
<span className="text-sm font-semibold gradient-text">{MONTHS[month]} {year}</span>
<button onClick={nextMonth} className="p-1.5 rounded-lg hover:bg-white/5 text-slate-500 hover:text-white transition-colors">
<button onClick={nextMonth} className="p-1 rounded-lg hover:bg-white/5 text-slate-500 hover:text-white transition-colors">
<ChevronRight className="w-4 h-4" />
</button>
</div>
{/* Weekday labels */}
{/* Grid */}
<div className="grid grid-cols-7 gap-0.5">
{WEEK_DAYS.map(d => (
<div key={d} className="text-center text-[10px] font-semibold text-slate-600 py-1">{d}</div>
<div key={d} className="text-center text-[9px] font-semibold text-slate-600 pb-1">{d}</div>
))}
{cells.map((day, i) => {
if (!day) return <div key={`empty-${i}`} />;
if (!day) return <div key={`e-${i}`} />;
const dateStr = `${year}-${String(month+1).padStart(2,"0")}-${String(day).padStart(2,"0")}`;
const isToday = dateStr === todayStr;
const isSelected = dateStr === selectedDate;
@@ -130,61 +120,67 @@ export function CalendarWidget() {
<button
key={dateStr}
onClick={() => handleDayClick(day)}
className={`relative flex flex-col items-center justify-center aspect-square rounded-xl text-sm transition-all ${
isSelected
? "text-white font-semibold"
: isToday
? "text-violet-300 font-semibold"
: "text-slate-400 hover:text-white hover:bg-white/5"
className={`relative flex flex-col items-center justify-center rounded-lg text-xs transition-all py-1.5 ${
isSelected ? "text-white font-semibold" :
isToday ? "text-violet-300 font-semibold" :
"text-slate-400 hover:text-white hover:bg-white/5"
}`}
style={isSelected ? { background: "linear-gradient(135deg,#6366f1,#8b5cf6)" } :
isToday ? { background: "rgba(139,92,246,0.15)", boxShadow: "inset 0 0 0 1px rgba(139,92,246,0.4)" } : {}}
>
{day}
{hasEvents && (
<span className={`absolute bottom-1 w-1 h-1 rounded-full ${isSelected ? "bg-white" : "bg-violet-400"}`} />
<span className={`absolute bottom-0.5 w-1 h-1 rounded-full ${isSelected ? "bg-white" : "bg-violet-400"}`} />
)}
</button>
);
})}
</div>
</div>
{/* Day events panel */}
{selectedDate && (
<div className="border-t border-white/5 pt-3 space-y-2">
{/* RIGHT: events for selected day */}
<div className="p-4 flex flex-col gap-3 min-h-[300px]">
{!selectedDate ? (
<div className="flex-1 flex flex-col items-center justify-center text-slate-600 gap-2">
<span className="text-3xl">📅</span>
<span className="text-xs">Выбери день</span>
</div>
) : (
<>
{/* Day header */}
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-slate-400">
<span className="text-sm font-semibold text-white">
{new Date(selectedDate + "T12:00:00").toLocaleDateString("ru-RU", { day: "numeric", month: "long" })}
</span>
<button
onClick={() => setShowCreate(s => !s)}
className="flex items-center gap-1 text-xs text-violet-400 hover:text-violet-300 transition-colors"
className="flex items-center gap-1 text-xs text-violet-400 hover:text-violet-300 transition-colors px-2 py-1 rounded-lg hover:bg-white/5"
>
<Plus className="w-3 h-3" /> Добавить
</button>
</div>
{/* Create form */}
{showCreate && (
<div className="bg-white/3 rounded-xl p-3 space-y-2 border border-white/5">
<div className="bg-white/3 rounded-xl p-3 space-y-2 border border-white/5 flex-shrink-0">
<input
className="w-full bg-white/5 rounded-lg px-3 py-2 text-sm text-white placeholder-slate-600 outline-none focus:ring-1 focus:ring-violet-500 border border-white/5"
placeholder="Название события"
value={newEvent.title}
onChange={e => setNewEvent(n => ({ ...n, title: e.target.value }))}
onKeyDown={e => e.key === "Enter" && createEvent()}
autoFocus
/>
<div className="flex items-center gap-2">
<label className="flex items-center gap-1.5 text-xs text-slate-400 cursor-pointer">
<input type="checkbox" className="accent-violet-500" checked={newEvent.allDay}
onChange={e => setNewEvent(n => ({ ...n, allDay: e.target.checked }))} />
Весь день
</label>
</div>
{!newEvent.allDay && (
<div className="flex gap-2">
<div className="flex gap-2 items-center">
<input type="time" value={newEvent.startTime} onChange={e => setNewEvent(n => ({ ...n, startTime: e.target.value }))}
className="flex-1 bg-white/5 rounded-lg px-2 py-1.5 text-xs text-white outline-none focus:ring-1 focus:ring-violet-500" />
<span className="text-slate-600 self-center"></span>
<span className="text-slate-600 text-xs"></span>
<input type="time" value={newEvent.endTime} onChange={e => setNewEvent(n => ({ ...n, endTime: e.target.value }))}
className="flex-1 bg-white/5 rounded-lg px-2 py-1.5 text-xs text-white outline-none focus:ring-1 focus:ring-violet-500" />
</div>
@@ -194,7 +190,7 @@ export function CalendarWidget() {
<button onClick={createEvent} disabled={creating || !newEvent.title.trim()}
className="flex-1 text-white text-xs py-2 rounded-lg transition-colors disabled:opacity-50"
style={{ background: "linear-gradient(135deg,#6366f1,#8b5cf6)" }}>
{creating ? "Создаю..." : "Создать"}
{creating ? "Создаю..." : "Создать в Google Calendar"}
</button>
<button onClick={() => setShowCreate(false)} className="p-2 rounded-lg hover:bg-white/5 text-slate-500 hover:text-white">
<X className="w-3.5 h-3.5" />
@@ -203,38 +199,47 @@ export function CalendarWidget() {
</div>
)}
{/* Events list */}
<div className="flex-1 overflow-y-auto space-y-2">
{loadingDay ? (
<div className="space-y-1.5 animate-pulse">
{[1,2].map(i => <div key={i} className="h-8 bg-white/5 rounded-lg" />)}
<div className="space-y-2 animate-pulse">
{[1,2,3].map(i => <div key={i} className="h-14 bg-white/5 rounded-xl" />)}
</div>
) : dayEvents.length === 0 ? (
<div className="text-xs text-slate-600 text-center py-2">Нет событий</div>
<div className="flex flex-col items-center justify-center h-full text-slate-600 gap-2 py-8">
<span className="text-2xl">🗓</span>
<span className="text-xs">Нет событий</span>
<button onClick={() => setShowCreate(true)} className="text-xs text-violet-400 hover:text-violet-300 mt-1">
+ Создать событие
</button>
</div>
) : (
<div className="space-y-1.5 max-h-40 overflow-y-auto">
{dayEvents.map(ev => (
<div key={ev.id} className="flex items-start gap-2 p-2.5 rounded-xl bg-white/3 border border-white/5 group">
<div className="w-0.5 h-full min-h-[32px] rounded-full flex-shrink-0"
style={{ background: "linear-gradient(to bottom, #8b5cf6, #6366f1)" }} />
dayEvents.map(ev => (
<div key={ev.id} className="flex items-start gap-3 p-3 rounded-xl bg-white/3 border border-white/5 group hover:bg-white/5 transition-colors">
<div className="w-1 self-stretch rounded-full flex-shrink-0 mt-0.5"
style={{ background: "linear-gradient(to bottom,#8b5cf6,#6366f1)", minHeight: "32px" }} />
<div className="flex-1 min-w-0">
<div className="text-xs font-medium text-white truncate">{ev.title}</div>
<div className="text-[10px] text-slate-500 flex items-center gap-1 mt-0.5">
<Clock className="w-2.5 h-2.5" />
<div className="text-sm font-medium text-white truncate">{ev.title}</div>
<div className="text-xs text-slate-500 flex items-center gap-1 mt-0.5">
<Clock className="w-3 h-3" />
{ev.allDay ? "Весь день" : formatTime(ev.start)}
{ev.end && !ev.allDay && `${formatTime(ev.end)}`}
</div>
</div>
{ev.htmlLink && (
<a href={ev.htmlLink} target="_blank" rel="noopener noreferrer"
className="opacity-0 group-hover:opacity-100 text-slate-600 hover:text-white transition-all">
<ExternalLink className="w-3 h-3" />
className="opacity-0 group-hover:opacity-100 text-slate-600 hover:text-violet-400 transition-all flex-shrink-0">
<ExternalLink className="w-4 h-4" />
</a>
)}
</div>
))}
</div>
))
)}
</div>
</>
)}
</div>
</div>
</div>
);
}