compact navigation: prayer view dropdown, menaion calendar with prev/next
This commit is contained in:
1 parent
88340ecc25
commit
0ff2ef08fb
2 files changed
+70
-62
No files matched your search
+64
-43
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Menaion view — browse the daily Menaion by month and day.
|
* Menaion view — browse the daily Menaion by month and day.
|
||||||
* Two-step selector: month → day grid → section list.
|
* Calendar-style navigation with prev/next month arrows.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assembleService, type ServiceContext } from '../core/service-assembler';
|
import { assembleService, type ServiceContext } from '../core/service-assembler';
|
||||||
@@ -13,6 +13,7 @@ export class MenaionView {
|
|||||||
private language: LanguageCode;
|
private language: LanguageCode;
|
||||||
private t: ReturnType<typeof getTranslations>;
|
private t: ReturnType<typeof getTranslations>;
|
||||||
private index: Record<string, number> = {};
|
private index: Record<string, number> = {};
|
||||||
|
private currentMonth: number = 1;
|
||||||
|
|
||||||
constructor(container: HTMLElement, language: LanguageCode) {
|
constructor(container: HTMLElement, language: LanguageCode) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
@@ -26,7 +27,20 @@ export class MenaionView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getMonthNames(): string[] {
|
private getMonthNames(): string[] {
|
||||||
return this.t.calendar.monthsGenitive;
|
const months = this.t.calendar.monthsGenitive;
|
||||||
|
// Some i18n month lists are genitive (used in date contexts).
|
||||||
|
// For labels we need nominative — derive from genitive or use indices.
|
||||||
|
const NOMINATIVE: Record<string, string> = {
|
||||||
|
'января': 'Январь', 'февраля': 'Февраль', 'марта': 'Март',
|
||||||
|
'апреля': 'Апрель', 'мая': 'Май', 'июня': 'Июнь',
|
||||||
|
'июля': 'Июль', 'августа': 'Август', 'сентября': 'Сентябрь',
|
||||||
|
'октября': 'Октябрь', 'ноября': 'Ноябрь', 'декабря': 'Декабрь',
|
||||||
|
'january': 'January', 'february': 'February', 'march': 'March',
|
||||||
|
'april': 'April', 'may': 'May', 'june': 'June',
|
||||||
|
'july': 'July', 'august': 'August', 'september': 'September',
|
||||||
|
'october': 'October', 'november': 'November', 'december': 'December',
|
||||||
|
};
|
||||||
|
return months.map(m => NOMINATIVE[m.toLowerCase()] || m);
|
||||||
}
|
}
|
||||||
|
|
||||||
async render() {
|
async render() {
|
||||||
@@ -37,68 +51,75 @@ export class MenaionView {
|
|||||||
}
|
}
|
||||||
} catch { /* empty */ }
|
} catch { /* empty */ }
|
||||||
|
|
||||||
const months = this.getMonthNames();
|
|
||||||
const daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
this.currentMonth = today.getMonth() + 1;
|
||||||
// Count sections per month
|
|
||||||
const monthCounts: number[] = new Array(12).fill(0);
|
|
||||||
for (const [dk, n] of Object.entries(this.index)) {
|
|
||||||
const m = parseInt(dk.slice(0, 2), 10) - 1;
|
|
||||||
if (m >= 0 && m < 12) monthCounts[m] += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
const monthHtml = months.map((m, i) => `
|
|
||||||
<button class="menaion-month px-3 py-1.5 text-sm rounded border transition-colors
|
|
||||||
border-gold/20 bg-white/50 text-navy hover:border-gold/50" data-month="${i + 1}">
|
|
||||||
${m} <span class="text-xs text-navy-light">(${monthCounts[i]})</span>
|
|
||||||
</button>
|
|
||||||
`).join('');
|
|
||||||
|
|
||||||
this.container.innerHTML = `
|
this.container.innerHTML = `
|
||||||
<div class="p-6 max-w-4xl xl:max-w-6xl mx-auto">
|
<div class="p-6 max-w-4xl xl:max-w-6xl mx-auto">
|
||||||
<h2 class="text-2xl font-bold text-red mb-2">${this.t.menaion.title}</h2>
|
<h2 class="text-2xl font-bold text-red mb-2">${this.t.menaion.title}</h2>
|
||||||
<p class="text-navy-light mb-4">${months[0]}</p>
|
<div id="menaion-calendar"></div>
|
||||||
<div class="flex flex-wrap gap-1 mb-6">
|
|
||||||
${monthHtml}
|
|
||||||
</div>
|
|
||||||
<div id="menaion-days" class="grid gap-1 sm:grid-cols-7 mb-6"></div>
|
|
||||||
<div id="menaion-content" class="bg-white/50 border border-gold/20 rounded-lg p-6 min-h-[300px]">
|
<div id="menaion-content" class="bg-white/50 border border-gold/20 rounded-lg p-6 min-h-[300px]">
|
||||||
<p class="text-navy-light italic">${this.t.prayer.selectSection}</p>
|
<p class="text-navy-light italic">${this.t.prayer.selectSection}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this.container.querySelectorAll('.menaion-month').forEach(btn => {
|
this.renderCalendar(this.currentMonth);
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
const month = parseInt(btn.getAttribute('data-month')!, 10);
|
|
||||||
this.renderMonth(month, daysInMonth[month - 1]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Default: current month
|
|
||||||
this.renderMonth(today.getMonth() + 1, daysInMonth[today.getMonth()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderMonth(month: number, maxDay: number) {
|
private renderCalendar(month: number) {
|
||||||
const daysEl = this.container.querySelector('#menaion-days');
|
const calEl = this.container.querySelector('#menaion-calendar');
|
||||||
if (!daysEl) return;
|
if (!calEl) return;
|
||||||
|
|
||||||
|
const months = this.getMonthNames();
|
||||||
|
const daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||||
|
const maxDay = daysInMonth[month - 1];
|
||||||
const mm = String(month).padStart(2, '0');
|
const mm = String(month).padStart(2, '0');
|
||||||
const days = [];
|
|
||||||
|
// First day of month (Julian) — approximate day-of-week
|
||||||
|
// Use a simple approach: Jan 1 Julian 2025 = Wednesday (3)
|
||||||
|
const firstDow = (month === 1 ? 3 : new Date(2025, month - 1, 1).getDay() + 1) % 7;
|
||||||
|
|
||||||
|
let daysHtml = '';
|
||||||
|
for (let i = 0; i < firstDow; i++) {
|
||||||
|
daysHtml += '<div></div>';
|
||||||
|
}
|
||||||
for (let d = 1; d <= maxDay; d++) {
|
for (let d = 1; d <= maxDay; d++) {
|
||||||
const dd = String(d).padStart(2, '0');
|
const dd = String(d).padStart(2, '0');
|
||||||
const key = `${mm}-${dd}`;
|
const key = `${mm}-${dd}`;
|
||||||
const n = this.index[key];
|
const n = this.index[key];
|
||||||
days.push(`
|
daysHtml += `
|
||||||
<button class="menaion-day px-2 py-1 text-xs rounded border transition-colors
|
<button class="px-1.5 py-0.5 text-xs rounded border transition-colors
|
||||||
${n ? 'border-gold/40 bg-gold/10 text-navy hover:border-gold' : 'border-gold/10 text-navy-light opacity-40 cursor-default'}"
|
${n ? 'border-gold/40 bg-gold/10 text-navy hover:border-gold cursor-pointer' : 'border-transparent text-navy-light opacity-30 cursor-default'}"
|
||||||
data-date="${key}" ${n ? '' : 'disabled'}>
|
data-date="${key}" ${n ? '' : 'disabled'}>
|
||||||
${d}${n ? ` <span class="text-[10px]">(${n})</span>` : ''}
|
${d}
|
||||||
</button>
|
</button>
|
||||||
`);
|
`;
|
||||||
}
|
}
|
||||||
daysEl.innerHTML = days.join('');
|
|
||||||
this.container.querySelectorAll('.menaion-day:not([disabled])').forEach(btn => {
|
calEl.innerHTML = `
|
||||||
|
<div class="flex items-center justify-between mb-3">
|
||||||
|
<button id="menaion-prev" class="text-sm text-navy-light hover:text-navy px-2 py-1">◀</button>
|
||||||
|
<span class="text-base font-bold text-navy">${months[month - 1]}</span>
|
||||||
|
<button id="menaion-next" class="text-sm text-navy-light hover:text-navy px-2 py-1">▶</button>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-1 grid-cols-7 mb-4">
|
||||||
|
${daysHtml}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('menaion-prev')?.addEventListener('click', () => {
|
||||||
|
const prev = this.currentMonth <= 1 ? 12 : this.currentMonth - 1;
|
||||||
|
this.currentMonth = prev;
|
||||||
|
this.renderCalendar(prev);
|
||||||
|
});
|
||||||
|
document.getElementById('menaion-next')?.addEventListener('click', () => {
|
||||||
|
const next = this.currentMonth >= 12 ? 1 : this.currentMonth + 1;
|
||||||
|
this.currentMonth = next;
|
||||||
|
this.renderCalendar(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
calEl.querySelectorAll('[data-date]:not([disabled])').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
this.loadDay(btn.getAttribute('data-date')!);
|
this.loadDay(btn.getAttribute('data-date')!);
|
||||||
});
|
});
|
||||||
|
|||||||
+6
-19
@@ -217,36 +217,23 @@ export class PrayerView {
|
|||||||
this.container.innerHTML = `
|
this.container.innerHTML = `
|
||||||
<div class="p-6 max-w-4xl xl:max-w-6xl mx-auto">
|
<div class="p-6 max-w-4xl xl:max-w-6xl mx-auto">
|
||||||
<h2 class="text-2xl font-bold text-red mb-2">${title}</h2>
|
<h2 class="text-2xl font-bold text-red mb-2">${title}</h2>
|
||||||
<p class="text-navy-light mb-6">${subtitle}</p>
|
<p class="text-navy-light mb-4">${subtitle}</p>
|
||||||
<div class="grid gap-2 sm:grid-cols-2 mb-6">
|
<select id="prayer-select" class="w-full border border-gold/20 bg-white/50 text-navy rounded-lg px-3 py-2 text-sm mb-6">
|
||||||
${sections.map(s => `
|
${sections.map(s => `
|
||||||
<button
|
<option value="${s.id}" ${this.activeSection === s.id ? 'selected' : ''}>${s.name}</option>
|
||||||
class="prayer-section text-left px-4 py-3 rounded-lg border transition-colors
|
|
||||||
${this.activeSection === s.id ? 'border-gold bg-gold/10 text-navy font-bold' : 'border-gold/20 bg-white/50 text-navy hover:border-gold/50'}"
|
|
||||||
data-section="${s.id}"
|
|
||||||
>
|
|
||||||
<div class="text-sm font-bold">${s.name}</div>
|
|
||||||
${s.description ? `<div class="text-xs text-navy-light">${s.description}</div>` : ''}
|
|
||||||
</button>
|
|
||||||
`).join('')}
|
`).join('')}
|
||||||
</div>
|
</select>
|
||||||
<div id="prayer-content" class="bg-white/50 border border-gold/20 rounded-lg p-6 min-h-[300px]">
|
<div id="prayer-content" class="bg-white/50 border border-gold/20 rounded-lg p-6 min-h-[300px]">
|
||||||
<p class="text-navy-light italic">${this.t.prayer.selectSection}</p>
|
<p class="text-navy-light italic">${this.t.prayer.selectSection}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this.container.querySelectorAll('.prayer-section').forEach(btn => {
|
document.getElementById('prayer-select')?.addEventListener('change', (e) => {
|
||||||
btn.addEventListener('click', () => {
|
const id = (e.target as HTMLSelectElement).value;
|
||||||
const id = btn.getAttribute('data-section')!;
|
|
||||||
this.activeSection = id;
|
this.activeSection = id;
|
||||||
this.container.querySelectorAll('.prayer-section').forEach(b => {
|
|
||||||
const bid = b.getAttribute('data-section');
|
|
||||||
b.className = `prayer-section text-left px-4 py-3 rounded-lg border transition-colors ${bid === id ? 'border-gold bg-gold/10 text-navy font-bold' : 'border-gold/20 bg-white/50 text-navy hover:border-gold/50'}`;
|
|
||||||
});
|
|
||||||
this.loadSection(id);
|
this.loadSection(id);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadSection(id: string) {
|
private async loadSection(id: string) {
|
||||||
|
|||||||
Reference in new issue
Block a user