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
23 changes: 23 additions & 0 deletions app/mayor/[year]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Metadata } from "next";
import { YearlyMayor } from "../../../components/Mayor/YearlyMayor";
import { Container } from "react-bootstrap";
import NavBar from "../../../components/NavBar/NavBar";
import Search from "../../../components/Search/Search";

export const metadata: Metadata = {
title: "Mayor Flips",
description: "A list of items that are affected by the current or upcoming mayor.",
};
Comment on lines +7 to +10

export default async function MayorPage({ params }: { params: { year: string } }) {
const year = params.year;
return (
<Container>
<NavBar />
<Search />
<h1>Mayor Data for Year {year}</h1>
<hr />
<YearlyMayor year={year} />
</Container>
);
}
22 changes: 22 additions & 0 deletions app/mayor/all/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Metadata } from "next";
import { AllMayors } from "../../../components/Mayor/AllMayors";
import { Container } from "react-bootstrap";
import NavBar from "../../../components/NavBar/NavBar";
import Search from "../../../components/Search/Search";

export const metadata: Metadata = {
title: "All Mayor Flips",
description: "A list of all mayor flips from the last 5 years.",
};
Comment on lines +7 to +10

export default function AllMayorPage() {
return (
<Container>
<NavBar />
<Search />
<h1>All Mayors</h1>
<hr />
<AllMayors />
</Container>
);
}
33 changes: 33 additions & 0 deletions components/Mayor/AllMayors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";
import { useMemo } from "react";
import { useGetApiMayor } from "../../api/_generated/skyApi";
import { MayorDetailsDisplay } from "./MayorDetailsDisplay";

const FIVE_YEARS_IN_MS = 5 * 365 * 24 * 60 * 60 * 1000;
const NOW = new Date()
const FIVE_YEARS_AGO = new Date(Date.now() - FIVE_YEARS_IN_MS);
Comment on lines +6 to +8
NOW.setHours(0, 0, 0, 0);
FIVE_YEARS_AGO.setHours(0, 0, 0, 0);

export function AllMayors() {
const { from, to } = useMemo(() => {
return {
from: FIVE_YEARS_AGO.toISOString(),
to: NOW.toISOString()
};
}, []);

const { data: mayorData, isLoading, error } = useGetApiMayor({ from, to });

if (isLoading) {
return <div>Loading...</div>;
}

if (error) {
return <div>Error loading mayor data</div>;
}

const elections = mayorData?.data ? (Array.isArray(mayorData.data) ? mayorData.data : [mayorData.data]) : [];

return <MayorDetailsDisplay elections={elections} />;
}
105 changes: 105 additions & 0 deletions components/Mayor/MayorDetails.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
.container {
padding: 2rem;
max-width: 1200px;
margin: 2rem auto;
background-color: #1a1a1a;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
color: #f0f0f0;
}

.container h1 {
text-align: center;
color: #00aaff;
margin-bottom: 1rem;
}

.container h2 {
text-align: center;
color: #00aaff;
margin-bottom: 2rem;
}

.table {
width: 100%;
border-collapse: collapse;
margin-top: 2rem;
}

.table th, .table td {
border: 1px solid #333;
padding: 12px 15px;
text-align: left;
}

.table th {
background-color: #003366;
color: #ffffff;
font-weight: bold;
text-transform: uppercase;
}

.table tr:nth-child(even) {
background-color: #2a2a2a;
}

.table tr:hover {
background-color: #3a3a3a;
}

.yearLinks {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}

.yearLinks a {
text-decoration: none;
color: #f0f0f0;
background-color: #0055a4;
padding: 0.5rem 1rem;
border-radius: 5px;
transition: background-color 0.3s ease;
}

.yearLinks a:hover {
background-color: #0077cc;
text-decoration: none;
}

.customAccordion {
--bs-accordion-bg: #1a1a1a;
--bs-accordion-color: #f0f0f0;
--bs-accordion-border-color: #333;
--bs-accordion-active-bg: #222;
--bs-accordion-active-color: #00aaff;
--bs-accordion-btn-focus-box-shadow: none;
}

.customAccordion :global(.accordion-button) {
font-weight: 500;
}

.customAccordion :global(.accordion-button:not(.collapsed)::after) {
filter: invert(1);
}

@media (max-width: 768px) {
.container {
padding: 1rem;
}

.table th, .table td {
padding: 8px;
}

.yearLinks {
gap: 0.5rem;
}

.yearLinks a {
padding: 0.4rem 0.8rem;
}
}
107 changes: 107 additions & 0 deletions components/Mayor/MayorDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"use client";
import { useGetApiMayor, useGetApiMayorYear } from "../../api/_generated/skyApi";
import styles from "./MayorDetails.module.css";
import Link from "next/link";
import { Accordion, Card, Col, Container, Row } from "react-bootstrap";
import { CoflnetSkyMayorModelsModelElectionPeriod } from "../../api/_generated/skyApi.schemas";

Comment on lines +1 to +7
type Props = {
year?: string;
}

function MayorElectionPeriod({ election }: { election: CoflnetSkyMayorModelsModelElectionPeriod }) {
return (
<Card className="mb-3">
<Card.Header>
<Accordion.Header>
Election in year {election.year}
</Accordion.Header>
</Card.Header>
<Accordion.Body>
<Card.Body>
Comment on lines +14 to +21
<Row>
<Col md={6}>
<h5>Candidates</h5>
<ul>
{election.candidates?.map(candidate => (
<li key={candidate.key}>{candidate.name}</li>
Comment on lines +26 to +27
))}
</ul>
</Col>
<Col md={6}>
<h5>Winner</h5>
{election.winner ? (
<div>
<p>Name: {election.winner.name}</p>
<p>Key: {election.winner.key}</p>
<h6>Perks:</h6>
<ul>
{election.winner.perks?.map(perk => (
<li key={perk.name}>{perk.name}: {perk.description}</li>
Comment on lines +39 to +40
))}
</ul>
</div>
) : <p>No winner declared.</p>}
</Col>
</Row>
</Card.Body>
</Accordion.Body>
</Card>
)
}


export default function MayorDetails({ year }: Props) {
const { data: mayorData, isLoading: isMayorLoading, error: mayorError } = useGetApiMayor(undefined,
{
query: {
enabled: !year,
},
}
);

const { data: mayorYearData, isLoading: isMayorYearLoading, error: mayorYearError } = useGetApiMayorYear(
parseInt(year || new Date().getFullYear().toString(), 10),
{
query: {
enabled: !!year,
},
}
Comment on lines +63 to +69
);

const isLoading = isMayorLoading || isMayorYearLoading;
const error = mayorError || mayorYearError;
const data = year ? mayorYearData : mayorData;

if (isLoading) {
return <div>Loading...</div>;
}

if (error) {
return <div>Error loading mayor data</div>;
}

const elections = data?.data ? (Array.isArray(data.data) ? data.data : [data.data]) : [];
const years = Array.from(new Set(elections.map(e => e.year)));

return (
<Container className={styles.container}>
<h1>Mayor Details</h1>
{year && <h2>Year: {year}</h2>}

<div className={styles.yearLinks}>
{years.map(y => (
<Link key={y} href={`/mayor/${y}`}>
{y}
</Link>
))}
</div>

<Accordion defaultActiveKey="0">
{elections.map((election, index) => (
<MayorElectionPeriod key={index} election={election} />
))}
</Accordion>
</Container>
);
}
Loading
Loading