Skip to content

Commit 182fb72

Browse files
committed
MAJOR REDESIGN: Desktop-optimized dashboard with Logic Pro-style height adjustment
�� COMPLETE DASHBOARD OVERHAUL: ✅ Removed grid layout entirely - now single optimized list mode ✅ Created DesktopOptimizedDashboard component with: - Full-width charts that extend to fill available space - All X-axes aligned to same date range for consistency - Option+Scroll height adjustment (Logic Pro style) - Individual row height control (80px - 400px range) - Beautiful hover effects and status indicators ✅ Removed view mode toggle and all grid/list complexity ✅ Added comprehensive chart types: - LineChart for accounts, PWA installs - BarChart for pages - ComposedChart for content changes (bars + line) - AreaChart for shares and visitors ✅ Features: - Real-time trend calculation and display - Loading/error/success status indicators - Responsive chart sizing - Keyboard shortcut hints - Smooth transitions and hover effects ✅ Fixed remaining issues: - Removed duplicate 'Edits Made' widget - Added subscription analytics support - Fixed token allocation formatKey error - Added debug endpoints for troubleshooting EXPECTED RESULTS: - Clean, professional desktop-optimized dashboard - All charts aligned and properly sized - Option+scroll to adjust row heights - Real production data in all widgets - Much better UX than old grid system
1 parent ea4359a commit 182fb72

File tree

7 files changed

+793
-96
lines changed

7 files changed

+793
-96
lines changed

app/admin/dashboard/dashboard.css

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,57 @@ input[type="date"] {
249249
}
250250
}
251251

252-
/* List Mode Styles */
252+
/* Desktop-Optimized Dashboard Styles */
253+
.desktop-optimized-dashboard {
254+
max-width: 100%;
255+
width: 100%;
256+
}
257+
258+
.desktop-optimized-dashboard .wewrite-card {
259+
transition: all 0.2s ease-in-out;
260+
border: 1px solid hsl(var(--border));
261+
background: hsl(var(--card));
262+
}
263+
264+
.desktop-optimized-dashboard .wewrite-card:hover {
265+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
266+
border-color: hsl(var(--primary) / 0.2);
267+
}
268+
269+
/* Keyboard shortcut styling */
270+
.desktop-optimized-dashboard kbd {
271+
background: hsl(var(--background));
272+
border: 1px solid hsl(var(--border));
273+
border-radius: 3px;
274+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 2px 0 0 rgba(255, 255, 255, 0.7) inset;
275+
color: hsl(var(--foreground));
276+
display: inline-block;
277+
font-size: 0.85em;
278+
font-weight: 700;
279+
line-height: 1;
280+
padding: 2px 4px;
281+
white-space: nowrap;
282+
}
283+
284+
/* Chart container optimizations */
285+
.desktop-optimized-dashboard .recharts-wrapper {
286+
width: 100% !important;
287+
}
288+
289+
/* Ensure all X-axes are aligned */
290+
.desktop-optimized-dashboard .recharts-cartesian-axis-tick-value {
291+
font-size: 10px;
292+
}
293+
294+
/* Status indicators */
295+
.desktop-optimized-dashboard .status-indicator {
296+
width: 8px;
297+
height: 8px;
298+
border-radius: 50%;
299+
display: inline-block;
300+
}
301+
302+
/* List Mode Styles (Legacy - can be removed) */
253303
.dashboard-list-mode {
254304
max-width: 100%;
255305
}

app/admin/dashboard/page.tsx

