| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- import { ChangeDetectorRef, Component } from '@angular/core';
- import { Router, RouterLink } from '@angular/router';
- import { KakochoService } from '../../services/kakocho-service';
- import { DankaService } from '../../services/dankaService';
- import { Danka } from '../../models/danka';
- import { AppHeader } from '../../share/header/app-header';
- import { AppSideMenu } from '../../share/side-menu/app-side-menu';
- import { FormsModule } from '@angular/forms';
-
- interface UpcomingMemorial {
- id: string;
- dankaId: string;
- date: Date;
- dateLabel: string;
- title: string;
- subText: string;
- type: '年忌法要' | '命日';
- status: '準備確認' | '要確認';
- }
-
- interface RecentDanka {
- danka: Danka;
- nextMemorialLabel: string;
- updatedAtLabel: string;
- }
-
- @Component({
- selector: 'app-dashboard',
- imports: [AppHeader, AppSideMenu, RouterLink, FormsModule],
- templateUrl: './dashboard.html',
- styleUrl: './dashboard.scss',
- })
- export class Dashboard {
- searchKeyword = '';
- todayLabel = this.formatTodayLabel(new Date());
- weeklyMemorialCount = 0;
- todayMemorialCount = 0;
- upcomingWeeklyMemorialCount = 0;
- monthlyMemorialCount = 0;
- recentDankaList: RecentDanka[] = [];
- upcomingMemorials: UpcomingMemorial[] = [];
-
- private readonly targetYear = new Date().getFullYear();
-
- constructor(
- private kakochoService: KakochoService,
- private dankaService: DankaService,
- private router: Router,
- private cdr: ChangeDetectorRef,
- ) {
- this.setWeeklyMemorialSummary();
- this.setMonthlyMemorialSummary();
- this.setRecentDankaList();
- this.setUpcomingMemorials();
- }
-
- searchAll(): void {
- const keyword = this.searchKeyword.trim();
-
- this.router.navigate(['/search'], {
- queryParams: keyword ? { keyword } : undefined,
- });
- }
-
- private async setRecentDankaList(): Promise<void> {
- const dankaList = await this.dankaService.getRecentDankaList(5);
-
- this.recentDankaList = await Promise.all(
- dankaList.map(async (danka) => ({
- danka,
- nextMemorialLabel: await this.getNextMemorialLabel(danka.id),
- updatedAtLabel: this.formatUpdatedAt(danka.updatedAt),
- })),
- );
- this.cdr.detectChanges();
- }
-
- private async setWeeklyMemorialSummary(): Promise<void> {
- const today = this.toDateOnly(new Date());
- const weekEnd = this.addDays(this.getWeekStart(today), 6);
-
- const weeklyMemorials = (await this.kakochoService.getKakochoList()).filter((kakocho) => {
- const deathDate = this.parseDate(kakocho.deathDate);
- if (!deathDate) {
- return false;
- }
-
- if (!this.isMemorialTarget(deathDate)) {
- return false;
- }
-
- const memorialDate = new Date(this.targetYear, deathDate.getMonth(), deathDate.getDate());
- return memorialDate >= today && memorialDate <= weekEnd;
- });
-
- this.weeklyMemorialCount = weeklyMemorials.length;
- this.todayMemorialCount = weeklyMemorials.filter((kakocho) => {
- const deathDate = this.parseDate(kakocho.deathDate);
- return deathDate?.getMonth() === today.getMonth() && deathDate.getDate() === today.getDate();
- }).length;
- this.upcomingWeeklyMemorialCount = this.weeklyMemorialCount - this.todayMemorialCount;
- this.cdr.detectChanges();
- }
-
- private async setMonthlyMemorialSummary(): Promise<void> {
- const today = this.toDateOnly(new Date());
-
- this.monthlyMemorialCount = (await this.kakochoService.getKakochoList()).filter((kakocho) => {
- const deathDate = this.parseDate(kakocho.deathDate);
- if (!deathDate || !this.isMemorialTarget(deathDate)) {
- return false;
- }
-
- return deathDate.getMonth() === today.getMonth();
- }).length;
- this.cdr.detectChanges();
- }
-
- private async setUpcomingMemorials(): Promise<void> {
- const today = this.toDateOnly(new Date());
- const endDate = this.addDays(today, 30);
-
- const kakochoList = await this.kakochoService.getKakochoList();
-
- const results = await Promise.all(
- kakochoList.map(async (kakocho): Promise<UpcomingMemorial | null> => {
- const deathDate = this.parseDate(kakocho.deathDate);
- if (!deathDate) return null;
-
- const eventDate = new Date(this.targetYear, deathDate.getMonth(), deathDate.getDate());
- if (eventDate < today || eventDate > endDate) return null;
-
- const memorialType = this.getMemorialType(deathDate);
-
- const danka = await this.dankaService.getDankaById(kakocho.dankaId);
-
- const type = memorialType ? '年忌法要' : '命日';
- const status = memorialType ? '準備確認' : '要確認';
-
- return {
- id: kakocho.id,
- dankaId: kakocho.dankaId,
- date: eventDate,
- dateLabel: this.formatDateLabel(eventDate, today),
- title: `${danka?.householdName ?? '不明'} ${kakocho.name}様 ${memorialType || '祥月命日'}`,
- subText: `${danka?.address ?? '住所未登録'} / ${kakocho.kaimyo || '戒名未登録'}`,
- type,
- status,
- };
- }),
- );
-
- this.upcomingMemorials = results
- .filter((memorial): memorial is UpcomingMemorial => memorial !== null)
- .sort((a, b) => a.date.getTime() - b.date.getTime() || a.title.localeCompare(b.title, 'ja'))
- .slice(0, 3);
- this.cdr.detectChanges();
- }
-
- private isMemorialTarget(deathDate: Date): boolean {
- return this.getMemorialType(deathDate) !== '';
- }
-
- private getMemorialType(deathDate: Date): string {
- const yearDiff = this.targetYear - deathDate.getFullYear();
-
- switch (yearDiff) {
- case 1:
- return '一周忌';
- case 2:
- return '三回忌';
- case 6:
- return '七回忌';
- case 12:
- return '十三回忌';
- case 16:
- return '十七回忌';
- case 22:
- return '二十三回忌';
- default:
- return '';
- }
- }
-
- private formatDateLabel(date: Date, today: Date): string {
- if (date.getTime() === today.getTime()) {
- return '今日';
- }
-
- return `${date.getMonth() + 1}月${date.getDate()}日`;
- }
-
- private async getNextMemorialLabel(dankaId: string): Promise<string> {
- const today = this.toDateOnly(new Date());
- const nextMemorial = (await this.kakochoService.getKakochoByDankaId(dankaId))
- .map((kakocho) => {
- const deathDate = this.parseDate(kakocho.deathDate);
- if (!deathDate) {
- return null;
- }
-
- const memorialDate = new Date(this.targetYear, deathDate.getMonth(), deathDate.getDate());
- if (memorialDate < today || !this.getMemorialType(deathDate)) {
- return null;
- }
-
- return memorialDate;
- })
- .filter((date): date is Date => date !== null)
- .sort((a, b) => a.getTime() - b.getTime())[0];
-
- return nextMemorial ? this.formatDateLabel(nextMemorial, today) : '未設定';
- }
-
- private formatUpdatedAt(updatedAt: unknown): string {
- const updatedDate = this.parseDate(updatedAt);
- const today = this.toDateOnly(new Date());
-
- if (!updatedDate) {
- return '未登録';
- }
-
- const diffDays = Math.floor((today.getTime() - updatedDate.getTime()) / 86400000);
-
- if (diffDays === 0) {
- return '今日';
- }
-
- if (diffDays === 1) {
- return '昨日';
- }
-
- if (diffDays > 1 && diffDays <= 7) {
- return `${diffDays}日前`;
- }
-
- return `${updatedDate.getMonth() + 1}月${updatedDate.getDate()}日`;
- }
-
- private formatTodayLabel(date: Date): string {
- const weekdays = ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'];
- return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 ${weekdays[date.getDay()]}`;
- }
-
- private getWeekStart(date: Date): Date {
- const day = date.getDay();
- const diff = day === 0 ? -6 : 1 - day;
- return this.addDays(date, diff);
- }
-
- private addDays(date: Date, days: number): Date {
- const result = new Date(date);
- result.setDate(result.getDate() + days);
- return result;
- }
-
- private toDateOnly(date: Date): Date {
- return new Date(date.getFullYear(), date.getMonth(), date.getDate());
- }
-
- private parseDate(value: unknown): Date | null {
- if (!value) {
- return null;
- }
-
- if (value instanceof Date) {
- return value;
- }
-
- if (typeof value === 'object' && 'toDate' in value && typeof value.toDate === 'function') {
- return value.toDate();
- }
-
- if (typeof value !== 'string') {
- return null;
- }
-
- const [year, month, day] = value.split('-').map(Number);
- if (!year || !month || !day) {
- return null;
- }
- return new Date(year, month - 1, day);
- }
- }
|