import {
ChangeDetectionStrategy,
Component,
computed,
contentChild,
input,
TemplateRef,
ViewEncapsulation,
} from "@angular/core";
import { DateOnly } from "@simplysm/core-common";
import { FormatPipe } from "../../core/format.pipe";
import {
type SdItemOfTemplateContext,
SdItemOfTemplate,
} from "../../core/template/sd-item-of-template";
import { NgTemplateOutlet } from "@angular/common";
@Component({
selector: "sd-calendar",
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [FormatPipe, NgTemplateOutlet],
template: `
|
{{ weeks[weekStartDay() % 7] }}
|
{{ weeks[(weekStartDay() + 1) % 7] }}
|
{{ weeks[(weekStartDay() + 2) % 7] }}
|
{{ weeks[(weekStartDay() + 3) % 7] }}
|
{{ weeks[(weekStartDay() + 4) % 7] }}
|
{{ weeks[(weekStartDay() + 5) % 7] }}
|
{{ weeks[(weekStartDay() + 6) % 7] }}
|
@for (row of dataTable(); let r = $index; track r) {
@for (data of row; let c = $index; track c) {
|
{{ data.date | format: "d" }}
@for (item of data.items; track $index) {
}
|
}
}
`,
styles: [
/* language=SCSS */ `
@use "../../../scss/commons/mixins";
sd-calendar {
> table {
border-collapse: collapse;
width: 100%;
height: 100%;
border-radius: var(--border-radius-default);
overflow: hidden;
> * > tr > * {
padding: var(--gap-sm) var(--gap-default);
border: 1px solid var(--theme-gray-light);
width: calc(100% / 7);
}
> thead > tr > th {
background: var(--theme-gray-lighter);
height: 10%;
}
> tbody > tr > td {
vertical-align: top;
height: 15%;
> .day {
margin-bottom: var(--gap-sm);
}
&.not-current {
background: var(--theme-gray-lightest);
> .day {
color: var(--theme-gray-default);
}
}
> .content {
display: flex;
flex-wrap: nowrap;
@include mixins.flex-direction(column, var(--gap-sm));
}
}
}
}
`,
],
})
export class SdCalendar {
items = input.required();
getItemDateFn = input.required<(item: T, index: number) => DateOnly>();
yearMonth = input(new DateOnly().setDay(1));
itemTplRef = contentChild.required>>(
SdItemOfTemplate,
{ read: TemplateRef },
);
weekStartDay = input(0);
minDaysInFirstWeek = input(1);
weeks = ["일", "월", "화", "수", "목", "금", "토"];
dataTable = computed(() => {
const result: {
date: DateOnly;
items: T[];
}[][] = [];
// Build a Map once for O(1) lookup per cell
const itemsByTick = new Map();
const getDateFn = this.getItemDateFn();
for (let i = 0; i < this.items().length; i++) {
const item = this.items()[i];
const tick = getDateFn(item, i).tick;
const arr = itemsByTick.get(tick);
if (arr != null) {
arr.push(item);
} else {
itemsByTick.set(tick, [item]);
}
}
const firstDate = this.yearMonth().getWeekSeqStartDate(
this.weekStartDay(),
this.minDaysInFirstWeek(),
);
for (let r = 0; r < 6; r++) {
const row: {
date: DateOnly;
items: T[];
}[] = [];
for (let c = 0; c < 7; c++) {
const date = firstDate.addDays(r * 7 + c);
row.push({
date,
items: itemsByTick.get(date.tick) ?? [],
});
}
result.push(row);
}
return result;
});
}