Remove global date/district filter from top bar
The global filter caused confusion — activities saved with future dates were silently filtered out, making it appear saves didn't work. Subviews can add their own filtering where it makes sense. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,49 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useData, DatePreset } from '@/lib/data-context';
|
import { useData } from '@/lib/data-context';
|
||||||
import { usePageTitle } from '@/lib/page-title-context';
|
import { usePageTitle } from '@/lib/page-title-context';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { useState, useRef, useEffect } from 'react';
|
|
||||||
import { DISTRICTS } from '@/types/data';
|
|
||||||
|
|
||||||
const DATE_PRESETS: { value: DatePreset; label: string }[] = [
|
|
||||||
{ value: 'this_week', label: 'This Week' },
|
|
||||||
{ value: 'last_7', label: 'Last 7 Days' },
|
|
||||||
{ value: 'this_month', label: 'This Month' },
|
|
||||||
{ value: 'this_quarter', label: 'This Quarter' },
|
|
||||||
{ value: 'last_quarter', label: 'Last Quarter' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function TopBar() {
|
export function TopBar() {
|
||||||
const { filters, setDistricts, setDatePreset, refresh, isLoading, lastRefreshed, clearAllFilters, clearCrossFilter } = useData();
|
const { filters, refresh, isLoading, lastRefreshed, clearCrossFilter } = useData();
|
||||||
const { title } = usePageTitle();
|
const { title } = usePageTitle();
|
||||||
const [showDistrictDropdown, setShowDistrictDropdown] = useState(false);
|
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function handleClick(e: MouseEvent) {
|
|
||||||
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
|
||||||
setShowDistrictDropdown(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener('mousedown', handleClick);
|
|
||||||
return () => document.removeEventListener('mousedown', handleClick);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const toggleDistrict = (d: string) => {
|
|
||||||
const current = filters.districts;
|
|
||||||
if (current.includes(d)) {
|
|
||||||
setDistricts(current.filter(x => x !== d));
|
|
||||||
} else {
|
|
||||||
setDistricts([...current, d]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const districtLabel = filters.districts.length === 0
|
|
||||||
? 'All Districts'
|
|
||||||
: filters.districts.length === 1
|
|
||||||
? filters.districts[0].replace('SE-', '')
|
|
||||||
: `${filters.districts.length} Districts`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="h-14 bg-topbar-bg border-b border-topbar-border flex items-center px-4 gap-3 flex-shrink-0">
|
<header className="h-14 bg-topbar-bg border-b border-topbar-border flex items-center px-4 gap-3 flex-shrink-0">
|
||||||
@@ -71,55 +34,6 @@ export function TopBar() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Date preset */}
|
|
||||||
<select
|
|
||||||
value={filters.datePreset}
|
|
||||||
onChange={e => setDatePreset(e.target.value as DatePreset)}
|
|
||||||
className="hidden sm:block text-xs border border-card-border rounded-lg px-2.5 py-1.5 bg-white text-foreground focus:outline-none focus:ring-2 focus:ring-brand-azure/30"
|
|
||||||
>
|
|
||||||
{DATE_PRESETS.map(p => (
|
|
||||||
<option key={p.value} value={p.value}>{p.label}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
{/* District filter */}
|
|
||||||
<div className="relative" ref={dropdownRef}>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowDistrictDropdown(!showDistrictDropdown)}
|
|
||||||
className="text-xs border border-card-border rounded-lg px-2.5 py-1.5 bg-white text-foreground flex items-center gap-1.5 hover:border-brand-azure/50 transition"
|
|
||||||
>
|
|
||||||
<svg className="w-3.5 h-3.5 text-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" /></svg>
|
|
||||||
<span className="hidden sm:inline">{districtLabel}</span>
|
|
||||||
<span className="sm:hidden">
|
|
||||||
{filters.districts.length > 0 ? filters.districts.length : 'All'}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
{showDistrictDropdown && (
|
|
||||||
<div className="absolute right-0 top-full mt-1 w-52 bg-white rounded-lg shadow-lg border border-card-border py-1 z-50">
|
|
||||||
<button
|
|
||||||
onClick={() => { setDistricts([]); setShowDistrictDropdown(false); }}
|
|
||||||
className={`w-full text-left px-3 py-2 text-xs hover:bg-brand-azure/5 ${filters.districts.length === 0 ? 'text-brand-azure font-semibold' : 'text-foreground'}`}
|
|
||||||
>
|
|
||||||
All Districts
|
|
||||||
</button>
|
|
||||||
{DISTRICTS.map(d => (
|
|
||||||
<button
|
|
||||||
key={d}
|
|
||||||
onClick={() => toggleDistrict(d)}
|
|
||||||
className="w-full text-left px-3 py-2 text-xs hover:bg-brand-azure/5 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<div className={`w-3.5 h-3.5 rounded border ${filters.districts.includes(d) ? 'bg-brand-azure border-brand-azure' : 'border-card-border'} flex items-center justify-center`}>
|
|
||||||
{filters.districts.includes(d) && (
|
|
||||||
<svg className="w-2.5 h-2.5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><polyline points="20 6 9 17 4 12" /></svg>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{d.replace('SE-', '')}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Refresh */}
|
{/* Refresh */}
|
||||||
<button
|
<button
|
||||||
onClick={refresh}
|
onClick={refresh}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
import React, { createContext, useContext, useState, useMemo, useCallback, ReactNode } from 'react';
|
import React, { createContext, useContext, useState, useMemo, useCallback, ReactNode } from 'react';
|
||||||
import { DashboardData, DashboardConfig, PipelineRecord, AccountRecord, ActivityRecord, TargetRecord } from '@/types/data';
|
import { DashboardData, DashboardConfig, PipelineRecord, AccountRecord, ActivityRecord, TargetRecord } from '@/types/data';
|
||||||
import { startOfWeek, endOfWeek, startOfMonth, endOfMonth, startOfQuarter, endOfQuarter, subDays, parseISO, isWithinInterval } from 'date-fns';
|
|
||||||
|
|
||||||
export type DatePreset = 'this_week' | 'last_7' | 'this_month' | 'this_quarter' | 'last_quarter' | 'custom';
|
export type DatePreset = 'this_week' | 'last_7' | 'this_month' | 'this_quarter' | 'last_quarter' | 'custom';
|
||||||
|
|
||||||
interface FilterState {
|
interface FilterState {
|
||||||
@@ -36,27 +34,6 @@ interface DataContextValue {
|
|||||||
|
|
||||||
const DataContext = createContext<DataContextValue | null>(null);
|
const DataContext = createContext<DataContextValue | null>(null);
|
||||||
|
|
||||||
function getDateRange(preset: DatePreset, custom?: { start: string; end: string }): { start: Date; end: Date } {
|
|
||||||
const now = new Date();
|
|
||||||
switch (preset) {
|
|
||||||
case 'this_week':
|
|
||||||
return { start: startOfWeek(now, { weekStartsOn: 1 }), end: endOfWeek(now, { weekStartsOn: 1 }) };
|
|
||||||
case 'last_7':
|
|
||||||
return { start: subDays(now, 7), end: now };
|
|
||||||
case 'this_month':
|
|
||||||
return { start: startOfMonth(now), end: endOfMonth(now) };
|
|
||||||
case 'this_quarter':
|
|
||||||
return { start: startOfQuarter(now), end: endOfQuarter(now) };
|
|
||||||
case 'last_quarter': {
|
|
||||||
const lastQ = new Date(now);
|
|
||||||
lastQ.setMonth(lastQ.getMonth() - 3);
|
|
||||||
return { start: startOfQuarter(lastQ), end: endOfQuarter(lastQ) };
|
|
||||||
}
|
|
||||||
case 'custom':
|
|
||||||
if (custom) return { start: parseISO(custom.start), end: parseISO(custom.end) };
|
|
||||||
return { start: subDays(now, 30), end: now };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DataProvider({ children, initialData, config }: { children: ReactNode; initialData: DashboardData; config: DashboardConfig }) {
|
export function DataProvider({ children, initialData, config }: { children: ReactNode; initialData: DashboardData; config: DashboardConfig }) {
|
||||||
const [data, setData] = useState<DashboardData>(initialData);
|
const [data, setData] = useState<DashboardData>(initialData);
|
||||||
@@ -106,28 +83,13 @@ export function DataProvider({ children, initialData, config }: { children: Reac
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
const { districts: distFilter, datePreset, dateRange, crossFilter } = filters;
|
const { crossFilter } = filters;
|
||||||
const range = getDateRange(datePreset, dateRange);
|
|
||||||
|
|
||||||
let pipeline = data.pipeline;
|
let pipeline = data.pipeline;
|
||||||
let accounts = data.accounts;
|
let accounts = data.accounts;
|
||||||
let activities = data.activities;
|
let activities = data.activities;
|
||||||
let targets = data.targets;
|
let targets = data.targets;
|
||||||
|
|
||||||
if (distFilter.length > 0) {
|
|
||||||
pipeline = pipeline.filter(p => distFilter.includes(p.District_Name));
|
|
||||||
accounts = accounts.filter(a => distFilter.includes(a.District_Name));
|
|
||||||
activities = activities.filter(a => distFilter.includes(a.District_Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
activities = activities.filter(a => {
|
|
||||||
try {
|
|
||||||
return isWithinInterval(parseISO(a.Activity_Date), range);
|
|
||||||
} catch {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (crossFilter) {
|
if (crossFilter) {
|
||||||
const { key, value } = crossFilter;
|
const { key, value } = crossFilter;
|
||||||
const match = (obj: object) => String((obj as never)[key as never]) === value;
|
const match = (obj: object) => String((obj as never)[key as never]) === value;
|
||||||
@@ -137,11 +99,6 @@ export function DataProvider({ children, initialData, config }: { children: Reac
|
|||||||
targets = targets.filter(match);
|
targets = targets.filter(match);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distFilter.length > 0) {
|
|
||||||
const accountNames = new Set(accounts.map(a => a.Account_Name));
|
|
||||||
targets = targets.filter(t => accountNames.has(t.Account_Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
return { pipeline, accounts, activities, targets };
|
return { pipeline, accounts, activities, targets };
|
||||||
}, [data, filters]);
|
}, [data, filters]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user