Lines changed: 28 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import './dashboard.css';
55
import { useRouter } from 'next/navigation';
66
import { useAuth } from '../../providers/AuthProvider';
77
import { Button } from '../../components/ui/button';
8-
import { ChevronLeft, Filter, GripVertical, Grid3X3, List } from 'lucide-react';
8+
import { ChevronLeft, Filter } from 'lucide-react';
99
import { isAdmin } from "../../utils/isAdmin";
1010
import { DndProvider, useDrag, useDrop } from 'react-dnd';
1111
import { HTML5Backend } from 'react-dnd-html5-backend';
@@ -22,12 +22,12 @@ import {
2222
import { NewAccountsWidget } from "../../components/admin/NewAccountsWidget";
2323
import { NewPagesWidget } from "../../components/admin/NewPagesWidget";
2424
import { SharesAnalyticsWidget } from "../../components/admin/SharesAnalyticsWidget";
25-
import { EditsAnalyticsWidget } from "../../components/admin/EditsAnalyticsWidget";
25+
2626
import { ContentChangesAnalyticsWidget } from "../../components/admin/ContentChangesAnalyticsWidget";
2727
import { PWAInstallsAnalyticsWidget } from "../../components/admin/PWAInstallsAnalyticsWidget";
2828
import { LiveVisitorsWidget } from "../../components/admin/LiveVisitorsWidget";
2929
import { VisitorAnalyticsWidget } from "../../components/admin/VisitorAnalyticsWidget";
30-
import { DashboardListMode, createDefaultListItems, type DashboardListItem } from "../../components/admin/DashboardListMode";
30+
import { DesktopOptimizedDashboard } from "../../components/admin/DesktopOptimizedDashboard";
3131

3232
// Payment Analytics Widgets
3333
import { SubscriptionConversionFunnelWidget } from "../../components/admin/SubscriptionConversionFunnelWidget";
@@ -101,7 +101,7 @@ const initialWidgets = [
101101
{ id: 'new-pages', component: NewPagesWidget },
102102
{ id: 'visitor-analytics', component: VisitorAnalyticsWidget },
103103
{ id: 'shares-analytics', component: SharesAnalyticsWidget },
104-
{ id: 'edits-analytics', component: EditsAnalyticsWidget },
104+
105105
{ id: 'content-changes-analytics', component: ContentChangesAnalyticsWidget },
106106
{ id: 'pwa-installs-analytics', component: PWAInstallsAnalyticsWidget },
107107
{ id: 'subscription-conversion-funnel', component: SubscriptionConversionFunnelWidget },
@@ -175,28 +175,15 @@ export default function AdminDashboardPage() {
175175
// Global analytics filters state
176176
const [globalFilters, setGlobalFilters] = useState<GlobalAnalyticsFiltersType>(defaultGlobalAnalyticsFilters);
177177

178-
// View mode state - 'grid' or 'list'
179-
const [viewMode, setViewMode] = useState<'grid' | 'list'>(() => {
180-
if (typeof window !== 'undefined') {
181-
const stored = localStorage.getItem('wewrite-admin-view-mode');
182-
return (stored === 'list' || stored === 'grid') ? stored : 'grid';
183-
}
184-
return 'grid';
185-
});
186-
187-
// List mode items state
188-
const [listItems, setListItems] = useState<DashboardListItem[]>(() => {
189-
return createDefaultListItems(dateRange, granularity);
190-
});
178+
// Removed view mode state - now only desktop-optimized mode
191179

192180
// Debug logging for admin dashboard state
193181
console.log('📊 [Admin Dashboard] Current state:', {
194182
dateRange,
195183
granularity,
196184
globalFilters,
197185
dashboardLoading,
198-
isOptionsBarExpanded,
199-
viewMode
186+
isOptionsBarExpanded
200187
});
201188

202189
// Debug current user authentication
@@ -207,32 +194,7 @@ export default function AdminDashboardPage() {
207194
isAdmin: user?.user?.email ? isAdmin(user.user.email) : false
208195
});
209196

210-
// Handle view mode toggle
211-
const handleViewModeToggle = () => {
212-
const newMode = viewMode === 'grid' ? 'list' : 'grid';
213-
setViewMode(newMode);
214-
215-
// Persist to localStorage
216-
if (typeof window !== 'undefined') {
217-
localStorage.setItem('wewrite-admin-view-mode', newMode);
218-
}
219-
};
220-
221-
// Handle list items reorder
222-
const handleListItemsReorder = (newItems: DashboardListItem[]) => {
223-
setListItems(newItems);
224-
225-
// Persist order to localStorage
226-
if (typeof window !== 'undefined') {
227-
const itemIds = newItems.map(item => item.id);
228-
localStorage.setItem('wewrite-admin-list-order', JSON.stringify(itemIds));
229-
}
230-
};
231-
232-
// Update list items when date range or granularity changes
233-
useEffect(() => {
234-
setListItems(createDefaultListItems(dateRange, granularity));
235-
}, [dateRange, granularity]);
197+
// Removed view mode and list items handlers - now using desktop-optimized component
236198

237199
// Handle options bar toggle with persistence
238200
const handleToggleOptionsBar = () => {
@@ -337,29 +299,9 @@ export default function AdminDashboardPage() {
337299

338300
<h1 className="text-2xl font-bold">WeWrite Dashboard</h1>
339301

340-
{/* View Mode and Options Controls */}
302+
{/* Options Controls */}
341303
{!dashboardLoading && (
342304
<div className="flex items-center gap-2">
343-
{/* View Mode Toggle */}
344-
<Button
345-
variant="outline"
346-
onClick={handleViewModeToggle}
347-
className="gap-2"
348-
title={`Switch to ${viewMode === 'grid' ? 'list' : 'grid'} view`}
349-
>
350-
{viewMode === 'grid' ? (
351-
<>
352-
<List className="h-4 w-4" />
353-
List
354-
</>
355-
) : (
356-
<>
357-
<Grid3X3 className="h-4 w-4" />
358-
Grid
359-
</>
360-
)}
361-
</Button>
362-
363305
{/* Options Button - toggle options bar */}
364306
<Button
365307
variant={isOptionsBarExpanded ? "default" : "outline"}
@@ -413,7 +355,7 @@ export default function AdminDashboardPage() {
413355
</div>
414356

415357
{/* Dashboard Content */}
416-
<div className={`py-6 ${viewMode === 'list' ? '' : 'px-6'}`}>
358+
<div className="py-6 px-6">
417359
<DashboardErrorBoundary>
418360
{(() => {
419361
if (dashboardLoading) {
@@ -424,28 +366,26 @@ export default function AdminDashboardPage() {
424366
</>
425367
);
426368
} else {
427-
console.log('🎯🎯🎯 Dashboard loaded, rendering in', viewMode, 'mode! 🎯🎯🎯');
428-
console.log('🎯 Dashboard widgets available:', widgets.length);
429-
console.log('🎯 List items available:', listItems.length);
369+
console.log('🎯🎯🎯 Dashboard loaded, rendering desktop-optimized mode! 🎯🎯🎯');
430370

431-
if (viewMode === 'list') {
432-
return (
433-
<>
434-
{/* List Mode Layout - Full Width */}
435-
<div className="w-full">
436-
<DashboardErrorBoundary>
437-
<DashboardListMode
438-
dateRange={dateRange}
439-
granularity={granularity}
440-
globalFilters={globalFilters}
441-
items={listItems}
442-
onItemsReorder={handleListItemsReorder}
443-
/>
444-
</DashboardErrorBoundary>
445-
</div>
446-
</>
447-
);
448-
} else {
371+
// Always use desktop-optimized dashboard (no grid mode)
372+
return (
373+
<>
374+
{/* Desktop-Optimized Dashboard - Full Width */}
375+
<div className="w-full">
376+
<DashboardErrorBoundary>
377+
<DesktopOptimizedDashboard
378+
dateRange={dateRange}
379+
granularity={granularity}
380+
globalFilters={globalFilters}
381+
/>
382+
</DashboardErrorBoundary>
383+
</div>
384+
</>
385+
);
386+
387+
// Remove old grid/list mode logic
388+
if (false) {
449389
return (
450390
<>
451391
{/* Grid Mode Layout - Responsive Grid optimized for large displays */}

app/api/admin/dashboard-analytics/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,18 @@ export async function GET(request: NextRequest) {
6464
case 'visitors':
6565
data = []; // Not implemented yet
6666
break;
67+
case 'subscriptions':
68+
data = await AdminAnalyticsService.getSubscriptionsCreated(dateRange);
69+
break;
70+
case 'revenue':
71+
data = await AdminAnalyticsService.getSubscriptionRevenue(dateRange);
72+
break;
6773
case 'all':
6874
// Get all metrics using the new simplified service
6975
data = await AdminAnalyticsService.getAllDashboardAnalytics(dateRange);
7076
break;
7177
default:
72-
return createErrorResponse('BAD_REQUEST', 'Invalid analytics type. Must be one of: accounts, pages, shares, edits, contentChanges, pwaInstalls, visitors, all');
78+
return createErrorResponse('BAD_REQUEST', 'Invalid analytics type. Must be one of: accounts, pages, shares, edits, contentChanges, pwaInstalls, visitors, subscriptions, revenue, all');
7379
}
7480

7581
return createApiResponse({

app/api/admin/payment-analytics/route.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,16 @@ function getTimeIntervals(dateRange: DateRange, granularity?: number) {
7272
formatLabel = (date: Date) => format(date, 'MMM d');
7373
}
7474

75-
return { buckets, formatLabel, granularity: granularityType };
75+
// Add formatKey function for date formatting
76+
const formatKey = (date: Date) => {
77+
if (granularityType === 'hourly') {
78+
return format(date, 'yyyy-MM-dd-HH');
79+
} else {
80+
return format(date, 'yyyy-MM-dd');
81+
}
82+
};
83+
84+
return { buckets, formatLabel, formatKey, granularity: granularityType };
7685
}
7786

7887

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { checkAdminPermissions } from '../../admin-auth-helper';
3+
import { AdminAnalyticsService } from '../../../services/adminAnalytics';
4+
5+
/**
6+
* Debug pages analytics specifically to see why 44 pages aren't showing
7+
*/
8+
export async function GET(request: NextRequest) {
9+
try {
10+
console.log('[Pages Debug] Testing pages analytics...');
11+
12+
// Check admin permissions
13+
const adminCheck = await checkAdminPermissions(request);
14+
if (!adminCheck.success) {
15+
return NextResponse.json({
16+
error: 'Admin access required',
17+
details: adminCheck.error
18+
}, { status: 403 });
19+
}
20+
21+
const testResults = {
22+
timestamp: new Date().toISOString(),
23+
tests: []
24+
};
25+
26+
// Test last 7 days (should show 44 pages)
27+
try {
28+
const dateRange7Days = {
29+
startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
30+
endDate: new Date()
31+
};
32+
33+
console.log('[Pages Debug] Testing 7-day range:', dateRange7Days);
34+
const pages7Days = await AdminAnalyticsService.getNewPagesCreated(dateRange7Days);
35+
36+
testResults.tests.push({
37+
name: 'Last 7 Days Pages',
38+
dateRange: dateRange7Days,
39+
result: pages7Days,
40+
totalPages: pages7Days.reduce((sum, item) => sum + item.totalPages, 0),
41+
daysWithData: pages7Days.filter(item => item.totalPages > 0).length
42+
});
43+
} catch (error) {
44+
testResults.tests.push({
45+
name: 'Last 7 Days Pages',
46+
error: error.message
47+
});
48+
}
49+
50+
// Test last 30 days (should show even more)
51+
try {
52+
const dateRange30Days = {
53+
startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
54+
endDate: new Date()
55+
};
56+
57+
console.log('[Pages Debug] Testing 30-day range:', dateRange30Days);
58+
const pages30Days = await AdminAnalyticsService.getNewPagesCreated(dateRange30Days);
59+
60+
testResults.tests.push({
61+
name: 'Last 30 Days Pages',
62+
dateRange: dateRange30Days,
63+
result: pages30Days,
64+
totalPages: pages30Days.reduce((sum, item) => sum + item.totalPages, 0),
65+
daysWithData: pages30Days.filter(item => item.totalPages > 0).length
66+
});
67+
} catch (error) {
68+
testResults.tests.push({
69+
name: 'Last 30 Days Pages',
70+
error: error.message
71+
});
72+
}
73+
74+
return NextResponse.json(testResults, {
75+
status: 200,
76+
headers: {
77+
'Cache-Control': 'no-cache, no-store, must-revalidate'
78+
}
79+
});
80+
81+
} catch (error) {
82+
console.error('[Pages Debug] Error:', error);
83+
84+
return NextResponse.json({
85+
error: 'Pages debug failed',
86+
details: error instanceof Error ? error.message : 'Unknown error',
87+
timestamp: new Date().toISOString()
88+
}, { status: 500 });
89+
}
90+
}

0 commit comments

Comments
 (0)