Moved duplicate folder
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
// pages/competition-page.ts
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import type { Tab } from "../../components/ui-tab-bar.js";
|
||||
import "../../components/ui-tab-bar.js";
|
||||
import "../../components/ui-badge.js";
|
||||
import { Icons } from "../../components/icons.js";
|
||||
|
||||
const TAB_LOADERS: Record<string, () => Promise<unknown>> = {
|
||||
details: () => import("./tabs/competition-details.js"),
|
||||
results: () => import("./tabs/competition-results.js"),
|
||||
tasks: () => import("./tabs/competition-tasks.js"),
|
||||
noticeboard: () => import("./tabs/competition-noticeboard.js"),
|
||||
pilots: () => import("./tabs/competition-pilots.js"),
|
||||
officials: () => import("./tabs/competition-officials.js"),
|
||||
};
|
||||
|
||||
@customElement("competition-page")
|
||||
export class CompetitionPage extends LitElement {
|
||||
@state() private activeTab = "details";
|
||||
@state() private loadedTabs = new Set<string>();
|
||||
@state() private loadingTab = "";
|
||||
|
||||
private tabs: Tab[] = [
|
||||
{ id: "details", label: "Event Details", icon: "calendar" },
|
||||
{ id: "results", label: "Results", icon: "trophy" },
|
||||
{ id: "tasks", label: "Task Data", icon: "target" },
|
||||
{ id: "noticeboard", label: "Noticeboard", icon: "clipboard" },
|
||||
{ id: "pilots", label: "Pilots", icon: "users" },
|
||||
{ id: "officials", label: "Officials", icon: "user" },
|
||||
];
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
sans-serif;
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%);
|
||||
color: white;
|
||||
padding: 3rem 1.5rem 2rem;
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.8;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumb a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.breadcrumb span {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.hero-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.hero-meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.hero-meta-item svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4rem;
|
||||
color: color-mix(in srgb, var(--color-text) 45%, transparent);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border: 2px solid var(--color-border);
|
||||
border-top-color: var(--color-accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.loadTab(this.activeTab);
|
||||
}
|
||||
|
||||
private async loadTab(id: string) {
|
||||
if (this.loadedTabs.has(id)) return;
|
||||
|
||||
const loader = TAB_LOADERS[id];
|
||||
if (!loader) return;
|
||||
|
||||
this.loadingTab = id;
|
||||
try {
|
||||
await loader();
|
||||
this.loadedTabs = new Set([...this.loadedTabs, id]);
|
||||
} catch (e) {
|
||||
console.error(`Failed to load tab "${id}":`, e);
|
||||
} finally {
|
||||
this.loadingTab = "";
|
||||
}
|
||||
}
|
||||
|
||||
private handleTabChange(e: CustomEvent<{ tab: string }>) {
|
||||
this.activeTab = e.detail.tab;
|
||||
this.loadTab(e.detail.tab);
|
||||
}
|
||||
|
||||
private renderTabContent() {
|
||||
if (this.loadingTab === this.activeTab) {
|
||||
return html`
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
Loading…
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
switch (this.activeTab) {
|
||||
case "details":
|
||||
return html`<competition-details></competition-details>`;
|
||||
case "results":
|
||||
return html`<competition-results></competition-results>`;
|
||||
case "tasks":
|
||||
return html`<competition-tasks></competition-tasks>`;
|
||||
case "noticeboard":
|
||||
return html`<competition-noticeboard></competition-noticeboard>`;
|
||||
case "pilots":
|
||||
return html`<competition-pilots></competition-pilots>`;
|
||||
case "officials":
|
||||
return html`<competition-officials></competition-officials>`;
|
||||
default:
|
||||
return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="hero">
|
||||
<div class="hero-inner">
|
||||
<div class="breadcrumb">
|
||||
<a href="/">Home</a>
|
||||
<span>/</span>
|
||||
<a href="/competitions">Competitions</a>
|
||||
<span>/</span>
|
||||
<span>EVENT_NAME</span>
|
||||
</div>
|
||||
|
||||
<h1>EVENT_NAME</h1>
|
||||
|
||||
<div class="hero-meta">
|
||||
<div class="hero-meta-item">
|
||||
${Icons.icons.map_pin} COMPETITION_LOCATION
|
||||
</div>
|
||||
<div class="hero-meta-item">
|
||||
${Icons.icons.calendar} DATE_FROM_UNTIL_SHORT
|
||||
</div>
|
||||
<ui-badge variant="white">EVENT_TYPE</ui-badge>
|
||||
<ui-badge variant="success">EVENT_STATUS</ui-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ui-tab-bar
|
||||
.tabs=${this.tabs}
|
||||
.active=${this.activeTab}
|
||||
@tab-change=${this.handleTabChange}
|
||||
></ui-tab-bar>
|
||||
|
||||
<div class="tab-content">${this.renderTabContent()}</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// tabs/competition-details.ts
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { Icons } from "../../../components/icons";
|
||||
|
||||
@customElement("competition-details")
|
||||
export class CompetitionDetails extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 380px;
|
||||
gap: 1.5rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.main-card {
|
||||
background: var(--color-bg-nav);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-date {
|
||||
padding: 1rem 1.5rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
color: color-mix(in srgb, var(--color-text) 65%, transparent);
|
||||
}
|
||||
|
||||
.card-body a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card-body a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.sidebar-card {
|
||||
background: var(--color-bg-nav);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar-card-header {
|
||||
padding: 1rem 1.5rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.sidebar-card-header svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.sidebar-card-body {
|
||||
padding: 1.25rem 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
color: color-mix(in srgb, var(--color-text) 65%, transparent);
|
||||
}
|
||||
|
||||
.detail-row svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--color-accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-row a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.detail-row a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="layout">
|
||||
<div>
|
||||
<div class="main-card">
|
||||
<div class="card-date">DATE_FROM – DATE_UNTIL</div>
|
||||
<div class="card-body">
|
||||
COMPETITION_DETAILS
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-card">
|
||||
<div class="sidebar-card-header">
|
||||
${Icons.icons.tools}
|
||||
Competition Details
|
||||
</div>
|
||||
<div class="sidebar-card-body">
|
||||
<div class="detail-row">
|
||||
${Icons.icons.user}
|
||||
Director: DIRECTOR_NAME
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
${Icons.icons.globe}
|
||||
EVENT_TYPE [National, Continental] (CAT 1 | CAT 2)
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
${Icons.icons.desktop}
|
||||
LOGGER_TYPE [Combined Logger, Marker Event]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-card">
|
||||
<div class="sidebar-card-header">
|
||||
${Icons.icons.mail}
|
||||
Contact Details
|
||||
</div>
|
||||
<div class="sidebar-card-body">
|
||||
<div class="detail-row">
|
||||
${Icons.icons.user}
|
||||
ORGANISATOR_NAME
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
${Icons.icons.home}
|
||||
<a href="#" target="_blank" rel="noopener">COMPETITION_WEBSITE</a>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
${Icons.icons.mail}
|
||||
<a href="mailto:-">CONTACT_EMAIL</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default CompetitionDetails;
|
||||
@@ -0,0 +1,42 @@
|
||||
// tabs/competition-noticeboard.ts
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("competition-noticeboard")
|
||||
export class CompetitionNoticeboard extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
background: var(--color-bg-nav);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
color: color-mix(in srgb, var(--color-text) 55%, transparent);
|
||||
}
|
||||
|
||||
.placeholder p:first-child {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.placeholder p:last-child {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="placeholder">
|
||||
<p>Noticeboard</p>
|
||||
<p>Content will appear here once the competition is underway.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default CompetitionNoticeboard;
|
||||
@@ -0,0 +1,41 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("competition-officials")
|
||||
export class CompetitionOfficials extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
background: var(--color-bg-nav);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
color: color-mix(in srgb, var(--color-text) 55%, transparent);
|
||||
}
|
||||
|
||||
.placeholder p:first-child {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.placeholder p:last-child {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="placeholder">
|
||||
<p>Officials</p>
|
||||
<p>Content will appear here once the competition is underway.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default CompetitionOfficials;
|
||||
@@ -0,0 +1,42 @@
|
||||
// tabs/competition-pilots.ts
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("competition-pilots")
|
||||
export class CompetitionPilots extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
background: var(--color-bg-nav);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
color: color-mix(in srgb, var(--color-text) 55%, transparent);
|
||||
}
|
||||
|
||||
.placeholder p:first-child {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.placeholder p:last-child {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="placeholder">
|
||||
<p>Pilots</p>
|
||||
<p>Content will appear here once the competition is underway.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default CompetitionPilots;
|
||||
@@ -0,0 +1,42 @@
|
||||
// tabs/competition-results.ts
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("competition-results")
|
||||
export class CompetitionResults extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
background: var(--color-bg-nav);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
color: color-mix(in srgb, var(--color-text) 55%, transparent);
|
||||
}
|
||||
|
||||
.placeholder p:first-child {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.placeholder p:last-child {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="placeholder">
|
||||
<p>Results</p>
|
||||
<p>Content will appear here once the competition is underway.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default CompetitionResults;
|
||||
@@ -0,0 +1,42 @@
|
||||
// tabs/competition-tasks.ts
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("competition-tasks")
|
||||
export class CompetitionTasks extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
background: var(--color-bg-nav);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
color: color-mix(in srgb, var(--color-text) 55%, transparent);
|
||||
}
|
||||
|
||||
.placeholder p:first-child {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.placeholder p:last-child {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="placeholder">
|
||||
<p>Task Data</p>
|
||||
<p>Content will appear here once the competition is underway.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default CompetitionTasks;
|
||||
Reference in New Issue
Block a user