mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:31:12 -05:00
Add tooltip for expanded count
Enhance persistence for moderator preferences - Add tooltip to moderation queue toggle showing number of items with detailed views expanded (based on global state, tooltip adapts to expanded/collapsed). - Persist expanded/collapsed state per moderator in the database instead of localStorage, integrating with user preferences and Supabase backend. X-Lovable-Edit-ID: edt-61e75a20-f83d-40b2-8bc4-b6ff40b23450
This commit is contained in:
@@ -4,6 +4,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { RefreshButton } from '@/components/ui/refresh-button';
|
import { RefreshButton } from '@/components/ui/refresh-button';
|
||||||
import { QueueSortControls } from './QueueSortControls';
|
import { QueueSortControls } from './QueueSortControls';
|
||||||
import { useFilterPanelState } from '@/hooks/useFilterPanelState';
|
import { useFilterPanelState } from '@/hooks/useFilterPanelState';
|
||||||
@@ -80,25 +81,40 @@ export const QueueFilters = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* Global toggle for detailed views */}
|
{/* Global toggle for detailed views */}
|
||||||
<Button
|
<TooltipProvider>
|
||||||
variant="ghost"
|
<Tooltip>
|
||||||
size="sm"
|
<TooltipTrigger asChild>
|
||||||
onClick={toggleDetails}
|
<Button
|
||||||
className="h-8 gap-2 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
variant="ghost"
|
||||||
title={detailsCollapsed ? "Expand all detailed views" : "Collapse all detailed views"}
|
size="sm"
|
||||||
>
|
onClick={toggleDetails}
|
||||||
{detailsCollapsed ? (
|
className="h-8 gap-2 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||||
<>
|
>
|
||||||
<Maximize2 className="h-3.5 w-3.5" />
|
{detailsCollapsed ? (
|
||||||
{!isMobile && <span>Expand All</span>}
|
<>
|
||||||
</>
|
<Maximize2 className="h-3.5 w-3.5" />
|
||||||
) : (
|
{!isMobile && <span>Expand All</span>}
|
||||||
<>
|
</>
|
||||||
<Minimize2 className="h-3.5 w-3.5" />
|
) : (
|
||||||
{!isMobile && <span>Collapse All</span>}
|
<>
|
||||||
</>
|
<Minimize2 className="h-3.5 w-3.5" />
|
||||||
)}
|
{!isMobile && <span>Collapse All</span>}
|
||||||
</Button>
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom" className="max-w-xs">
|
||||||
|
<p className="text-xs">
|
||||||
|
{detailsCollapsed
|
||||||
|
? "Show detailed field-by-field view for all items in the queue"
|
||||||
|
: "Hide detailed field-by-field view for all items in the queue"}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
This preference is saved to your account
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||||
|
|||||||
@@ -1,48 +1,127 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { logger } from '@/lib/logger';
|
import { logger } from '@/lib/logger';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { supabase } from '@/lib/supabaseClient';
|
||||||
|
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||||
|
import type { Json } from '@/integrations/supabase/types';
|
||||||
|
|
||||||
const STORAGE_KEY = 'detailed-view-collapsed';
|
const STORAGE_KEY = 'detailed-view-collapsed';
|
||||||
|
|
||||||
|
interface ModerationPreferences {
|
||||||
|
detailed_view_collapsed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface UseDetailedViewStateReturn {
|
interface UseDetailedViewStateReturn {
|
||||||
isCollapsed: boolean;
|
isCollapsed: boolean;
|
||||||
toggle: () => void;
|
toggle: () => void;
|
||||||
setCollapsed: (value: boolean) => void;
|
setCollapsed: (value: boolean) => void;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to manage detailed view collapsed/expanded state
|
* Hook to manage detailed view collapsed/expanded state
|
||||||
* Syncs with localStorage for persistence across sessions
|
* Persists to database for authenticated users, localStorage for guests
|
||||||
* Defaults to collapsed to reduce visual clutter
|
* Defaults to collapsed to reduce visual clutter
|
||||||
*/
|
*/
|
||||||
export function useDetailedViewState(): UseDetailedViewStateReturn {
|
export function useDetailedViewState(): UseDetailedViewStateReturn {
|
||||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
|
const { user } = useAuth();
|
||||||
// Initialize from localStorage on mount
|
const [isCollapsed, setIsCollapsed] = useState<boolean>(true);
|
||||||
try {
|
const [loading, setLoading] = useState(true);
|
||||||
const stored = localStorage.getItem(STORAGE_KEY);
|
|
||||||
// Default to collapsed (true) to reduce visual clutter
|
|
||||||
return stored ? JSON.parse(stored) : true;
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn('Error reading detailed view state from localStorage', { error });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sync to localStorage when state changes
|
// Load preferences on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
loadPreferences();
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const loadPreferences = async () => {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(isCollapsed));
|
if (user) {
|
||||||
|
// Load from database for authenticated users
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('user_preferences')
|
||||||
|
.select('moderation_preferences')
|
||||||
|
.eq('user_id', user.id)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (error && error.code !== 'PGRST116') {
|
||||||
|
handleNonCriticalError(error, {
|
||||||
|
action: 'Load moderation preferences',
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data?.moderation_preferences) {
|
||||||
|
const prefs = data.moderation_preferences as ModerationPreferences;
|
||||||
|
setIsCollapsed(prefs.detailed_view_collapsed ?? true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Load from localStorage for guests
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
setIsCollapsed(stored ? JSON.parse(stored) : true);
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Error reading detailed view state from localStorage', { error });
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('Error saving detailed view state to localStorage', { error });
|
logger.warn('Error loading detailed view preferences', { error });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [isCollapsed]);
|
};
|
||||||
|
|
||||||
const toggle = () => setIsCollapsed(prev => !prev);
|
const savePreferences = async (collapsed: boolean) => {
|
||||||
|
try {
|
||||||
|
if (user) {
|
||||||
|
// Save to database for authenticated users
|
||||||
|
const moderationPrefs: ModerationPreferences = {
|
||||||
|
detailed_view_collapsed: collapsed,
|
||||||
|
};
|
||||||
|
|
||||||
const setCollapsed = (value: boolean) => setIsCollapsed(value);
|
const { error } = await supabase
|
||||||
|
.from('user_preferences')
|
||||||
|
.upsert({
|
||||||
|
user_id: user.id,
|
||||||
|
moderation_preferences: moderationPrefs as unknown as Json,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
}, {
|
||||||
|
onConflict: 'user_id',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
handleNonCriticalError(error, {
|
||||||
|
action: 'Save moderation preferences',
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Save to localStorage for guests
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(collapsed));
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Error saving detailed view state to localStorage', { error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Error saving detailed view preferences', { error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
const newValue = !isCollapsed;
|
||||||
|
setIsCollapsed(newValue);
|
||||||
|
savePreferences(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCollapsed = (value: boolean) => {
|
||||||
|
setIsCollapsed(value);
|
||||||
|
savePreferences(value);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isCollapsed,
|
isCollapsed,
|
||||||
toggle,
|
toggle,
|
||||||
setCollapsed,
|
setCollapsed,
|
||||||
|
loading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user