Skip to content
Open
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
35 changes: 31 additions & 4 deletions apps/frontend/src/app/accounts/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';
"use client";

import React from 'react';
import React, { useEffect, useState } from 'react';
import { useApi } from '@/hooks/useApi';
import StaffCard from '../components/StaffCard';
import { User } from '@/types';

Expand Down Expand Up @@ -28,18 +29,44 @@ export const teamMembers = mockUsers.filter(u => !u.is_admin);


export default function AccountsPage() {
const api = useApi();
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
async function fetchUsers() {
try {
const json = await api.get<User[] | { data: User[] }>('http://localhost:3001/users');
const list = Array.isArray(json) ? json : (json && 'data' in json ? json.data : []);
setUsers(list);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load users');
setUsers([]);
} finally {
setLoading(false);
}
}
fetchUsers();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const shownFacilitation = users.length ? users.filter(u => u.is_admin) : facilitationTeam;
const shownTeam = users.length ? users.filter(u => !u.is_admin) : teamMembers;
return (
<div className="!p-6">
<h1 className="![font-family:var(--font-heading)] !text-[length:var(--font-size-heading-1)] !font-semibold">Accounts</h1>
<h3 className="![font-family:var(--font-heading)] !text-[length:var(--font-size-heading-3)] !font-semibold">Core BRANCH Facilitation Team</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 !pt-3 !pb-7">
{facilitationTeam.map(user => (
{loading && <p>Loading users...</p>}
{error && <p style={{ color: 'var(--color-error-red)' }}>{error}</p>}
{!loading && !error && shownFacilitation.map(user => (
<StaffCard key={user.user_id} name={user.name} email={user.email} />
))}
</div>
<h3 className="![font-family:var(--font-heading)] !text-[length:var(--font-size-heading-3)] !font-semibold">BRANCH Team Members</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 !pt-3 !pb-7">
{teamMembers.map(user => (
{!loading && !error && shownTeam.map(user => (
<StaffCard key={user.user_id} name={user.name} email={user.email} />
))}
</div>
Expand Down
108 changes: 81 additions & 27 deletions apps/frontend/src/app/donations/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import NavBar from "../components/Navbar";
import { HStack, Input, Button, Table, Dialog, Portal, CloseButton, Stack } from "@chakra-ui/react";
import TextInputField from '../components/TextInputField';
Expand All @@ -14,29 +14,79 @@ type Donation = {
project_name: string;
amount: number;
};
import { useApi } from '@/hooks/useApi';

const mockDonors = ['Green Future Foundation', 'Horizon Trust', 'Bright Path Nonprofit', 'Unity Giving Circle', 'Sunrise Community Fund'];
const mockProjects = ['Clean Water Initiative', 'Youth Mentorship Program', 'Food Security Drive', 'Urban Garden Project', 'STEM Education Fund'];
const donorsBase = 'http://localhost:3003';
const projectsBase = 'http://localhost:3002';

const mockDonations: Donation[] = [
{ donor_id: 1, date: '03/12/2024', project_name: 'Clean Water Initiative', amount: 5000 },
{ donor_id: 2, date: '01/05/2024', project_name: 'Youth Mentorship Program', amount: 12000 },
{ donor_id: 3, date: '02/28/2024', project_name: 'Food Security Drive', amount: 750 },
{ donor_id: 4, date: '03/30/2024', project_name: 'Urban Garden Project', amount: 3200 },
{ donor_id: 5, date: '04/01/2024', project_name: 'STEM Education Fund', amount: 8500 },
{ donor_id: 6, date: '02/14/2024', project_name: 'Shelter Renovation', amount: 1500 },
{ donor_id: 7, date: '01/20/2024', project_name: 'Mental Health Outreach', amount: 20000 },
{ donor_id: 8, date: '03/05/2024', project_name: 'Digital Literacy Program', amount: 9750 },
{ donor_id: 9, date: '04/10/2024', project_name: 'Community Health Fair', amount: 4300 },
{ donor_id: 10, date: '03/22/2024', project_name: 'After-School Arts', amount: 600 },
];

export default function DonationsPage() {
const [currentPage, setCurrentPage] = useState(1);
const rowsPerPage = 10;

const totalPages = Math.ceil(mockDonations.length / rowsPerPage);
const currentDonations = mockDonations.slice(
const api = useApi();
const [donations, setDonations] = useState<Donation[]>([]);
const [donorNames, setDonorNames] = useState<string[]>([]);
const [projectNames, setProjectNames] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
async function loadAll() {
try {
const [donationsJson, donorsJson, projectsJson] = await Promise.all([
api.get<Donation[] | { data: Donation[] }>(`${donorsBase}/donations`),
api.get<string[] | { data: unknown[] }>(`${donorsBase}/donors`),
api.get<string[] | { data: unknown[] }>(`${projectsBase}/projects`),
]);

const dList = Array.isArray(donationsJson) ? donationsJson : (donationsJson && 'data' in donationsJson ? donationsJson.data : []);
const dn = Array.isArray(donorsJson) ? donorsJson : (donorsJson && 'data' in donorsJson ? donorsJson.data : []);
const pn = Array.isArray(projectsJson) ? projectsJson : (projectsJson && 'data' in projectsJson ? projectsJson.data : []);

setDonations(dList);
// donors API may return objects; if so map to organization names
const dnArray: unknown[] = dn as unknown[];
const donorNamesMapped = dnArray
.map((d) => {
if (typeof d === 'string') return d;
if (d && typeof d === 'object' && 'organization' in d) {
const maybeOrg = (d as { [key: string]: unknown })['organization'];
if (typeof maybeOrg === 'string') return maybeOrg;
}
return '';
})
.filter((s): s is string => Boolean(s));

const pnArray: unknown[] = pn as unknown[];
const projectNamesMapped = pnArray
.map((p) => {
if (typeof p === 'string') return p;
if (p && typeof p === 'object' && 'name' in p) {
const maybeName = (p as { [key: string]: unknown })['name'];
if (typeof maybeName === 'string') return maybeName;
}
return '';
})
.filter((s): s is string => Boolean(s));

setDonorNames(donorNamesMapped);
setProjectNames(projectNamesMapped);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load donations data');
setDonations([]);
setDonorNames([]);
setProjectNames([]);
} finally {
setLoading(false);
}
}
loadAll();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const totalPages = Math.max(1, Math.ceil(donations.length / rowsPerPage));
const currentDonations = donations.slice(
(currentPage - 1) * rowsPerPage,
currentPage * rowsPerPage
);
Expand Down Expand Up @@ -104,7 +154,7 @@ export default function DonationsPage() {
{showFilter && (
<div style={{ position: 'absolute', top: '100%', left: 0, zIndex: 10 }}>
<DropdownSelector
options={mockDonors}
options={donorNames}
placeholder="Filter by donor..."
multiSelect={true}
value={selectedDonor}
Expand All @@ -126,13 +176,13 @@ export default function DonationsPage() {
</Button>
{showSort && (
<div style={{ position: 'absolute', top: '100%', left: 0, zIndex: 10 }}>
<DropdownSelector
options={sortOptions}
placeholder="Sort by..."
multiSelect={false}
value={selectedSort}
onChange={(val: string | string[]) => setSelectedSort(val as string)}
/>
<DropdownSelector
options={sortOptions}
placeholder="Sort by..."
multiSelect={false}
value={selectedSort}
onChange={(val: string | string[]) => setSelectedSort(val as string)}
/>
</div>
)}
</div>
Expand Down Expand Up @@ -174,15 +224,15 @@ export default function DonationsPage() {
{dateError && <span style={{ color: 'red', fontSize: '12px' }}>Enter a valid date</span>}
</div>
<DropdownSelector
options={mockDonors}
options={donorNames}
placeholder="Select a donor"
multiSelect={false}
value={newDonor}
onChange={(val: string | string[]) => { setNewDonor(val as string); setDonorError(false); }}
/>
{donorError && <span style={{ color: 'red', fontSize: '12px' }}>Select a donor</span>}
<DropdownSelector
options={mockProjects}
options={projectNames}
placeholder="Select a project"
multiSelect={false}
value={newProject}
Expand All @@ -208,6 +258,9 @@ export default function DonationsPage() {
</Portal>
</Dialog.Root>

{loading && <p>Loading donations...</p>}
{error && <p style={{ color: 'var(--color-error-red)' }}>{error}</p>}
{!loading && !error && (
<Table.Root>
<Table.ColumnGroup>
<Table.Column width="15%" />
Expand All @@ -234,6 +287,7 @@ export default function DonationsPage() {
))}
</Table.Body>
</Table.Root>
)}

<div style={{ marginTop: 'auto' }}>
<HStack width="100%" justify="center" paddingTop="3%" paddingBottom="3%" gap="6">
Expand Down
50 changes: 34 additions & 16 deletions apps/frontend/src/app/donors/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import NavBar from "../components/Navbar";
import { HStack, Input, Button, Table, Dialog, Portal, CloseButton, Stack } from "@chakra-ui/react";
import TextInputField from '../components/TextInputField';
Expand All @@ -16,26 +16,40 @@ type Donor = {
num_projects: number;
last_donation: string | null;
};
import { useApi } from '@/hooks/useApi';

// fetched from API
const apiBase = 'http://localhost:3003';

const mockDonors: Donor[] = [
{ donor_id: 1, organization: 'Green Future Foundation', contact_name: 'Alice Chen', contact_email: 'alice@greenfuture.org', num_projects: 4, last_donation: '03/12/2024' },
{ donor_id: 2, organization: 'Horizon Trust', contact_name: 'James Patel', contact_email: 'james@horizontrust.org', num_projects: 2, last_donation: '01/05/2024' },
{ donor_id: 3, organization: 'Bright Path Nonprofit', contact_name: null, contact_email: null, num_projects: 7, last_donation: '02/28/2024' },
{ donor_id: 4, organization: 'Unity Giving Circle', contact_name: 'Maria Lopez', contact_email: 'maria@unitygiving.org', num_projects: 1, last_donation: '03/30/2024' },
{ donor_id: 5, organization: 'Sunrise Community Fund', contact_name: 'David Kim', contact_email: 'david@sunrisefund.org', num_projects: 3, last_donation: '04/01/2024' },
{ donor_id: 6, organization: 'Blue Ridge Giving', contact_name: 'Sarah Thompson', contact_email: 'sarah@blueridge.org', num_projects: 5, last_donation: '02/14/2024' },
{ donor_id: 7, organization: 'Maple Leaf Charitable Trust', contact_name: null, contact_email: null, num_projects: 2, last_donation: '01/20/2024' },
{ donor_id: 8, organization: 'Evergreen Partners', contact_name: 'Rachel Singh', contact_email: 'rachel@evergreenpartners.org', num_projects: 6, last_donation: '03/05/2024' },
{ donor_id: 9, organization: 'New Horizons Society', contact_name: 'Tom Bradley', contact_email: 'tom@newhorizons.org', num_projects: 9, last_donation: '04/10/2024' },
{ donor_id: 10, organization: 'Coastal Care Foundation', contact_name: 'Nina Rossi', contact_email: 'nina@coastalcare.org', num_projects: 3, last_donation: '03/22/2024' },
];

export default function DonorsPage() {
const [currentPage, setCurrentPage] = useState(1);
const rowsPerPage = 10;

const totalPages = Math.ceil(mockDonors.length / rowsPerPage);
const currentDonors = mockDonors.slice(
const api = useApi();
const [donors, setDonors] = useState<Donor[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
async function fetchDonors() {
try {
const json = await api.get<Donor[] | { data: Donor[] }>(`${apiBase}/donors`);
const list = Array.isArray(json) ? json : (json && 'data' in json ? json.data : []);
setDonors(list);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load donors');
setDonors([]);
} finally {
setLoading(false);
}
}
fetchDonors();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const totalPages = Math.max(1, Math.ceil(donors.length / rowsPerPage));
const currentDonors = donors.slice(
(currentPage - 1) * rowsPerPage,
currentPage * rowsPerPage
);
Expand All @@ -49,7 +63,7 @@ export default function DonorsPage() {

const [showFilter, setShowFilter] = useState(false);
const [selectedDonor, setSelectedDonor] = useState<string>('');
const donorNames = mockDonors.map(d => d.organization);
const donorNames = donors.map(d => d.organization);

const [showSort, setShowSort] = useState(false);
const [selectedSort, setSelectedSort] = useState<string>('');
Expand Down Expand Up @@ -186,6 +200,9 @@ export default function DonorsPage() {
</Portal>
</Dialog.Root>

{loading && <p>Loading donors...</p>}
{error && <p style={{ color: 'var(--color-error-red)' }}>{error}</p>}
{!loading && !error && (
<Table.Root>
<Table.ColumnGroup>
<Table.Column width="15%" />
Expand All @@ -212,6 +229,7 @@ export default function DonorsPage() {
))}
</Table.Body>
</Table.Root>
)}

<div style={{ marginTop: 'auto' }}>
<HStack width="100%" justify="center" paddingTop="3%" paddingBottom="3%" gap="6">
Expand Down
Loading
Loading