jcbowyer's picture
Clean HuggingFace deployment without binary files
e59d91d
Raw
History Blame Contribute Delete
49.8 kB
import React, { useState } from 'react';
import HomePage from './components/HomePage';
import ImpactDashboard from './components/ImpactDashboard';
import TopicNavigation from './components/TopicNavigation';
import DecisionCard from './components/shared/DecisionCard';
import SplitScreenView from './components/SplitScreenView';
import NonprofitCard from './components/NonprofitCard';
import { metadata } from './data/dashboardData';
import { Home, Grid, Building2, Heart, Church, Search, X, Calendar, Filter, MapPin, Edit2, Lightbulb } from 'lucide-react';
export default function App() {
const [viewMode, setViewMode] = useState('home'); // 'home', 'impact', 'browse', 'split-screen'
const [exploreMode, setExploreMode] = useState('decisions'); // 'decisions', 'organizations', or 'causes'
const [sectorView, setSectorView] = useState('all'); // 'all', 'public', 'nonprofits', 'churches'
const [selectedPersona, setSelectedPersona] = useState(null);
const [selectedTopic, setSelectedTopic] = useState(null);
const [selectedDecision, setSelectedDecision] = useState(null);
const [selectedTopics, setSelectedTopics] = useState([]);
const [selectedPatterns, setSelectedPatterns] = useState([]);
const [selectedResources, setSelectedResources] = useState([]);
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const [searchQuery, setSearchQuery] = useState('');
const [quickDateFilter, setQuickDateFilter] = useState('all'); // 'all', '7days', '30days', '90days'
const [quickTopicFilter, setQuickTopicFilter] = useState('all'); // 'all', 'health', 'education', 'infrastructure'
const [jurisdictionType, setJurisdictionType] = useState('city'); // 'nation', 'state', 'county', 'city', 'school-district'
const [jurisdictionName, setJurisdictionName] = useState('Tuscaloosa');
const [jurisdictionState, setJurisdictionState] = useState('AL');
const [showLocationModal, setShowLocationModal] = useState(false);
const [tempJurisdictionType, setTempJurisdictionType] = useState('city');
const [tempJurisdictionName, setTempJurisdictionName] = useState('');
const [tempJurisdictionState, setTempJurisdictionState] = useState('');
const handlePersonaSelect = (persona, topic) => {
setSelectedPersona(persona);
setSelectedTopic(topic);
setViewMode('impact');
};
const handleTopicSelect = (topicId) => {
setSelectedTopics([topicId]);
setViewMode('browse');
};
const handleTopicToggle = (topicId) => {
setSelectedTopics(prev =>
prev.includes(topicId)
? prev.filter(t => t !== topicId)
: [...prev, topicId]
);
};
const handlePatternToggle = (patternId) => {
setSelectedPatterns(prev =>
prev.includes(patternId)
? prev.filter(p => p !== patternId)
: [...prev, patternId]
);
};
const handleResourceToggle = (resourceId) => {
setSelectedResources(prev =>
prev.includes(resourceId)
? prev.filter(r => r !== resourceId)
: [...prev, resourceId]
);
};
const handleClearFilters = () => {
setSelectedTopics([]);
setSelectedPatterns([]);
setSelectedResources([]);
setStartDate(null);
setEndDate(null);
setSearchQuery('');
setQuickDateFilter('all');
setQuickTopicFilter('all');
};
const handleQuickDateFilter = (filter) => {
setQuickDateFilter(filter);
const today = new Date();
switch(filter) {
case '7days':
const sevenDaysAgo = new Date(today);
sevenDaysAgo.setDate(today.getDate() - 7);
setStartDate(sevenDaysAgo.toISOString().split('T')[0]);
setEndDate(today.toISOString().split('T')[0]);
break;
case '30days':
const thirtyDaysAgo = new Date(today);
thirtyDaysAgo.setDate(today.getDate() - 30);
setStartDate(thirtyDaysAgo.toISOString().split('T')[0]);
setEndDate(today.toISOString().split('T')[0]);
break;
case '90days':
const ninetyDaysAgo = new Date(today);
ninetyDaysAgo.setDate(today.getDate() - 90);
setStartDate(ninetyDaysAgo.toISOString().split('T')[0]);
setEndDate(today.toISOString().split('T')[0]);
break;
case 'all':
default:
setStartDate(null);
setEndDate(null);
break;
}
};
const handleQuickTopicFilter = (filter) => {
setQuickTopicFilter(filter);
if (filter === 'all') {
setSelectedTopics([]);
} else {
setSelectedTopics([filter]);
}
};
const handleBackToHome = () => {
setViewMode('home');
setSelectedPersona(null);
setSelectedTopic(null);
setSelectedDecision(null);
};
const handleDecisionClick = (decision) => {
setSelectedDecision(decision);
setViewMode('split-screen');
};
const handleSectorSelect = (sector) => {
setViewMode('browse');
// Set explore mode and sector based on selection
if (sector === 'public') {
setExploreMode('decisions');
setSectorView('public');
} else if (sector === 'nonprofits' || sector === 'churches') {
setExploreMode('organizations');
setSectorView(sector);
} else if (sector === 'all') {
setExploreMode('organizations');
setSectorView('all');
}
};
// Example decision data - would come from Python export
const exampleDecisions = [
{
decision_summary: "Approval of $850,000 athletic turf replacement project",
outcome: "Approved",
primary_rationale: "Athletic facilities are essential for student engagement and community pride. The current turf is beyond its useful life and poses safety concerns.",
supporters: [
{ name: "Board Member Johnson", role: "Board Member" },
{ name: "Athletic Director Smith", role: "Staff" }
],
opponents: [
{ name: "Parent Coalition for Health", role: "Public" }
],
vote_result: "6-1",
meeting_date: "2026-03-15",
tradeoffs_discussed: ["Athletic facilities vs. health screening programs"],
evidence_cited: [{ source: "Athletic Department Report" }],
policy_domain: "facilities",
ntee_code: "N20" // Recreation, Sports, and Athletics
},
{
decision_summary: "Tabled decision on dental screening partnership with West Alabama Dental Clinic",
outcome: "Tabled for further study",
primary_rationale: "Risk management concerns require additional legal review and liability analysis before proceeding with external health partnerships.",
supporters: [
{ name: "Patricia Johnson, Risk Manager", role: "Staff" }
],
opponents: [
{ name: "Dr. Sarah Martinez", role: "Public" },
{ name: "Parent Teacher Association", role: "Public" },
{ name: "Board Member Williams", role: "Board Member" }
],
vote_result: "5-2 to table",
meeting_date: "2026-01-18",
tradeoffs_discussed: ["Preventive care vs. perceived liability risk"],
evidence_cited: [{ source: "Risk Management Memo" }],
policy_domain: "health",
ntee_code: "E32", // School-Based Health Care
community_gap: {
description: "100% of surveyed parents want dental screenings for students",
nonprofit_filling_gap: true
}
},
{
decision_summary: "Reduction of nursing staff from 5 FTE to 3 FTE",
outcome: "Approved",
primary_rationale: "Budget constraints necessitate cost reductions. Nursing positions will be restructured to focus on emergency response rather than preventive services.",
supporters: [
{ name: "Superintendent Brown", role: "Staff" },
{ name: "Board Chair Thompson", role: "Board Member" }
],
opponents: [
{ name: "School Nurses Association", role: "Public" },
{ name: "Board Member Lee", role: "Board Member" }
],
vote_result: "4-3",
meeting_date: "2026-02-20",
tradeoffs_discussed: ["Cost savings vs. preventive health services"],
evidence_cited: [{ source: "FY2026 Budget Proposal" }],
policy_domain: "budget",
ntee_code: "E40", // Health - General and Rehabilitative
community_gap: {
description: "Students lost access to preventive health services",
nonprofit_filling_gap: true
}
}
];
// Example nonprofit data - would come from IRS/GuideStar API
const exampleNonprofits = [
{
name: "West Alabama Dental Initiative",
ein: "63-1234567",
ntee_code: "E32",
ntee_description: "School-Based Health Care",
mission: "Providing free dental screenings and preventive care to underserved students in West Alabama",
services: [
"Mobile dental unit visits to schools",
"Free toothbrush and fluoride kits",
"Dental education workshops for parents"
],
annual_budget: 125000,
students_served: 2400,
contact: {
website: "https://wadaldentalinitiative.org",
email: "info@wadaldentalinitiative.org",
phone: "(205) 555-0123"
},
volunteer_opportunities: true,
accepting_board_members: true
},
{
name: "Tuscaloosa Family Health Network",
ein: "63-7654321",
ntee_code: "E40",
ntee_description: "Health - General and Rehabilitative",
mission: "Connecting low-income families with preventive health services and wellness programs",
services: [
"Health screenings at community centers",
"Nutrition education programs",
"Mental health counseling referrals"
],
annual_budget: 280000,
families_served: 850,
contact: {
website: "https://tuscfamilyhealth.org",
email: "contact@tuscfamilyhealth.org",
phone: "(205) 555-0456"
},
volunteer_opportunities: true,
accepting_board_members: false
},
{
name: "After School Champions",
ein: "63-9876543",
ntee_code: "O50",
ntee_description: "Youth Development Programs",
mission: "Providing safe, enriching after-school programs that support academic success and healthy development",
services: [
"Homework help and tutoring",
"Healthy snacks and meals",
"Physical activity and sports",
"Health and wellness workshops"
],
annual_budget: 340000,
youth_served: 450,
contact: {
website: "https://afterschoolchamps.org",
email: "info@afterschoolchamps.org",
phone: "(205) 555-0789"
},
volunteer_opportunities: true,
accepting_board_members: true
},
{
name: "First Baptist Church Tuscaloosa - Health Ministry",
ein: "63-2345678",
ntee_code: "X20",
ntee_description: "Christian",
mission: "Faith-based health outreach serving Tuscaloosa families through free dental kits, health screenings, and nutrition education",
services: [
"Free dental hygiene kits distribution",
"Health screenings after Sunday service",
"Nutrition education classes",
"Mobile health unit partnership"
],
annual_budget: 45000,
families_served: 450,
contact: {
website: "https://fbctuscaloosa.org/health",
email: "health@fbctuscaloosa.org",
phone: "(205) 555-0200"
},
volunteer_opportunities: true,
accepting_board_members: false
},
{
name: "Tuscaloosa County Interfaith Dental Initiative",
ein: "63-3456789",
ntee_code: "X20",
ntee_description: "Christian",
mission: "Multi-faith collaboration providing free dental care to low-income students across Tuscaloosa County schools",
services: [
"Mobile dental unit serving Title I schools",
"Free toothbrush and fluoride programs",
"Parent education workshops",
"Dental emergency fund for families"
],
annual_budget: 180000,
students_served: 1600,
contact: {
website: "https://tuscaloosainterfaithdental.org",
email: "contact@tuscaloosainterfaithdental.org",
phone: "(205) 555-0300"
},
volunteer_opportunities: true,
accepting_board_members: true
},
{
name: "Catholic Social Services - Dental Outreach",
ein: "63-4567890",
ntee_code: "X20",
ntee_description: "Christian",
mission: "Diocese of Birmingham outreach providing dental care and health services to underserved communities",
services: [
"Quarterly dental clinics at parish hall",
"School dental screening partnerships",
"Dental supply distribution",
"Financial assistance for dental emergencies"
],
annual_budget: 95000,
families_served: 320,
contact: {
website: "https://cssalabama.org/dental",
email: "dental@cssalabama.org",
phone: "(205) 555-0400"
},
volunteer_opportunities: true,
accepting_board_members: false
}
];
// Filter decisions based on search, topics, patterns, resources, and date range
const filteredDecisions = exampleDecisions.filter(decision => {
const matchesSearch = searchQuery === '' ||
decision.decision_summary.toLowerCase().includes(searchQuery.toLowerCase()) ||
decision.primary_rationale.toLowerCase().includes(searchQuery.toLowerCase());
// Map policy_domain to topic IDs
const topicMap = {
'health': 'public-health',
'facilities': 'infrastructure',
'budget': 'education' // Example mapping
};
const decisionTopic = topicMap[decision.policy_domain] || decision.policy_domain;
const matchesTopic = selectedTopics.length === 0 || selectedTopics.includes(decisionTopic);
// Date filtering
const decisionDate = new Date(decision.meeting_date);
const matchesStartDate = !startDate || decisionDate >= new Date(startDate);
const matchesEndDate = !endDate || decisionDate <= new Date(endDate);
// TODO: Add pattern matching based on decision.patterns field
// TODO: Add resource matching based on decision.available_resources field
return matchesSearch && matchesTopic && matchesStartDate && matchesEndDate;
});
// Filter nonprofits and churches based on search and topic
const filteredNonprofits = exampleNonprofits.filter(org => {
const matchesSearch = searchQuery === '' ||
org.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
org.mission.toLowerCase().includes(searchQuery.toLowerCase()) ||
org.services.some(service => service.toLowerCase().includes(searchQuery.toLowerCase())) ||
org.ntee_description.toLowerCase().includes(searchQuery.toLowerCase());
// Map NTEE codes to topic IDs
const nteeTopicMap = {
'E': 'public-health', // Health
'F': 'public-health', // Mental Health
'K': 'public-health', // Food/Nutrition
'O': 'education', // Youth Development
'P': 'education', // Human Services
'X': 'public-health' // Religious (often health ministries)
};
const orgTopic = nteeTopicMap[org.ntee_code?.[0]] || 'public-health';
const matchesTopic = selectedTopics.length === 0 || selectedTopics.includes(orgTopic);
return matchesSearch && matchesTopic;
});
return (
<div>
{/* Sticky Header */}
<div style={{
position: 'sticky',
top: 0,
zIndex: 1000,
background: 'white',
borderBottom: '1px solid #eee',
boxShadow: '0 2px 8px rgba(0,0,0,0.05)'
}}>
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '16px 24px' }}>
{/* Logo and Title Row */}
<div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 12 }}>
{/* CommunityOne Logo */}
<img
src="/communityone_logo.svg"
alt="CommunityOne"
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
style={{
height: 100,
cursor: 'pointer',
objectFit: 'contain'
}}
/>
{/* User Location Display */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '8px 16px',
background: '#f5f5f5',
borderRadius: 8,
border: '1px solid #e0e0e0'
}}>
<MapPin size={18} style={{ color: '#059669' }} />
<div>
<div style={{ fontSize: 14, fontWeight: 600, color: '#111' }}>
{jurisdictionType === 'nation' ? 'United States' :
jurisdictionType === 'state' ? jurisdictionState :
jurisdictionType === 'county' ? `${jurisdictionName} County, ${jurisdictionState}` :
jurisdictionType === 'school-district' ? `${jurisdictionName} (${jurisdictionState})` :
`${jurisdictionName}, ${jurisdictionState}`}
</div>
<div style={{ fontSize: 12, color: '#666', textTransform: 'capitalize' }}>
{jurisdictionType === 'school-district' ? 'School District' : jurisdictionType}
</div>
</div>
<button
onClick={() => {
setTempJurisdictionType(jurisdictionType);
setTempJurisdictionName(jurisdictionName);
setTempJurisdictionState(jurisdictionState);
setShowLocationModal(true);
}}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 4,
display: 'flex',
alignItems: 'center',
color: '#059669'
}}
title="Change location"
>
<Edit2 size={16} />
</button>
</div>
<div style={{ flex: 1 }}>
<h1 style={{ fontSize: 20, fontWeight: 600, marginBottom: 4, color: '#111' }}>
{metadata.title}
</h1>
<p style={{ color: '#666', fontSize: 14, lineHeight: 1.4, margin: 0 }}>
{metadata.description}
</p>
</div>
</div>
{/* Global Search Bar */}
<div style={{
background: 'white',
border: '1px solid #eee',
borderRadius: 8,
padding: 12,
marginBottom: 12
}}>
<div style={{
position: 'relative',
display: 'flex',
alignItems: 'center'
}}>
<Search
size={16}
style={{
position: 'absolute',
left: 12,
color: '#999'
}}
/>
<input
type="text"
placeholder="Search decisions, nonprofits, churches, or topics..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
style={{
width: '100%',
padding: '10px 40px 10px 40px',
fontSize: 14,
border: '1px solid #ddd',
borderRadius: 6,
outline: 'none',
transition: 'border-color 0.2s'
}}
onFocus={(e) => e.target.style.borderColor = '#059669'}
onBlur={(e) => e.target.style.borderColor = '#ddd'}
/>
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
style={{
position: 'absolute',
right: 12,
background: 'none',
border: 'none',
cursor: 'pointer',
color: '#999',
display: 'flex',
alignItems: 'center',
padding: 4
}}
>
<X size={16} />
</button>
)}
</div>
</div>
{/* View Mode Toggle */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<div style={{ display: 'flex', gap: 6 }}>
<button
onClick={() => setViewMode('home')}
style={{
padding: '10px 20px',
borderRadius: 8,
fontSize: 15,
cursor: 'pointer',
border: '1px solid',
borderColor: viewMode === 'home' ? '#888' : '#ddd',
background: viewMode === 'home' ? '#f5f5f2' : 'white',
fontWeight: viewMode === 'home' ? 500 : 400,
color: viewMode === 'home' ? '#111' : '#666',
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
<Home size={14} />
Home
</button>
<button
onClick={() => {
setViewMode('browse');
setExploreMode('causes');
setSectorView('all');
}}
style={{
padding: '10px 20px',
borderRadius: 8,
fontSize: 15,
cursor: 'pointer',
border: '1px solid',
borderColor: (viewMode === 'browse' && exploreMode === 'causes') ? '#888' : '#ddd',
background: (viewMode === 'browse' && exploreMode === 'causes') ? '#f5f5f2' : 'white',
fontWeight: (viewMode === 'browse' && exploreMode === 'causes') ? 500 : 400,
color: (viewMode === 'browse' && exploreMode === 'causes') ? '#111' : '#666',
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
<Lightbulb size={14} />
Explore Causes
</button>
<button
onClick={() => {
setViewMode('browse');
setExploreMode('decisions');
setSectorView('public');
}}
style={{
padding: '10px 20px',
borderRadius: 8,
fontSize: 15,
cursor: 'pointer',
border: '1px solid',
borderColor: (viewMode === 'browse' && exploreMode === 'decisions') ? '#888' : '#ddd',
background: (viewMode === 'browse' && exploreMode === 'decisions') ? '#f5f5f2' : 'white',
fontWeight: (viewMode === 'browse' && exploreMode === 'decisions') ? 500 : 400,
color: (viewMode === 'browse' && exploreMode === 'decisions') ? '#111' : '#666',
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
<Building2 size={14} />
Explore Decisions
</button>
<button
onClick={() => {
setViewMode('browse');
setExploreMode('organizations');
setSectorView('all');
}}
style={{
padding: '10px 20px',
borderRadius: 8,
fontSize: 15,
cursor: 'pointer',
border: '1px solid',
borderColor: (viewMode === 'browse' && exploreMode === 'organizations') ? '#888' : '#ddd',
background: (viewMode === 'browse' && exploreMode === 'organizations') ? '#f5f5f2' : 'white',
fontWeight: (viewMode === 'browse' && exploreMode === 'organizations') ? 500 : 400,
color: (viewMode === 'browse' && exploreMode === 'organizations') ? '#111' : '#666',
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
<Heart size={14} />
Explore Organizations
</button>
</div>
</div>
</div>
{/* Main Content Area */}
<div style={{ maxWidth: 1200, margin: '0 auto', padding: 24 }}>
{/* Search Bar & Quick Filters (show in browse view) */}
{viewMode === 'browse' && (
<div style={{
background: 'white',
border: '1px solid #eee',
borderRadius: 12,
padding: 16,
marginBottom: 16,
boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
}}>
{/* Quick Filters */}
<div style={{
display: 'flex',
gap: 16,
flexWrap: 'wrap',
alignItems: 'center'
}}>
{/* Date Range Quick Filter */}
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Calendar size={14} style={{ color: '#666' }} />
<span style={{ fontSize: 13, color: '#666', fontWeight: 500 }}>Date:</span>
<div style={{ display: 'flex', gap: 4 }}>
{['all', '7days', '30days', '90days'].map(filter => (
<button
key={filter}
onClick={() => handleQuickDateFilter(filter)}
style={{
padding: '6px 12px',
fontSize: 12,
fontWeight: 500,
border: '1px solid',
borderColor: quickDateFilter === filter ? '#059669' : '#ddd',
background: quickDateFilter === filter ? '#dcfce7' : 'white',
color: quickDateFilter === filter ? '#059669' : '#666',
borderRadius: 6,
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
{filter === 'all' ? 'All Time' :
filter === '7days' ? 'Last 7 Days' :
filter === '30days' ? 'Last 30 Days' : 'Last 90 Days'}
</button>
))}
</div>
</div>
{/* Topic Quick Filter */}
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Filter size={14} style={{ color: '#666' }} />
<span style={{ fontSize: 13, color: '#666', fontWeight: 500 }}>Topic:</span>
<div style={{ display: 'flex', gap: 4 }}>
{[
{ id: 'all', label: 'All' },
{ id: 'public-health', label: 'Health' },
{ id: 'education', label: 'Education' },
{ id: 'infrastructure', label: 'Infrastructure' },
{ id: 'safety', label: 'Safety' }
].map(topic => (
<button
key={topic.id}
onClick={() => handleQuickTopicFilter(topic.id)}
style={{
padding: '6px 12px',
fontSize: 12,
fontWeight: 500,
border: '1px solid',
borderColor: quickTopicFilter === topic.id ? '#059669' : '#ddd',
background: quickTopicFilter === topic.id ? '#dcfce7' : 'white',
color: quickTopicFilter === topic.id ? '#059669' : '#666',
borderRadius: 6,
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
{topic.label}
</button>
))}
</div>
</div>
{/* Clear All Filters */}
{(searchQuery || quickDateFilter !== 'all' || quickTopicFilter !== 'all' ||
selectedPatterns.length > 0 || selectedResources.length > 0) && (
<button
onClick={handleClearFilters}
style={{
padding: '6px 12px',
fontSize: 12,
fontWeight: 500,
border: '1px solid #D85A30',
background: 'white',
color: '#D85A30',
borderRadius: 6,
cursor: 'pointer',
marginLeft: 'auto'
}}
>
Clear All
</button>
)}
</div>
</div>
)}
{/* Filters (show in browse view for decisions only) */}
{viewMode === 'browse' && exploreMode === 'decisions' && (
<TopicNavigation
selectedTopics={selectedTopics}
selectedPatterns={selectedPatterns}
selectedResources={selectedResources}
startDate={startDate}
endDate={endDate}
onTopicToggle={handleTopicToggle}
onPatternToggle={handlePatternToggle}
onResourceToggle={handleResourceToggle}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
onClearAll={handleClearFilters}
/>
)}
{/* Content */}
{viewMode === 'home' ? (
<HomePage
onPersonaSelect={handlePersonaSelect}
onTopicSelect={handleTopicSelect}
onSectorSelect={handleSectorSelect}
decisionsCount={exampleDecisions.length}
nonprofitsCount={exampleNonprofits.filter(org => !org.ntee_code.startsWith('X')).length}
churchesCount={exampleNonprofits.filter(org => org.ntee_code.startsWith('X')).length}
/>
) : viewMode === 'impact' ? (
<div style={{
background: 'white',
border: '1px solid #eee',
borderRadius: 12,
padding: '1.5rem',
boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
}}>
<button
onClick={handleBackToHome}
style={{
fontSize: 13,
color: '#666',
background: 'none',
border: 'none',
cursor: 'pointer',
marginBottom: 16,
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
← Back to Home
</button>
<ImpactDashboard persona={selectedPersona} topic={selectedTopic} />
</div>
) : viewMode === 'split-screen' ? (
<div style={{
background: 'white',
border: '1px solid #eee',
borderRadius: 12,
padding: '1.5rem',
boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
}}>
<button
onClick={() => setViewMode('browse')}
style={{
fontSize: 13,
color: '#666',
background: 'none',
border: 'none',
cursor: 'pointer',
marginBottom: 16,
display: 'flex',
alignItems: 'center',
gap: 4
}}
>
← Back to Decisions
</button>
<div style={{
fontSize: 12,
fontWeight: 600,
color: '#059669',
textTransform: 'uppercase',
letterSpacing: '0.05em',
marginBottom: 8
}}>
Split-Screen Analysis
</div>
<h2 style={{
fontSize: 22,
fontWeight: 600,
color: '#111',
marginBottom: 16
}}>
Government Decision ↔ Community Response
</h2>
<SplitScreenView
decision={selectedDecision}
nonprofits={exampleNonprofits}
/>
</div>
) : (
<div style={{
background: 'white',
border: '1px solid #eee',
borderRadius: 12,
padding: '1.5rem',
boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
}}>
<h2 style={{
fontSize: 20,
fontWeight: 500,
marginBottom: 16
}}>
{exploreMode === 'decisions' ? 'Explore Decisions' : exploreMode === 'organizations' ? 'Explore Organizations' : 'Explore Causes'}
{sectorView !== 'all' && sectorView !== 'public' && exploreMode !== 'causes' && (
<span style={{ color: '#999', fontWeight: 400 }}>
{' '}› {sectorView === 'nonprofits' ? 'Nonprofits' : 'Churches'}
</span>
)}
</h2>
{/* Sector Filter Buttons */}
{exploreMode === 'decisions' && (
<div style={{
display: 'flex',
gap: 8,
marginBottom: 20,
padding: 16,
background: '#f9fafb',
borderRadius: 8,
flexWrap: 'wrap'
}}>
<button
onClick={() => setSectorView('public')}
style={{
padding: '10px 16px',
borderRadius: 6,
fontSize: 14,
fontWeight: 500,
cursor: 'pointer',
border: '1px solid',
borderColor: sectorView === 'public' ? '#185FA5' : '#ddd',
background: sectorView === 'public' ? '#EFF6FF' : 'white',
color: sectorView === 'public' ? '#185FA5' : '#666',
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
<Building2 size={14} />
Public Sector ({filteredDecisions.length})
</button>
</div>
)}
{exploreMode === 'organizations' && (
<div style={{
display: 'flex',
gap: 8,
marginBottom: 20,
padding: 16,
background: '#f9fafb',
borderRadius: 8,
flexWrap: 'wrap'
}}>
<button
onClick={() => setSectorView('all')}
style={{
padding: '10px 16px',
borderRadius: 6,
fontSize: 14,
fontWeight: 500,
cursor: 'pointer',
border: '1px solid',
borderColor: sectorView === 'all' ? '#059669' : '#ddd',
background: sectorView === 'all' ? '#dcfce7' : 'white',
color: sectorView === 'all' ? '#059669' : '#666',
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
<Grid size={14} />
All Organizations
</button>
<button
onClick={() => setSectorView('nonprofits')}
style={{
padding: '10px 16px',
borderRadius: 6,
fontSize: 14,
fontWeight: 500,
cursor: 'pointer',
border: '1px solid',
borderColor: sectorView === 'nonprofits' ? '#059669' : '#ddd',
background: sectorView === 'nonprofits' ? '#dcfce7' : 'white',
color: sectorView === 'nonprofits' ? '#059669' : '#666',
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
<Heart size={14} />
Nonprofits ({filteredNonprofits.filter(org => !org.ntee_code.startsWith('X')).length})
</button>
<button
onClick={() => setSectorView('churches')}
style={{
padding: '10px 16px',
borderRadius: 6,
fontSize: 14,
fontWeight: 500,
cursor: 'pointer',
border: '1px solid',
borderColor: sectorView === 'churches' ? '#A855F7' : '#ddd',
background: sectorView === 'churches' ? '#FAF5FF' : 'white',
color: sectorView === 'churches' ? '#A855F7' : '#666',
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
<Church size={14} />
Churches ({filteredNonprofits.filter(org => org.ntee_code.startsWith('X')).length})
</button>
</div>
)}
{/* Public Sector Decisions */}
{exploreMode === 'decisions' && (
<div>
{filteredDecisions.length === 0 ? (
<div style={{
textAlign: 'center',
padding: '2rem',
color: '#999',
background: '#f9fafb',
borderRadius: 8
}}>
<p style={{ fontSize: 15, marginBottom: 8 }}>
No government decisions match your filters
</p>
<button
onClick={handleClearFilters}
style={{
fontSize: 14,
color: '#D85A30',
background: 'none',
border: 'none',
cursor: 'pointer',
textDecoration: 'underline'
}}
>
Clear all filters
</button>
</div>
) : (
<>
<div style={{ fontSize: 14, color: '#888', marginBottom: 12 }}>
Showing {filteredDecisions.length} decision{filteredDecisions.length !== 1 ? 's' : ''}
</div>
{filteredDecisions.map((decision, i) => (
<DecisionCard
key={i}
decision={decision}
onClick={() => handleDecisionClick(decision)}
/>
))}
</>
)}
</div>
)}
{/* Nonprofits Section */}
{exploreMode === 'organizations' && (sectorView === 'all' || sectorView === 'nonprofits') && (
<div style={{ marginBottom: sectorView === 'all' ? 32 : 0 }}>
{sectorView === 'all' && (
<h3 style={{
fontSize: 18,
fontWeight: 600,
color: '#059669',
marginBottom: 16,
display: 'flex',
alignItems: 'center',
gap: 8
}}>
<Heart size={20} />
Nonprofit Organizations
</h3>
)}
{filteredNonprofits.filter(org => !org.ntee_code.startsWith('X')).length === 0 ? (
<div style={{
textAlign: 'center',
padding: '2rem',
color: '#999',
background: '#f9fafb',
borderRadius: 8
}}>
<p style={{ fontSize: 15 }}>
No nonprofits match your search
</p>
</div>
) : (
filteredNonprofits
.filter(org => !org.ntee_code.startsWith('X'))
.map((nonprofit, i) => (
<NonprofitCard key={i} nonprofit={nonprofit} isChurch={false} />
))
)}
</div>
)}
{/* Churches Section */}
{exploreMode === 'organizations' && (sectorView === 'all' || sectorView === 'churches') && (
<div>
{sectorView === 'all' && (
<h3 style={{
fontSize: 18,
fontWeight: 600,
color: '#A855F7',
marginBottom: 16,
display: 'flex',
alignItems: 'center',
gap: 8
}}>
<Church size={20} />
Faith-Based Organizations
</h3>
)}
{filteredNonprofits.filter(org => org.ntee_code.startsWith('X')).length === 0 ? (
<div style={{
textAlign: 'center',
padding: '2rem',
color: '#999',
background: '#f9fafb',
borderRadius: 8
}}>
<p style={{ fontSize: 15 }}>
No churches match your search
</p>
</div>
) : (
filteredNonprofits
.filter(org => org.ntee_code.startsWith('X'))
.map((church, i) => (
<NonprofitCard key={i} nonprofit={church} isChurch={true} />
))
)}
</div>
)}
{/* Causes Section */}
{exploreMode === 'causes' && (
<div>
<div style={{
textAlign: 'center',
padding: '3rem 2rem',
color: '#666',
background: '#f9fafb',
borderRadius: 12,
border: '2px dashed #ddd'
}}>
<Lightbulb size={48} style={{ color: '#D85A30', marginBottom: 16 }} />
<h3 style={{
fontSize: 20,
fontWeight: 600,
color: '#111',
marginBottom: 12
}}>
Explore Root Causes
</h3>
<p style={{ fontSize: 15, lineHeight: 1.6, maxWidth: 600, margin: '0 auto', marginBottom: 16 }}>
Discover the underlying factors driving policy decisions in your community.
Understand the connections between social determinants, community needs, and government actions.
</p>
<p style={{ fontSize: 14, color: '#999', fontStyle: 'italic' }}>
Coming soon: Interactive cause analysis and trend mapping
</p>
</div>
</div>
)}
</div>
)}
</div>
</div>
{/* Location Selection Modal */}
{showLocationModal && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 2000
}}>
<div style={{
background: 'white',
borderRadius: 12,
padding: 32,
maxWidth: 500,
width: '90%',
boxShadow: '0 10px 40px rgba(0,0,0,0.2)'
}}>
<h2 style={{
fontSize: 22,
fontWeight: 600,
marginBottom: 8,
color: '#111'
}}>
Select Your Jurisdiction
</h2>
<p style={{
color: '#666',
fontSize: 14,
marginBottom: 24
}}>
Choose the level and location to see relevant decisions and organizations.
</p>
<div style={{ marginBottom: 16 }}>
<label style={{
display: 'block',
fontSize: 14,
fontWeight: 500,
marginBottom: 8,
color: '#111'
}}>
Jurisdiction Level
</label>
<select
value={tempJurisdictionType}
onChange={(e) => setTempJurisdictionType(e.target.value)}
style={{
width: '100%',
padding: '10px 12px',
fontSize: 15,
border: '1px solid #ddd',
borderRadius: 6,
outline: 'none',
cursor: 'pointer'
}}
onFocus={(e) => e.target.style.borderColor = '#059669'}
onBlur={(e) => e.target.style.borderColor = '#ddd'}
>
<option value="nation">Nation</option>
<option value="state">State</option>
<option value="county">County</option>
<option value="city">City</option>
<option value="school-district">School District</option>
</select>
</div>
{tempJurisdictionType !== 'nation' && (
<div style={{ marginBottom: 16 }}>
<label style={{
display: 'block',
fontSize: 14,
fontWeight: 500,
marginBottom: 8,
color: '#111'
}}>
{tempJurisdictionType === 'state' ? 'State' :
tempJurisdictionType === 'county' ? 'County Name' :
tempJurisdictionType === 'school-district' ? 'School District Name' :
'City Name'}
</label>
<input
type="text"
value={tempJurisdictionName}
onChange={(e) => setTempJurisdictionName(e.target.value)}
placeholder={
tempJurisdictionType === 'state' ? 'e.g., Alabama' :
tempJurisdictionType === 'county' ? 'e.g., Tuscaloosa' :
tempJurisdictionType === 'school-district' ? 'e.g., Tuscaloosa City Schools' :
'e.g., Tuscaloosa'
}
style={{
width: '100%',
padding: '10px 12px',
fontSize: 15,
border: '1px solid #ddd',
borderRadius: 6,
outline: 'none'
}}
onFocus={(e) => e.target.style.borderColor = '#059669'}
onBlur={(e) => e.target.style.borderColor = '#ddd'}
/>
</div>
)}
{tempJurisdictionType !== 'nation' && tempJurisdictionType !== 'state' && (
<div style={{ marginBottom: 24 }}>
<label style={{
display: 'block',
fontSize: 14,
fontWeight: 500,
marginBottom: 8,
color: '#111'
}}>
State
</label>
<input
type="text"
value={tempJurisdictionState}
onChange={(e) => setTempJurisdictionState(e.target.value)}
placeholder="e.g., AL"
maxLength="2"
style={{
width: '100%',
padding: '10px 12px',
fontSize: 15,
border: '1px solid #ddd',
borderRadius: 6,
outline: 'none',
textTransform: 'uppercase'
}}
onFocus={(e) => e.target.style.borderColor = '#059669'}
onBlur={(e) => e.target.style.borderColor = '#ddd'}
/>
</div>
)}
{tempJurisdictionType === 'state' && (
<div style={{ marginBottom: 24 }}>
<label style={{
display: 'block',
fontSize: 14,
fontWeight: 500,
marginBottom: 8,
color: '#111'
}}>
State Code
</label>
<input
type="text"
value={tempJurisdictionState}
onChange={(e) => setTempJurisdictionState(e.target.value)}
placeholder="e.g., AL"
maxLength="2"
style={{
width: '100%',
padding: '10px 12px',
fontSize: 15,
border: '1px solid #ddd',
borderRadius: 6,
outline: 'none',
textTransform: 'uppercase'
}}
onFocus={(e) => e.target.style.borderColor = '#059669'}
onBlur={(e) => e.target.style.borderColor = '#ddd'}
/>
</div>
)}
<div style={{
display: 'flex',
gap: 12,
justifyContent: 'flex-end'
}}>
<button
onClick={() => setShowLocationModal(false)}
style={{
padding: '10px 20px',
fontSize: 15,
border: '1px solid #ddd',
background: 'white',
borderRadius: 6,
cursor: 'pointer',
color: '#666'
}}
>
Cancel
</button>
<button
onClick={() => {
setJurisdictionType(tempJurisdictionType);
setJurisdictionName(tempJurisdictionName);
setJurisdictionState(tempJurisdictionState);
setShowLocationModal(false);
}}
style={{
padding: '10px 20px',
fontSize: 15,
border: 'none',
background: '#059669',
color: 'white',
borderRadius: 6,
cursor: 'pointer',
fontWeight: 500
}}
>
Save Location
</button>
</div>
</div>
</div>
)}
</div>
);
}