Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 58 additions & 18 deletions client/app/dashboard/data-feed/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"use client"

import { useEffect, useState } from "react"
import { ArrowUpRight, Info, X } from "lucide-react"
import { ArrowUpRight, Search, Info, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { SentimentBadge } from "@/components/sentiment-badge"
import { BlueskyIcon } from "@/components/bluesky-icon"
import { Lordicon } from "@/components/lordicon"
Expand Down Expand Up @@ -76,6 +77,8 @@ export default function DataFeedPage() {
const [loading, setLoading] = useState(true)
const [loadingCrises, setLoadingCrises] = useState(false)
const [lastUpdated, setLastUpdated] = useState<Date | null>(null)
const [searchQuery, setSearchQuery] = useState("")
const [debouncedSearch, setDebouncedSearch] = useState("")
const [showDisclaimer, setShowDisclaimer] = useState(true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused state variable.

The showDisclaimer state is declared but never used anywhere in the component. This should be removed to keep the code clean.

Apply this diff to remove the unused state:

  const [searchQuery, setSearchQuery] = useState("")
  const [debouncedSearch, setDebouncedSearch] = useState("")
- const [showDisclaimer, setShowDisclaimer] = useState(true)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In client/app/dashboard/data-feed/page.tsx around line 82, the component
declares an unused state variable `showDisclaimer` via useState which is never
referenced; remove the entire declaration (importing useState is optional) and
any related unused imports so the component no longer defines or references
`showDisclaimer`, and tidy up imports if useState becomes unused.


useEffect(() => {
Expand All @@ -102,11 +105,21 @@ export default function DataFeedPage() {
fetchInitialData()
}, [])

// Debounce search input
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedSearch(searchQuery)
setCurrentPage(1) // Reset to first page on search
}, 500)

return () => clearTimeout(timer)
}, [searchQuery])

useEffect(() => {
async function fetchCrises() {
try {
setLoadingCrises(true)
const crisesData = await getWeeklyCrises(7, currentPage, 10)
const crisesData = await getWeeklyCrises(7, currentPage, 10, debouncedSearch)
setWeeklyCrises(crisesData.crises)
setPagination(crisesData.pagination)
setLastUpdated(new Date())
Expand All @@ -117,7 +130,7 @@ export default function DataFeedPage() {
}
}
fetchCrises()
}, [currentPage])
}, [currentPage, debouncedSearch])

const formatDateTime = (dateString: string) => {
const date = new Date(dateString)
Expand Down Expand Up @@ -462,15 +475,29 @@ export default function DataFeedPage() {
{/* Crisis Feed */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
Recent Crisis Events
<Badge variant="secondary" className="ml-2">
{pagination?.total_count || 0} total
</Badge>
</CardTitle>
<p className="text-sm text-muted-foreground">
Events detected in the past 7 days from Bluesky posts
</p>
<div className="flex items-center justify-between gap-4">
<div>
<CardTitle className="flex items-center gap-2">
Recent Crisis Events
<Badge variant="secondary" className="ml-2">
{pagination?.total_count || 0} total
</Badge>
</CardTitle>
<p className="text-sm text-muted-foreground">
Events detected in the past 7 days from Bluesky posts
</p>
</div>
<div className="relative w-80">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder="Search by name, location, or disaster type..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
</div>
</CardHeader>
<CardContent>
{loadingCrises ? (
Expand Down Expand Up @@ -585,12 +612,25 @@ export default function DataFeedPage() {
colorize="currentColor"
/>
</div>
<p className="text-lg font-medium text-muted-foreground">
No crises detected in the past week. This is good news!
</p>
<p className="text-sm text-muted-foreground mt-2">
Try adjusting your time range or filters
</p>
{debouncedSearch ? (
<>
<p className="text-lg font-medium text-muted-foreground">
No results found for &quot;{debouncedSearch}&quot;
</p>
<p className="text-sm text-muted-foreground mt-2">
Try a different search term or clear your search
</p>
</>
) : (
<>
<p className="text-lg font-medium text-muted-foreground">
No crises detected in the past week. This is good news!
</p>
<p className="text-sm text-muted-foreground mt-2">
Try adjusting your time range or filters
</p>
</>
)}
</div>
)}

Expand Down
14 changes: 12 additions & 2 deletions client/lib/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,17 @@ export async function getDataFeedOverview() {
}>('/api/data-feed/overview');
}

export async function getWeeklyCrises(days = 7, page = 1, pageSize = 10) {
export async function getWeeklyCrises(days = 7, page = 1, pageSize = 10, search = "") {
const params = new URLSearchParams({
days: days.toString(),
page: page.toString(),
page_size: pageSize.toString(),
});

if (search && search.trim()) {
params.append('search', search.trim());
}

return apiGet<{
crises: Array<{
id: number;
Expand All @@ -116,7 +126,7 @@ export async function getWeeklyCrises(days = 7, page = 1, pageSize = 10) {
has_next: boolean;
has_prev: boolean;
}
}>(`/api/data-feed/weekly-crises?days=${days}&page=${page}&page_size=${pageSize}`);
}>(`/api/data-feed/weekly-crises?${params.toString()}`);
}

// Analysis API functions
Expand Down
19 changes: 16 additions & 3 deletions server/routers/data_feed.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from sqlalchemy import func, desc, and_
from sqlalchemy import func, desc, and_, or_
from datetime import datetime, timedelta
from typing import Optional
from db_utils.db import SessionLocal, DataFeed, Post, Disaster, CollectionRun
Expand Down Expand Up @@ -82,18 +82,31 @@ def get_weekly_crises(
days: int = Query(default=7, ge=1, le=90),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=10, ge=1, le=100),
search: str = Query(default="", description="Search crises by name, location, or disaster type"),
db: Session = Depends(get_db),
):
"""Get recent crisis events with pagination (default last 7 days)"""
"""Get recent crisis events with pagination and search (default last 7 days)"""
cutoff_date = datetime.utcnow() - timedelta(days=days)

query = (
db.query(Disaster)
.join(Post, Disaster.post_id == Post.id, isouter=True)
.filter(Disaster.extracted_at >= cutoff_date)
.order_by(desc(Disaster.extracted_at))
)

# Apply search filter if provided
if search and search.strip():
search_term = f"%{search.strip()}%"
query = query.filter(
or_(
Disaster.description.ilike(search_term),
Disaster.location_name.ilike(search_term),
Post.disaster_type.ilike(search_term)
)
)

query = query.order_by(desc(Disaster.extracted_at))

total_count = query.count()
total_pages = (total_count + page_size - 1) // page_size

Expand Down