diff --git a/src/app/(dashboard)/bookmarks/page.tsx b/src/app/(dashboard)/bookmarks/page.tsx index 4d6ffb3..0c2c3fa 100644 --- a/src/app/(dashboard)/bookmarks/page.tsx +++ b/src/app/(dashboard)/bookmarks/page.tsx @@ -67,16 +67,16 @@ export default function BookmarksPage() { ]; return ( -
-
-

Bookmarks

-

Все ссылки в одном месте

+
+
+

Bookmarks

+

Все ссылки в одном месте

{categories.map((cat) => (
-

+

{cat.emoji} {cat.label}

@@ -88,11 +88,11 @@ export default function BookmarksPage() { href={link.url} target="_blank" rel="noopener noreferrer" - className="card p-4 hover:-translate-y-1 transition-all duration-200 group" + className="card group p-4 hover:border-white/15" >
{link.emoji} -
+
{link.name}
diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index 53ae786..9b025c0 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -1,22 +1,13 @@ -import { auth } from "@/auth"; -import { redirect } from "next/navigation"; import { Sidebar } from "@/components/layout/Sidebar"; -export default async function DashboardLayout({ - children, -}: { - children: React.ReactNode; -}) { - const session = await auth(); - if (!session) { - redirect("/auth/signin"); - } - +export default function DashboardLayout({ children }: { children: React.ReactNode }) { return ( -
- -
- {children} +
+ +
+
+ {children} +
); diff --git a/src/app/(dashboard)/page.tsx b/src/app/(dashboard)/page.tsx index 6dd0013..3f4924d 100644 --- a/src/app/(dashboard)/page.tsx +++ b/src/app/(dashboard)/page.tsx @@ -1,34 +1,35 @@ -export const dynamic = "force-dynamic"; import { WeatherWidget } from "@/components/widgets/WeatherWidget"; -import { TasksWidget } from "@/components/widgets/TasksWidget"; import { CalendarWidget } from "@/components/widgets/CalendarWidget"; +import { TasksWidget } from "@/components/widgets/TasksWidget"; 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() { return ( -
+
{/* Weather - full width */} - {/* Calendar + Tasks */} + {/* Calendar + Tasks + Claude */}
-
+
+
+ + +
- {/* Claude row */} -
- - -
+ {/* Services */} +
); } diff --git a/src/app/(dashboard)/system/page.tsx b/src/app/(dashboard)/system/page.tsx index 74d3208..d8d0a37 100644 --- a/src/app/(dashboard)/system/page.tsx +++ b/src/app/(dashboard)/system/page.tsx @@ -6,7 +6,7 @@ const TABS = ["Openclaw", "Сервисы"] as const; type TabName = typeof TABS[number]; const MACHINE_MAP: Record = { - "Openclaw": "openclaw", + "Openclaw": "ocplatform", "Сервисы": "services", }; @@ -29,13 +29,20 @@ function CircularGauge({ value, size = 96, color, label }: { value: number; size
+ + + + + +
@@ -54,7 +61,7 @@ function UsageBar({ label, value, color, detail }: { label: string; value: numbe {label} {detail || `${value}%`}
-
+
-
+
+ {/* Header */} +
-

System Monitor

-

+

System Monitor

+

{lastUpdated ? `Обновлено: ${lastUpdated.toLocaleTimeString("ru-RU")}` : "Загрузка..."}

{/* Tabs */} -
+
{TABS.map((tab) => (
-
); diff --git a/src/components/widgets/ClaudeApiWidget.tsx b/src/components/widgets/ClaudeApiWidget.tsx index 4dab0b0..cc65ebe 100644 --- a/src/components/widgets/ClaudeApiWidget.tsx +++ b/src/components/widgets/ClaudeApiWidget.tsx @@ -11,16 +11,15 @@ export function ClaudeApiWidget() { }; return ( -
-
-
-
- A +
+
+
+
+ 🔮
Claude API
-
Usage & Cost
+
Cost & Usage
-
+
{[ { label: "Стоимость", value: "—", color: "text-violet-400" }, { label: "Запросы", value: "—", color: "text-white" }, { label: "Токены", value: "—", color: "text-white" }, ].map((item) => ( -
-
{item.value}
-
{item.label}
+
+ {item.label} + {item.value}
))}
-
+
- Автоматическое получение данных недоступно + Автополучение недоступно
); diff --git a/src/components/widgets/ClaudeUsageWidget.tsx b/src/components/widgets/ClaudeUsageWidget.tsx index a6a102b..447c3c2 100644 --- a/src/components/widgets/ClaudeUsageWidget.tsx +++ b/src/components/widgets/ClaudeUsageWidget.tsx @@ -11,15 +11,14 @@ export function ClaudeUsageWidget() { }; return ( -
-
-
-
- C +
+
+
+
+
-
Claude Subscription
+
Claude Sub
Usage
@@ -28,22 +27,22 @@ export function ClaudeUsageWidget() {
-
+
{[ { label: "Использовано", value: "—", color: "text-amber-400" }, { label: "Лимит", value: "—", color: "text-white" }, { label: "Сброс", value: "—", color: "text-white" }, ].map((item) => ( -
-
{item.value}
-
{item.label}
+
+ {item.label} + {item.value}
))}
-
+
- Автоматическое получение данных недоступно + Автополучение недоступно
); diff --git a/src/components/widgets/DashboardHeader.tsx b/src/components/widgets/DashboardHeader.tsx index ec04903..7d147cd 100644 --- a/src/components/widgets/DashboardHeader.tsx +++ b/src/components/widgets/DashboardHeader.tsx @@ -1,48 +1,35 @@ "use client"; -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; + +const MONTHS = ["января","февраля","марта","апреля","мая","июня","июля","августа","сентября","октября","ноября","декабря"]; +const DAYS = ["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"]; export function DashboardHeader() { - const [time, setTime] = useState(new Date()); - + const [now, setNow] = useState(new Date()); useEffect(() => { - const i = setInterval(() => setTime(new Date()), 1000); + const i = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(i); }, []); - const hour = time.getHours(); - const greeting = - hour < 6 ? "Доброй ночи" : - hour < 12 ? "Доброе утро" : - hour < 17 ? "Добрый день" : - hour < 22 ? "Добрый вечер" : - "Доброй ночи"; + const hour = now.getHours(); + const greeting = hour < 6 ? "Доброй ночи" : hour < 12 ? "Доброе утро" : hour < 17 ? "Добрый день" : "Добрый вечер"; return ( -
+
-

- {greeting},{" "} - Daniil{" "} - 👋 +

+ {greeting}, Daniil 👋

-

- {time.toLocaleDateString("ru-RU", { - weekday: "long", - day: "numeric", - month: "long", - year: "numeric", - })} - {" · "} - {time.toLocaleTimeString("ru-RU", { - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - })} +

+ {DAYS[now.getDay()]}, {now.getDate()} {MONTHS[now.getMonth()]} {now.getFullYear()}

-
-
- digital-home.site +
+
+ {now.toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" })} +
+
+ {now.toLocaleTimeString("ru-RU", { second: "2-digit" })} сек
diff --git a/src/components/widgets/ServicesGrid.tsx b/src/components/widgets/ServicesGrid.tsx index 9caa478..aa83e2b 100644 --- a/src/components/widgets/ServicesGrid.tsx +++ b/src/components/widgets/ServicesGrid.tsx @@ -85,36 +85,44 @@ function ServiceCard({ service }: { service: Service }) { href={service.url} target="_blank" rel="noopener noreferrer" - className="card p-4 flex items-center gap-3 hover:-translate-y-1 transition-all duration-200 group" + className="card group flex items-center gap-3 p-4 hover:border-white/15" > -
+ {/* Icon */} +
{service.icon ? ( // eslint-disable-next-line @next/next/no-img-element - {service.name} { - (e.target as HTMLImageElement).style.display = "none"; - const parent = (e.target as HTMLImageElement).parentElement; - if (parent) parent.textContent = service.emoji ?? "🔗"; - }} /> + {service.name} { + (e.target as HTMLImageElement).style.display = "none"; + const parent = (e.target as HTMLImageElement).parentElement; + if (parent) parent.textContent = service.emoji ?? "🔗"; + }} + /> ) : ( {service.emoji ?? "🔗"} )}
+ {/* Info */}
- {service.name} + + {service.name} +

{service.desc}

-
+ {/* Status */} +
{status === "online" && ( - + )}
@@ -123,15 +131,17 @@ function ServiceCard({ service }: { service: Service }) { export function ServicesGrid() { return ( -
-
+
+

Сервисы

{SERVICE_CATEGORIES.map((cat) => (
-

{cat.label}

-
+

+ {cat.label} +

+
{cat.services.map((svc) => ( ))} diff --git a/src/components/widgets/TasksWidget.tsx b/src/components/widgets/TasksWidget.tsx index 4941aea..1c82464 100644 --- a/src/components/widgets/TasksWidget.tsx +++ b/src/components/widgets/TasksWidget.tsx @@ -36,10 +36,12 @@ export function TasksWidget() { }; return ( -
+
- +
+ +
Задачи
-
- - {error ? ( -
Нет данных о погоде
- ) : loading ? ( -
+ {loading ? ( +
@@ -70,62 +48,78 @@ export function WeatherWidget() {
-
{[...Array(7)].map((_,i) =>
)}
+
{[...Array(7)].map((_,i) =>
)}
- ) : c && day ? ( - <> - {/* Current weather */} -
+ ) : error ? ( +
Нет данных о погоде
+ ) : data && ( +
+ {/* Current */} +
-
- - {selected === 0 ? c.temp : day.maxTemp}° +

Санкт-Петербург

+
+ + {selected === 0 ? data.current.temp : data.forecast[selected].maxTemp}° - {selected === 0 ? c.icon : day.icon} +
+ {selected === 0 ? data.current.icon : data.forecast[selected].icon} +
-

{selected === 0 ? c.desc : day.desc}

- {selected === 0 && ( -

Ощущается как {c.feelsLike}°

+

{selected === 0 ? data.current.desc : data.forecast[selected].desc}

+ {selected !== 0 && ( +

мин {data.forecast[selected].minTemp}°

)}
{selected === 0 && ( -
-
- Влажность - 💧 {c.humidity}% +
+
+ 🌡️ + Ощущ. {data.current.feelsLike}°
-
- Ветер - 💨 {c.windKmh} км/ч +
+ 💧 + {data.current.humidity}%
+
+ 💨 + {data.current.windKmh} км/ч +
+
)}
{/* 7-day forecast */}
- {data!.forecast.map((f, i) => ( - - ))} + {data.forecast.map((day: DayForecast, i: number) => { + const d = new Date(day.date + "T12:00:00"); + return ( + + ); + })}
- - ) : null} +
+ )}
); diff --git a/tailwind.config.ts b/tailwind.config.ts index acc4e7a..4e2728a 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -36,10 +36,10 @@ const config: Config = { foreground: "hsl(var(--card-foreground))", }, dash: { - bg: "#0a0a0f", - surface: "#111118", - elevated: "#1a1a24", - border: "rgba(255,255,255,0.06)", + bg: "#080810", + surface: "#0f0f1a", + elevated: "#161624", + border: "rgba(255,255,255,0.07)", }, }, backgroundImage: { @@ -65,6 +65,10 @@ const config: Config = { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, + "ping-slow": { + "0%, 100%": { opacity: "0.8", transform: "scale(1)" }, + "50%": { opacity: "0.3", transform: "scale(1.5)" }, + }, glow: { "0%": { boxShadow: "0 0 5px rgba(99,102,241,0.3)" }, "100%": { boxShadow: "0 0 20px rgba(99,102,241,0.6)" }, @@ -73,18 +77,14 @@ const config: Config = { "0%, 100%": { transform: "translateY(0px)" }, "50%": { transform: "translateY(-6px)" }, }, - "status-pulse": { - "0%, 100%": { opacity: "1", transform: "scale(1)" }, - "50%": { opacity: "0.4", transform: "scale(1.5)" }, - }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", + "ping-slow": "ping-slow 2s ease-in-out infinite", "pulse-slow": "pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite", glow: "glow 2s ease-in-out infinite alternate", float: "float 6s ease-in-out infinite", - "status-pulse": "status-pulse 2s ease-in-out infinite", }, }, },