diff --git a/app/hosts/[hostname]/components/ScreenTimeOverviewChart.tsx b/app/hosts/[hostname]/components/ScreenTimeOverviewChart.tsx index e1ee288..649aacd 100644 --- a/app/hosts/[hostname]/components/ScreenTimeOverviewChart.tsx +++ b/app/hosts/[hostname]/components/ScreenTimeOverviewChart.tsx @@ -24,30 +24,27 @@ interface ScreenTimeOverviewChartProps { loading: boolean; refreshing: boolean; onSelectDate: (date: string, nextUnit?: 'day' | 'week') => void; - onSpotlightAppClick: (processId: string) => void; } -interface WeekChartDatum { +const SPOTLIGHT_COLORS = ['#2f7cff', '#17c3d1', '#ff9f43', '#ff6b6b'] as const; +const SPOTLIGHT_KEYS = ['spotlight_0', 'spotlight_1', 'spotlight_2', 'spotlight_3'] as const; +type SpotlightKey = typeof SPOTLIGHT_KEYS[number]; + +type WeekChartDatum = Record & { date: string; label: string; totalDurationSeconds: number; mutedDuration: number; selectedRemainder: number; - spotlight_0: number; - spotlight_1: number; - spotlight_2: number; isSelected: boolean; -} +}; -interface HourChartDatum { +type HourChartDatum = Record & { hour: number; label: string; totalDurationSeconds: number; - spotlight_0: number; - spotlight_1: number; - spotlight_2: number; otherDurationSeconds: number; -} +}; interface TooltipPayloadRow { payload: T; @@ -62,11 +59,10 @@ interface RoundedBarShapeProps { payload?: Record; } -const SPOTLIGHT_COLORS = ['#2f7cff', '#17c3d1', '#ff9f43']; const OTHER_COLOR = '#d4d7df'; const MUTED_BAR_COLOR = '#cfd3dc'; -const WEEK_STACK_KEYS = ['mutedDuration', 'spotlight_0', 'spotlight_1', 'spotlight_2', 'selectedRemainder'] as const; -const HOUR_STACK_KEYS = ['spotlight_0', 'spotlight_1', 'spotlight_2', 'otherDurationSeconds'] as const; +const WEEK_STACK_KEYS = ['mutedDuration', ...SPOTLIGHT_KEYS, 'selectedRemainder'] as const; +const HOUR_STACK_KEYS = [...SPOTLIGHT_KEYS, 'otherDurationSeconds'] as const; const getNumericValue = (value: unknown) => { return typeof value === 'number' && Number.isFinite(value) ? value : 0; @@ -218,10 +214,10 @@ function HourTooltip({ if (!active || !payload || payload.length === 0) return null; const datum = payload[0].payload; - const rows = [0, 1, 2] - .map((index) => ({ - label: spotlightNames[index], - value: datum[`spotlight_${index}` as keyof HourChartDatum] as number, + const rows: Array<{ label: string; value: number; color: string }> = SPOTLIGHT_KEYS + .map((key, index) => ({ + label: spotlightNames[index] ?? '', + value: datum[key], color: SPOTLIGHT_COLORS[index] })) .filter((row) => row.label && row.value > 0); @@ -261,7 +257,6 @@ export default function ScreenTimeOverviewChart({ loading, refreshing, onSelectDate, - onSpotlightAppClick }: ScreenTimeOverviewChartProps) { const weekChartData = useMemo(() => { if (!data) return [] as WeekChartDatum[]; @@ -269,32 +264,46 @@ export default function ScreenTimeOverviewChart({ const spotlightTotal = data.overview.selectedDay.spotlightApps .reduce((sum, app) => sum + app.durationSeconds, 0); - return data.overview.week.days.map((day) => ({ - date: day.date, - label: day.weekdayLabel, - totalDurationSeconds: day.totalDurationSeconds, - mutedDuration: day.isSelected ? 0 : day.totalDurationSeconds, - selectedRemainder: day.isSelected ? Math.max(day.totalDurationSeconds - spotlightTotal, 0) : 0, - spotlight_0: day.isSelected ? (data.overview.selectedDay.spotlightApps[0]?.durationSeconds ?? 0) : 0, - spotlight_1: day.isSelected ? (data.overview.selectedDay.spotlightApps[1]?.durationSeconds ?? 0) : 0, - spotlight_2: day.isSelected ? (data.overview.selectedDay.spotlightApps[2]?.durationSeconds ?? 0) : 0, - isSelected: day.isSelected - })); + return data.overview.week.days.map((day) => { + const spotlightDurations = Object.fromEntries( + SPOTLIGHT_KEYS.map((key, index) => [ + key, + day.isSelected ? (data.overview.selectedDay.spotlightApps[index]?.durationSeconds ?? 0) : 0 + ]) + ) as Record; + + return { + date: day.date, + label: day.weekdayLabel, + totalDurationSeconds: day.totalDurationSeconds, + mutedDuration: day.isSelected ? 0 : day.totalDurationSeconds, + selectedRemainder: day.isSelected ? Math.max(day.totalDurationSeconds - spotlightTotal, 0) : 0, + ...spotlightDurations, + isSelected: day.isSelected + }; + }); }, [data]); const hourChartData = useMemo(() => { if (!data) return [] as HourChartDatum[]; const spotlightApps = data.overview.selectedDay.spotlightApps; - return data.overview.selectedDay.hours.map((hour) => ({ - hour: hour.hour, - label: hour.label, - totalDurationSeconds: hour.totalDurationSeconds, - spotlight_0: spotlightApps[0] ? hour.spotlightDurations[spotlightApps[0].processId] ?? 0 : 0, - spotlight_1: spotlightApps[1] ? hour.spotlightDurations[spotlightApps[1].processId] ?? 0 : 0, - spotlight_2: spotlightApps[2] ? hour.spotlightDurations[spotlightApps[2].processId] ?? 0 : 0, - otherDurationSeconds: hour.otherDurationSeconds - })); + return data.overview.selectedDay.hours.map((hour) => { + const spotlightDurations = Object.fromEntries( + SPOTLIGHT_KEYS.map((key, index) => [ + key, + spotlightApps[index] ? hour.spotlightDurations[spotlightApps[index].processId] ?? 0 : 0 + ]) + ) as Record; + + return { + hour: hour.hour, + label: hour.label, + totalDurationSeconds: hour.totalDurationSeconds, + ...spotlightDurations, + otherDurationSeconds: hour.otherDurationSeconds + }; + }); }, [data]); const spotlightNames = data?.overview.selectedDay.spotlightApps.map((app) => app.processName) ?? []; @@ -402,12 +411,12 @@ export default function ScreenTimeOverviewChart({ /> ))} - {[0, 1, 2].map((index) => ( + {SPOTLIGHT_KEYS.map((key, index) => ( { const day = data.overview.week.days[clickedIndex]; if (day) { @@ -417,7 +426,7 @@ export default function ScreenTimeOverviewChart({ > {weekChartData.map((entry) => ( @@ -477,13 +486,13 @@ export default function ScreenTimeOverviewChart({ tickFormatter={formatAxisDuration} /> } cursor={{ fill: 'rgba(148, 163, 184, 0.08)' }} /> - {[0, 1, 2].map((index) => ( + {SPOTLIGHT_KEYS.map((key, index) => ( ))} -
+
{data.overview.selectedDay.spotlightApps.length > 0 ? data.overview.selectedDay.spotlightApps.map((app, index) => ( )) : ( -
+
暂无应用数据
)} diff --git a/app/hosts/[hostname]/components/ScreenTimeTab.tsx b/app/hosts/[hostname]/components/ScreenTimeTab.tsx index ca15164..c8cedf3 100644 --- a/app/hosts/[hostname]/components/ScreenTimeTab.tsx +++ b/app/hosts/[hostname]/components/ScreenTimeTab.tsx @@ -28,7 +28,6 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [expandedApps, setExpandedApps] = useState>(new Set()); - const [highlightedProcessId, setHighlightedProcessId] = useState(null); const [initialLoading, setInitialLoading] = useState(true); const abortControllerRef = React.useRef(null); @@ -125,11 +124,6 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) { setSelectedDate(date); }; - const handleSpotlightAppClick = (processId: string) => { - setHighlightedProcessId(processId); - setExpandedApps((previous) => new Set(previous).add(processId)); - }; - return (
@@ -202,7 +196,6 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) { loading={loading || initialLoading} refreshing={isRefreshing} onSelectDate={handleOverviewDateSelect} - onSpotlightAppClick={handleSpotlightAppClick} /> {error && ( @@ -250,16 +243,13 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) { {screenTime.apps.map((app, index) => { const isExpanded = expandedApps.has(app.processId); const processWidth = maxDurationSeconds > 0 - ? Math.max((app.durationSeconds / maxDurationSeconds) * 100, 4) + ? Math.max((app.durationSeconds / maxDurationSeconds) * 100, 2) : 0; return (
diff --git a/app/hosts/[hostname]/screen-time/route.ts b/app/hosts/[hostname]/screen-time/route.ts index 7e2c1ca..aa08870 100644 --- a/app/hosts/[hostname]/screen-time/route.ts +++ b/app/hosts/[hostname]/screen-time/route.ts @@ -8,6 +8,7 @@ import { Prisma } from '@prisma/client' const MAX_GAP_MS = 5 * 60 * 1000 const DEFAULT_SAMPLE_MS = 30 * 1000 const MIN_SAMPLE_MS = 5 * 1000 +const SPOTLIGHT_APP_LIMIT = 4 const LABEL_TOKEN_THRESHOLD_RATIO = 0.5 const HOUR_MS = 60 * 60 * 1000 const DAY_MS = 24 * HOUR_MS @@ -458,7 +459,7 @@ async function handleScreenTime(req: NextRequest) { const aggregateSummary = buildAppList(aggregateUsageMap) const selectedDaySummary = buildAppList(selectedDayUsageMap) - const spotlightApps = selectedDaySummary.apps.slice(0, 3).map(app => ({ + const spotlightApps = selectedDaySummary.apps.slice(0, SPOTLIGHT_APP_LIMIT).map(app => ({ processId: app.processId, processName: app.processName, durationSeconds: app.durationSeconds,