additional files

This commit is contained in:
kfj001 committed 2026-06-19 17:37:19 +00:00
1 parent 266c2451f1
commit 2d2b2d040b
25 files changed
+709 -239

No files matched your search

+28 -65
View File
@@ -2,79 +2,42 @@
<div class="container">
<div class="schedule-header">
<img src="svg/small-flower.svg" alt="" class="schedule-flower" aria-hidden="true">
<h2>Today's <span class="text-accent">Program Schedule</span></h2>
<h2>Program <span class="text-accent">Schedule</span></h2>
<p class="section-intro">
All times are local (Mountain Time). Every show is free to listen to
live at 98.7 FM or via our online stream.
</p>
</div>
@if (loading) {
<div class="schedule-loading">Loading schedule&hellip;</div>
} @else if (programs.length === 0) {
<div class="schedule-empty">No programs scheduled. Check back soon!</div>
} @else {
<div class="schedule-list">
<div class="schedule-day-label">Monday – Friday</div>
<div class="schedule-table">
<div *ngFor="let program of programs" class="schedule-row">
<div class="schedule-time">{{ program.time }}</div>
<div class="schedule-divider"></div>
<div class="schedule-details">
<h4 class="schedule-title">{{ program.title }}</h4>
<div class="schedule-meta">
<span class="schedule-host">
<span aria-hidden="true">&#x1F399;</span> {{ program.host }}
</span>
<span class="schedule-genre">{{ program.genre }}</span>
</div>
@if (schedule$ | async; as state) {
@if (state.loading) {
<div class="schedule-loading">Loading schedule&hellip;</div>
} @else if (state.dayGroups.length === 0) {
<div class="schedule-empty">No programs scheduled. Check back soon!</div>
} @else {
@for (group of state.dayGroups; track group.day_label) {
<div class="schedule-day-block">
<div class="schedule-day-label">{{ group.day_label }}</div>
<div class="schedule-table">
@for (program of group.programs; track program.id) {
<div class="schedule-row">
<div class="schedule-time">{{ program.time }}</div>
<div class="schedule-divider"></div>
<div class="schedule-details">
<h4 class="schedule-title">{{ program.title }}</h4>
<div class="schedule-meta">
<span class="schedule-host">
<span aria-hidden="true">&#x1F399;</span> {{ program.host }}
</span>
<span class="schedule-genre">{{ program.genre }}</span>
</div>
</div>
</div>
}
</div>
</div>
</div>
</div>
}
}
}
<div class="schedule-weekend">
<div class="weekend-heading">
<img src="svg/small-flower.svg" alt="" class="schedule-flower-sm" aria-hidden="true">
<h3>Weekend Programming</h3>
</div>
<div class="weekend-grid">
<div class="weekend-card">
<div class="card-day">Saturday</div>
<div class="card-programs">
<div class="card-item">
<strong>9:00 AM</strong> — Country Roads Hour
</div>
<div class="card-item">
<strong>11:00 AM</strong> — Community Hour (call-ins welcome!)
</div>
<div class="card-item">
<strong>2:00 PM</strong> — Afternoon Bluegrass
</div>
<div class="card-item">
<strong>4:00 PM</strong> — Record Request Hour
</div>
</div>
</div>
<div class="weekend-card">
<div class="card-day">Sunday</div>
<div class="card-programs">
<div class="card-item">
<strong>10:00 AM</strong> — Sunday Morning Hymns & Folk
</div>
<div class="card-item">
<strong>12:00 PM</strong> — Gospel & Roots
</div>
<div class="card-item">
<strong>3:00 PM</strong> — The Long Wave (ambient / nature sounds)
</div>
<div class="card-item">
<strong>6:00 PM</strong> — Sunday Sunset Jazz
</div>
</div>
</div>
</div>
</div>
</div>
</section>
+10 -69
View File
@@ -18,9 +18,17 @@
}
}
.schedule-list {
.schedule-loading,
.schedule-empty {
text-align: center;
padding: $spacing-3xl $spacing-md;
color: var(--color-medium);
font-size: 1.1rem;
}
.schedule-day-block {
max-width: 800px;
margin: 0 auto $spacing-3xl;
margin: 0 auto $spacing-xl;
}
.schedule-day-label {
@@ -101,73 +109,6 @@
}
}
// Weekend
.schedule-weekend {
margin-top: $spacing-xl;
}
.weekend-heading {
text-align: center;
margin-bottom: $spacing-lg;
.schedule-flower-sm {
width: 35px;
height: 35px;
margin: 0 auto $spacing-sm;
opacity: 0.5;
}
h3 {
font-size: 1.6rem;
color: var(--color-primary);
}
}
.weekend-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: $spacing-lg;
}
.weekend-card {
@include card-style;
padding: $spacing-xl;
background: var(--color-white);
.card-day {
font-family: var(--font-heading);
font-size: 1.4rem;
font-weight: 700;
color: var(--color-accent);
margin-bottom: $spacing-md;
padding-bottom: $spacing-sm;
border-bottom: 2px solid var(--color-light);
}
.card-programs {
display: flex;
flex-direction: column;
gap: $spacing-sm;
}
.card-item {
font-size: 0.95rem;
color: var(--color-dark);
padding: $spacing-sm;
border-radius: $radius-sm;
transition: background $transition-fast;
strong {
color: var(--color-primary);
}
&:hover {
background: var(--color-cream);
}
}
}
@media (max-width: #{$bp-md - 1px}) {
.schedule-time { width: 80px; font-size: 0.85rem; }
.weekend-grid { grid-template-columns: 1fr; }
}
+40 -19
View File
@@ -1,32 +1,53 @@
import { Component, inject, OnInit } from '@angular/core';
import { NgFor } from '@angular/common';
import { Component, inject } from '@angular/core';
import { AsyncPipe, NgFor } from '@angular/common';
import { map, Observable, startWith } from 'rxjs';
import { ProgramService } from '../services/program.service';
import { Program } from '../interfaces/program';
interface DayGroup {
day_label: string;
day_of_week: number;
programs: Program[];
}
interface ScheduleState {
loading: boolean;
dayGroups: DayGroup[];
}
function groupByDay(programs: Program[]): DayGroup[] {
const map = new Map<string, DayGroup>();
for (const p of programs) {
if (!map.has(p.day_label)) {
map.set(p.day_label, {
day_label: p.day_label,
day_of_week: p.day_of_week,
programs: [],
});
}
map.get(p.day_label)!.programs.push(p);
}
return Array.from(map.values()).sort((a, b) => a.day_of_week - b.day_of_week);
}
@Component({
selector: 'app-schedule',
standalone: true,
imports: [NgFor],
imports: [AsyncPipe, NgFor],
templateUrl: './schedule.component.html',
styleUrl: './schedule.component.scss',
})
export class ScheduleComponent implements OnInit {
export class ScheduleComponent {
private programService = inject(ProgramService);
programs: Program[] = [];
loading = true;
ngOnInit(): void {
this.programService.getPrograms().subscribe({
next: (data) => {
this.programs = data;
this.loading = false;
},
error: () => {
this.loading = false;
this.programs = [];
},
});
}
/** Async pipe drives the template — guarantees change detection on every emission. */
readonly schedule$: Observable<ScheduleState> =
this.programService.getPrograms().pipe(
map((programs) => groupByDay(programs)),
map((dayGroups) => ({ loading: false, dayGroups })),
startWith({ loading: true, dayGroups: [] }),
);
}