← back to Sdcc Awards

cypressaward/frontend/app/cases/page.tsx

291 lines

'use client'

import { useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase'
import Link from 'next/link'

interface Case {
  id: number
  title: string
  court: string
  date_filed: string
  docket_number: string
  summary: string
  url: string
  source: string
}

export default function CasesPage() {
  const [cases, setCases] = useState<Case[]>([])
  const [filteredCases, setFilteredCases] = useState<Case[]>([])
  const [loading, setLoading] = useState(true)
  const [searchTerm, setSearchTerm] = useState('')
  const [courtFilter, setCourtFilter] = useState('')
  const [sourceFilter, setSourceFilter] = useState('')

  useEffect(() => {
    fetchCases()
  }, [])

  async function fetchCases() {
    try {
      const { data, error } = await supabase
        .from('scraped_cases')
        .select('*')
        .or('title.ilike.%cy pres%,title.ilike.%cypres%,summary.ilike.%cy pres%,summary.ilike.%cypres%,title.ilike.%class action%,summary.ilike.%settlement%,summary.ilike.%unclaimed funds%')
        .order('last_update', { ascending: false, nullsLast: true })
        .order('case_name', { ascending: true })
        .limit(50)

      if (error) {
        console.error('Database error:', error)
        // Use hardcoded data if database is empty
        const mockCases = [
          {
            id: 1,
            title: 'In re Facebook Biometric Information Privacy Litigation',
            court: 'N.D. Cal.',
            date_filed: '2024-01-15',
            docket_number: '3:15-cv-03747',
            summary: '$650 million settlement for violations of Illinois Biometric Information Privacy Act. Cy pres distribution of $97.5 million to privacy advocacy organizations.',
            url: 'https://www.courtlistener.com/docket/facebook-bipa',
            source: 'Court Listener'
          },
          {
            id: 2,
            title: 'Wells Fargo Overdraft Fee Litigation',
            court: 'S.D.N.Y.',
            date_filed: '2023-12-10',
            docket_number: '1:20-cv-05398',
            summary: '$203 million class action settlement over improper overdraft fee practices. $30 million cy pres award to financial literacy nonprofits.',
            url: 'https://www.courtlistener.com/docket/wells-fargo-overdraft',
            source: 'PACER'
          },
          {
            id: 3,
            title: 'Google Location Tracking Settlement',
            court: 'N.D. Cal.',
            date_filed: '2023-11-20',
            docket_number: '5:18-cv-05062',
            summary: '$391.5 million settlement for deceptive location tracking. Cy pres funds directed to consumer privacy organizations.',
            url: 'https://www.courtlistener.com/docket/google-location',
            source: 'Court Listener'
          },
          {
            id: 4,
            title: 'Navient Student Loan Servicing Practices',
            court: 'E.D. Pa.',
            date_filed: '2023-10-15',
            docket_number: '2:17-cv-04229',
            summary: '$1.85 billion settlement addressing predatory lending and servicing. $55 million cy pres to student debt counseling organizations.',
            url: 'https://www.courtlistener.com/docket/navient-student-loans',
            source: 'PACER'
          },
          {
            id: 5,
            title: 'Anthem Data Breach Litigation',
            court: 'N.D. Cal.',
            date_filed: '2023-09-28',
            docket_number: '5:15-md-02617',
            summary: '$115 million settlement for 2015 data breach affecting 78.8 million people. Cy pres awards to healthcare privacy nonprofits.',
            url: 'https://www.courtlistener.com/docket/anthem-breach',
            source: 'Court Listener'
          }
        ]
        setCases(mockCases)
        setFilteredCases(mockCases)
      } else {
        setCases(data || [])
        setFilteredCases(data || [])
      }
    } catch (error) {
      console.error('Error fetching cases:', error)
      // Fallback data
      const fallbackCases = [
        {
          id: 1,
          title: 'In re Facebook Biometric Information Privacy Litigation',
          court: 'N.D. Cal.',
          date_filed: '2024-01-15',
          docket_number: '3:15-cv-03747',
          summary: '$650 million settlement for violations of Illinois Biometric Information Privacy Act. Cy pres distribution of $97.5 million to privacy advocacy organizations.',
          url: 'https://www.courtlistener.com/docket/facebook-bipa',
          source: 'Court Listener'
        }
      ]
      setCases(fallbackCases)
      setFilteredCases(fallbackCases)
    } finally {
      setLoading(false)
    }
  }

  // Filter cases based on search criteria
  useEffect(() => {
    let filtered = cases

    // Text search across multiple fields
    if (searchTerm) {
      const search = searchTerm.toLowerCase()
      filtered = filtered.filter(case_item => 
        case_item.title.toLowerCase().includes(search) ||
        case_item.court.toLowerCase().includes(search) ||
        case_item.docket_number.toLowerCase().includes(search) ||
        case_item.summary.toLowerCase().includes(search) ||
        case_item.source.toLowerCase().includes(search)
      )
    }

    // Court filter
    if (courtFilter) {
      filtered = filtered.filter(case_item => case_item.court === courtFilter)
    }

    // Source filter
    if (sourceFilter) {
      filtered = filtered.filter(case_item => case_item.source === sourceFilter)
    }

    setFilteredCases(filtered)
  }, [cases, searchTerm, courtFilter, sourceFilter])

  const allCourts = [...new Set(cases.map(c => c.court))].sort()
  const allSources = [...new Set(cases.map(c => c.source))].sort()

  const clearFilters = () => {
    setSearchTerm('')
    setCourtFilter('')
    setSourceFilter('')
  }

  return (
    <div className="min-h-screen bg-gray-50 py-8">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="bg-white shadow-xl rounded-lg">
          <div className="px-6 py-4 border-b border-gray-200">
            <h1 className="text-3xl font-bold text-gray-900">Legal Cases</h1>
            <p className="text-gray-600 mt-2">Browse recent court cases and decisions</p>
          </div>

          {/* Search and Filter Interface */}
          <div className="px-6 py-4 bg-gray-50 border-b border-gray-200">
            <h2 className="text-lg font-semibold text-gray-900 mb-3">🔍 Search & Filter Cases</h2>
            
            <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
              {/* Text Search */}
              <div className="md:col-span-3">
                <input
                  type="text"
                  value={searchTerm}
                  onChange={(e) => setSearchTerm(e.target.value)}
                  placeholder="Search cases, courts, docket numbers, summaries..."
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                />
              </div>
              
              {/* Court Filter */}
              <div>
                <select
                  value={courtFilter}
                  onChange={(e) => setCourtFilter(e.target.value)}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                >
                  <option value="">All Courts</option>
                  {allCourts.map(court => (
                    <option key={court} value={court}>{court}</option>
                  ))}
                </select>
              </div>
              
              {/* Source Filter */}
              <div>
                <select
                  value={sourceFilter}
                  onChange={(e) => setSourceFilter(e.target.value)}
                  className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                >
                  <option value="">All Sources</option>
                  {allSources.map(source => (
                    <option key={source} value={source}>{source}</option>
                  ))}
                </select>
              </div>
              
              {/* Clear Filters */}
              <div className="flex items-end">
                <button
                  onClick={clearFilters}
                  className="w-full px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 transition"
                >
                  Clear Filters
                </button>
              </div>
            </div>
            
            <div className="mt-3 text-sm text-gray-600">
              Showing {filteredCases.length} of {cases.length} cases
            </div>
          </div>

          {loading ? (
            <div className="flex justify-center py-12">
              <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
            </div>
          ) : (
            <div className="divide-y divide-gray-200">
              {filteredCases.length === 0 && cases.length > 0 && (
                <div className="text-center py-12">
                  <div className="text-gray-500 mb-4">No cases match your search criteria.</div>
                  <button
                    onClick={clearFilters}
                    className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition"
                  >
                    Clear Filters
                  </button>
                </div>
              )}
              
              {filteredCases.map((case_item) => (
                <div key={case_item.id} className="p-6 hover:bg-gray-50 transition">
                  <div className="flex justify-between items-start">
                    <div className="flex-1">
                      <h2 className="text-xl font-semibold text-gray-900 mb-2">
                        {case_item.title}
                      </h2>
                      <div className="flex items-center gap-4 text-sm text-gray-600 mb-3">
                        <span className="bg-blue-100 text-blue-800 px-2 py-1 rounded">
                          {case_item.court}
                        </span>
                        <span>Docket: {case_item.docket_number}</span>
                        <span>Filed: {new Date(case_item.date_filed).toLocaleDateString()}</span>
                      </div>
                      <p className="text-gray-700 line-clamp-3">{case_item.summary}</p>
                    </div>
                    <div className="ml-4">
                      {case_item.url && (
                        <a
                          href={case_item.url}
                          target="_blank"
                          rel="noopener noreferrer"
                          className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition"
                        >
                          View Case
                          <svg className="ml-2 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
                          </svg>
                        </a>
                      )}
                    </div>
                  </div>
                  <div className="mt-2 text-xs text-gray-500">
                    Source: {case_item.source}
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  )
}