import { NgTemplateOutlet } from "@angular/common";
import {
booleanAttribute,
ChangeDetectionStrategy,
Component,
computed,
contentChild,
effect,
inject,
input,
model,
signal,
TemplateRef,
untracked,
ViewEncapsulation,
} from "@angular/core";
import { str } from "@simplysm/core-common";
import { matchesSearchText } from "./matchesSearchText";
import type { SharedDataBase } from "../../core/shared-data/sd-shared-data.provider";
import {
SdItemOfTemplate,
type SdItemOfTemplateContext,
} from "../../core/template/sd-item-of-template";
import { SdModalProvider } from "../../core/modal/sd-modal.provider";
import type {
SdSelectModal,
SdSelectModalInfo,
} from "../../controls/button/sd-modal-select-button";
import { setupModelHook } from "../../core/setupModelHook";
import { SdPagination } from "../../controls/pagination/sd-pagination";
import { SdTextfield } from "../../controls/input/sd-textfield";
import { SdAnchor } from "../../controls/button/sd-anchor";
import { SdList } from "../../controls/list/sd-list";
import { SdListItem } from "../../controls/list/sd-list-item";
import { NgIcon } from "@ng-icons/core";
import { tablerExternalLink } from "@ng-icons/tabler-icons";
@Component({
selector: "sd-shared-data-select-list",
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [
NgTemplateOutlet,
SdTextfield,
SdPagination,
SdAnchor,
SdList,
SdListItem,
NgIcon,
],
host: {
class: "flex-column fill",
},
template: `
@if (header()) {
}
@if (headerTplRef() || modal()) {
@if (headerTplRef()) {
}
@if (modal()) {
}
}
@if (!filterTplRef()) {
} @else {
}
@if (pageItemCount()) {
}
@if (useUndefined()) {
@if (undefinedTplRef()) {
} @else {
미지정
}
}
@for (item of displayItems(); let index = $index; track item.__valueKey) {
}
`,
})
export class SdSharedDataSelectList<
TItem extends SharedDataBase,
TModal extends SdSelectModal,
> {
private readonly _sdModal = inject(SdModalProvider);
selectedItem = model();
canChangeFn = input<(item: TItem | undefined) => boolean | Promise>(
() => true,
);
items = input.required();
selectedIcon = input();
useUndefined = input(false, { transform: booleanAttribute });
filterFn = input<(item: TItem, index: number) => boolean>();
modal = input>();
header = input();
headerTplRef = contentChild>("headerTpl", {
read: TemplateRef,
});
filterTplRef = contentChild>("filterTpl", {
read: TemplateRef,
});
itemTplRef = contentChild>>(
SdItemOfTemplate,
{ read: TemplateRef },
);
undefinedTplRef = contentChild>("undefinedTpl", {
read: TemplateRef,
});
searchText = signal(undefined);
pageItemCount = input();
page = signal(0);
private readonly _filteredItems = computed(() => {
let result = this.items().filter((item) => !item.__isHidden);
if (!str.isNullOrEmpty(this.searchText())) {
result = result.filter((item) =>
matchesSearchText(item.__searchText, this.searchText()),
);
}
if (this.filterFn() != null) {
result = result.filter((item, i) => this.filterFn()!(item, i));
}
return result;
});
pageLength = computed(() => {
const pic = this.pageItemCount();
if (pic != null && pic > 0) {
return Math.ceil(this._filteredItems().length / pic);
}
return 0;
});
displayItems = computed(() => {
const filtered = this._filteredItems();
const pic = this.pageItemCount();
if (pic != null && pic > 0) {
return filtered.slice(pic * this.page(), pic * (this.page() + 1));
}
return filtered;
});
constructor() {
setupModelHook(this.selectedItem, this.canChangeFn);
// items 변경 시 selectedItem 동기화 (selectedItem은 추적하지 않음)
effect(() => {
const items = this.items();
const current = untracked(() => this.selectedItem());
if (current != null) {
const updated = items.find(
(item) => item.__valueKey === current.__valueKey,
);
if (updated !== current) {
this.selectedItem.set(updated);
}
}
});
}
select(item: TItem | undefined): void {
this.selectedItem.set(item);
}
toggle(item: TItem | undefined): void {
this.selectedItem.update((v) => (v === item ? undefined : item));
}
async onModalButtonClick(): Promise {
const modalInfo = this.modal();
if (modalInfo == null) return;
const result = await this._sdModal.showAsync({
...modalInfo,
inputs: {
selectMode: "single",
selectedKeys: [this.selectedItem()]
.filter((v): v is NonNullable => v != null)
.map((item) => item.__valueKey),
...modalInfo.inputs,
} as any,
});
if (result != null) {
const newSelectedItem = this.items().find(
(item) => item.__valueKey === result.selectedKeys[0],
);
this.selectedItem.set(newSelectedItem);
}
}
protected readonly tablerExternalLink = tablerExternalLink;
}