Compare commits

...

2 Commits

Author SHA1 Message Date
Shreya Shankar 7847b26743 feat: add ability to sort chronologically for epstein emails 2025-11-16 08:47:37 -08:00
Shreya Shankar 8dbe96d87c feat: add ability to sort chronologically for epstein emails 2025-11-16 08:45:45 -08:00
1 changed files with 87 additions and 32 deletions

View File

@ -36,6 +36,12 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
interface Participant {
name: string;
@ -595,6 +601,7 @@ export default function EpsteinEmailExplorer() {
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
const [showCrimesOnly, setShowCrimesOnly] = useState(false);
const [showVictimsOnly, setShowVictimsOnly] = useState(false);
const [chronologicalOrder, setChronologicalOrder] = useState<"none" | "asc" | "desc">("none");
const [filtersExpanded, setFiltersExpanded] = useState(true);
const [emails, setEmails] = useState<Email[]>([]);
const [totalEmails, setTotalEmails] = useState(0);
@ -646,12 +653,15 @@ export default function EpsteinEmailExplorer() {
setShowMobileWarning(false);
};
const handleDownloadInsights = () => {
const handleDownloadInsights = (downloadAll: boolean) => {
try {
setIsDownloading(true);
// Use the already-loaded emails data
const compactEmails = emails.map((email) => ({
// Choose which emails to download and sort chronologically (oldest first)
const emailsToDownload = downloadAll ? emails : filteredEmails;
const sortedEmails = [...emailsToDownload].sort((a, b) => a.date.localeCompare(b.date));
const compactEmails = sortedEmails.map((email) => ({
subject: email.subject || '',
date: email.date || '',
participants: email.participants?.map((p) => p.name).filter(Boolean) || [],
@ -677,7 +687,10 @@ export default function EpsteinEmailExplorer() {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'epstein_emails_insights.txt';
const filename = downloadAll
? 'epstein_emails_insights_all.txt'
: 'epstein_emails_insights_filtered.txt';
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
@ -821,16 +834,25 @@ export default function EpsteinEmailExplorer() {
result = result.filter(e => e.mentions_victims);
}
// Sort: emails with potential crimes first
return result.sort((a, b) => {
const aHasCrimes = a.potential_crimes && a.potential_crimes.trim() ? 1 : 0;
const bHasCrimes = b.potential_crimes && b.potential_crimes.trim() ? 1 : 0;
if (bHasCrimes !== aHasCrimes) return bHasCrimes - aHasCrimes;
// Sort based on chronological order preference
if (chronologicalOrder === "asc") {
// Oldest first
return result.sort((a, b) => a.date.localeCompare(b.date));
} else if (chronologicalOrder === "desc") {
// Newest first
return result.sort((a, b) => b.date.localeCompare(a.date));
} else {
// Default: emails with potential crimes first, then by date (newest first)
return result.sort((a, b) => {
const aHasCrimes = a.potential_crimes && a.potential_crimes.trim() ? 1 : 0;
const bHasCrimes = b.potential_crimes && b.potential_crimes.trim() ? 1 : 0;
if (bHasCrimes !== aHasCrimes) return bHasCrimes - aHasCrimes;
// Then by date (newest first)
return b.date.localeCompare(a.date);
});
}, [emails, attributeFilters, showCrimesOnly, showVictimsOnly, dateFrom, dateTo]);
// Then by date (newest first)
return b.date.localeCompare(a.date);
});
}
}, [emails, attributeFilters, showCrimesOnly, showVictimsOnly, dateFrom, dateTo, chronologicalOrder]);
// Lightweight count of unique people (always computed for tab label)
const peopleCount = useMemo(() => {
@ -956,25 +978,42 @@ export default function EpsteinEmailExplorer() {
)}
{/* Download Insights Button */}
<div className="flex justify-end mb-4">
<Button
onClick={handleDownloadInsights}
disabled={isLoadingInitial || emails.length === 0 || isDownloading}
variant="outline"
size="sm"
className="bg-white"
>
{isDownloading ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Preparing download...
</>
) : (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
disabled={isLoadingInitial || emails.length === 0 || isDownloading}
variant="outline"
size="sm"
className="bg-white"
>
{isDownloading ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Preparing download...
</>
) : (
<>
<Download className="h-4 w-4 mr-2" />
Download Insights for AI Chat
<ChevronDown className="h-4 w-4 ml-2" />
</>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleDownloadInsights(true)}>
<Download className="h-4 w-4 mr-2" />
Download Insights for AI Chat
</>
)}
</Button>
Complete Dataset ({emails.length} emails)
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleDownloadInsights(false)}
disabled={filteredEmails.length === emails.length}
>
<Filter className="h-4 w-4 mr-2" />
Current View ({filteredEmails.length} emails)
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<StatsDashboard emails={filteredEmails} />
@ -1182,7 +1221,7 @@ export default function EpsteinEmailExplorer() {
</div>
{/* Quick filters */}
<div className="flex gap-4 pt-2 border-t">
<div className="flex flex-wrap gap-4 pt-2 border-t">
<div className="flex items-center space-x-2">
<Checkbox
id="crimes"
@ -1204,6 +1243,22 @@ export default function EpsteinEmailExplorer() {
Show only emails mentioning victims
</Label>
</div>
<div className="flex items-center space-x-2">
<Label htmlFor="chronological" className="whitespace-nowrap">
Sort by:
</Label>
<Select value={chronologicalOrder} onValueChange={(v) => setChronologicalOrder(v as "none" | "asc" | "desc")}>
<SelectTrigger id="chronological" className="w-[180px]">
<SelectValue placeholder="Select order..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">Flagged first</SelectItem>
<SelectItem value="asc">Oldest first</SelectItem>
<SelectItem value="desc">Newest first</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="text-sm text-gray-600">