mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 12:45:24 +02:00
Add date range filter with calendar picker for Launch Date and Ready to Order
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Calendar } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
interface DateFilterPopoverProps {
|
||||
selectedRange: { start: string; end: string };
|
||||
onRangeChange: (range: { start: string; end: string }) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function DateFilterPopover({ selectedRange, onRangeChange, onClose }: DateFilterPopoverProps) {
|
||||
const [startDate, setStartDate] = useState(selectedRange.start);
|
||||
const [endDate, setEndDate] = useState(selectedRange.end);
|
||||
|
||||
const handleApply = () => {
|
||||
onRangeChange({ start: startDate, end: endDate });
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setStartDate('');
|
||||
setEndDate('');
|
||||
onRangeChange({ start: '', end: '' });
|
||||
onClose();
|
||||
};
|
||||
|
||||
const hasValue = startDate || endDate;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute top-full left-0 mt-1 w-72 bg-slate-800 border border-slate-700 rounded-lg shadow-2xl z-50 p-4 flex flex-col gap-4 animate-in fade-in zoom-in-95 duration-100"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-slate-400 uppercase tracking-wider">
|
||||
<Calendar className="w-4 h-4" />
|
||||
Filter by Date Range
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-[10px] text-slate-500 uppercase tracking-wider">From</label>
|
||||
<input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={e => setStartDate(e.target.value)}
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded px-3 py-2 text-xs text-white focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-[10px] text-slate-500 uppercase tracking-wider">To</label>
|
||||
<input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={e => setEndDate(e.target.value)}
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded px-3 py-2 text-xs text-white focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-3 border-t border-slate-700">
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className="text-[10px] font-bold text-slate-400 hover:text-white transition-colors uppercase tracking-tight"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-3 py-1.5 text-[10px] font-medium text-slate-300 hover:text-white hover:bg-slate-700 rounded transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleApply}
|
||||
className="px-4 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-[10px] font-black rounded transition-colors shadow-lg"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { ExcelRow, COLUMNS } from '../types';
|
||||
import { Search, ChevronDown, ChevronUp, X, Edit2, Save } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
||||
import { DateFilterPopover } from './DateFilterPopover';
|
||||
|
||||
interface MissingDataViewProps {
|
||||
data: ExcelRow[];
|
||||
@@ -72,6 +73,7 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
||||
const [page, setPage] = useState(1);
|
||||
const [editing, setEditing] = useState<EditingState | null>(null);
|
||||
const [columnFilters, setColumnFilters] = useState<Record<number, string[]>>({});
|
||||
const [dateFilters, setDateFilters] = useState<Record<number, { start: string; end: string }>>({});
|
||||
const [openFilter, setOpenFilter] = useState<number | null>(null);
|
||||
const pageSize = 100;
|
||||
|
||||
@@ -137,6 +139,7 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
||||
(Object.entries(columnFilters) as [string, string[]][]).forEach(([colIdx, filterValues]) => {
|
||||
if (!filterValues || filterValues.length === 0) return;
|
||||
const colIdxNum = parseInt(colIdx);
|
||||
if (colIdxNum === launchDateCol || colIdxNum === readyToOrderCol) return;
|
||||
result = result.filter(r => {
|
||||
const val: any = r.row[colIdxNum];
|
||||
const displayVal = colIdxNum === launchDateCol || colIdxNum === readyToOrderCol
|
||||
@@ -146,6 +149,29 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
||||
});
|
||||
});
|
||||
|
||||
(Object.entries(dateFilters) as [string, { start: string; end: string }][]).forEach(([colIdx, range]) => {
|
||||
if (!range.start && !range.end) return;
|
||||
const colIdxNum = parseInt(colIdx);
|
||||
result = result.filter(r => {
|
||||
const val = r.row[colIdxNum];
|
||||
const dateStr = formatDateValue(val) || String(val ?? '');
|
||||
if (!dateStr) return false;
|
||||
const parts = dateStr.split('/');
|
||||
if (parts.length !== 3) return false;
|
||||
const rowDate = new Date(`${parts[2]}-${parts[1]}-${parts[0]}`);
|
||||
if (isNaN(rowDate.getTime())) return false;
|
||||
if (range.start) {
|
||||
const startDate = new Date(range.start);
|
||||
if (rowDate < startDate) return false;
|
||||
}
|
||||
if (range.end) {
|
||||
const endDate = new Date(range.end);
|
||||
if (rowDate > endDate) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
if (sortCol !== null) {
|
||||
result.sort((a, b) => {
|
||||
const valA = a.row[sortCol];
|
||||
@@ -269,6 +295,34 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 relative">
|
||||
{col === launchDateCol || col === readyToOrderCol ? (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === col ? null : col); }}
|
||||
className={cn(
|
||||
"w-full flex items-center justify-between px-1.5 py-0.5 bg-slate-800 border rounded text-[10px] transition-colors",
|
||||
dateFilters[col]?.start || dateFilters[col]?.end
|
||||
? "border-indigo-500 text-white"
|
||||
: "border-slate-600 text-slate-400 hover:border-slate-500"
|
||||
)}
|
||||
>
|
||||
<span className="truncate">
|
||||
{dateFilters[col]?.start || dateFilters[col]?.end
|
||||
? `${dateFilters[col].start ? dateFilters[col].start : '...'} - ${dateFilters[col].end ? dateFilters[col].end : '...'}`
|
||||
: 'Filter...'}
|
||||
</span>
|
||||
<ChevronDown className={cn("w-3 h-3 transition-transform", openFilter === col && "rotate-180")} />
|
||||
</button>
|
||||
{openFilter === col && (
|
||||
<DateFilterPopover
|
||||
selectedRange={dateFilters[col] || { start: '', end: '' }}
|
||||
onRangeChange={(range) => setDateFilters(prev => ({ ...prev, [col]: range }))}
|
||||
onClose={() => setOpenFilter(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === col ? null : col); }}
|
||||
className={cn(
|
||||
@@ -295,6 +349,8 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
||||
onClose={() => setOpenFilter(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
);})}
|
||||
|
||||
Reference in New Issue
Block a user