@kengcom202/kcore-ui 0.1.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kengcom202-kcore-ui.mjs","names":[],"sources":["../../../projects/ui/src/lib/utils/anchored-popup.ts","../../../projects/ui/src/lib/utils/thai-date.ts","../../../projects/ui/src/lib/pickers/datepicker-popup.component.ts","../../../projects/ui/src/lib/pickers/picker-styles.ts","../../../projects/ui/src/lib/pickers/datepicker.directive.ts","../../../projects/ui/src/lib/pickers/clockpicker-popup.component.ts","../../../projects/ui/src/lib/pickers/clockpicker.directive.ts","../../../projects/ui/src/lib/pickers/thai-date-picker.component.ts","../../../projects/ui/src/lib/pickers/thai-date-picker.component.html"],"sourcesContent":["import { ApplicationRef, ComponentRef, EnvironmentInjector, NgZone, Type, createComponent } from '@angular/core';\n\n/**\n * popup ที่ผูกกับช่อง input หนึ่งช่อง — ใช้ร่วมกันโดย appDatePicker และ appClockPicker\n *\n * สร้าง component ตอนเปิดแล้วแปะไว้ที่ <body> (ไม่ใช่ข้าง input) เพื่อไม่ให้โดน overflow:hidden ของกล่อง/modal\n * ที่ครอบช่องตัดทิ้ง และทำลายทิ้งทันทีที่ปิด ตัว component ต้องเป็น position:fixed เอง (ตำแหน่ง top/left ตั้งจากที่นี่)\n * - วางใต้ช่อง ถ้าด้านล่างไม่พอแต่ด้านบนพอให้เปิดขึ้นด้านบน และไม่ให้ล้นขอบซ้าย/ขวาของจอ (จอมือถือ)\n * - ปิดเองเมื่อคลิก/แตะนอกช่องและนอก popup, วางตำแหน่งใหม่เมื่อเลื่อนหน้าหรือย่อขยายจอ\n */\nexport class AnchoredPopup<T> {\n private ref: ComponentRef<T> | null = null;\n\n private readonly outsideHandler = (e: Event) => {\n const target = e.target as Node;\n if (this.anchor.contains(target) || this.ref?.location.nativeElement.contains(target)) return;\n this.zone.run(() => this.close());\n };\n private readonly repositionHandler = () => this.position();\n\n constructor(\n private anchor: HTMLElement,\n private component: Type<T>,\n private appRef: ApplicationRef,\n private injector: EnvironmentInjector,\n private zone: NgZone,\n ) {}\n\n get isOpen(): boolean {\n return this.ref !== null;\n }\n\n get instance(): T | null {\n return this.ref?.instance ?? null;\n }\n\n /** setup ใช้ตั้งค่าเริ่มต้น/callback ให้ component ก่อนเรนเดอร์ครั้งแรก */\n open(setup: (instance: T) => void): void {\n if (this.ref) return;\n\n const ref = createComponent(this.component, { environmentInjector: this.injector });\n setup(ref.instance);\n this.appRef.attachView(ref.hostView);\n document.body.appendChild(ref.location.nativeElement);\n ref.changeDetectorRef.detectChanges();\n this.ref = ref;\n this.position();\n\n this.zone.runOutsideAngular(() => {\n document.addEventListener('mousedown', this.outsideHandler, true);\n document.addEventListener('touchstart', this.outsideHandler, true);\n window.addEventListener('scroll', this.repositionHandler, true);\n window.addEventListener('resize', this.repositionHandler);\n });\n }\n\n close(): void {\n if (!this.ref) return;\n document.removeEventListener('mousedown', this.outsideHandler, true);\n document.removeEventListener('touchstart', this.outsideHandler, true);\n window.removeEventListener('scroll', this.repositionHandler, true);\n window.removeEventListener('resize', this.repositionHandler);\n this.appRef.detachView(this.ref.hostView);\n this.ref.destroy();\n this.ref = null;\n }\n\n position(): void {\n const popup = this.ref?.location.nativeElement as HTMLElement | undefined;\n if (!popup) return;\n const rect = this.anchor.getBoundingClientRect();\n const h = popup.offsetHeight;\n const w = popup.offsetWidth;\n const gap = 2;\n const margin = 8;\n\n let top = rect.bottom + gap;\n if (top + h > window.innerHeight - margin && rect.top - gap - h >= margin) {\n top = rect.top - gap - h;\n }\n const left = Math.max(margin, Math.min(rect.left, window.innerWidth - w - margin));\n\n popup.style.top = `${top}px`;\n popup.style.left = `${left}px`;\n }\n}\n","/** ส่วนประกอบวันที่แบบ ค.ศ. (month เริ่มที่ 0 แบบ Date) */\nexport interface CeDateParts { year: number; month: number; day: number; }\n\n/** ผลต่างระหว่างปี พ.ศ. กับ ค.ศ. */\nconst BE_OFFSET = 543;\n/** ปีที่เกินค่านี้ถือว่าเป็น พ.ศ. อยู่แล้ว ใช้กันบวกทบเวลาถูกเรียกซ้ำ */\nconst BE_THRESHOLD = 2400;\n/** รูปแบบค่าที่ใช้ทั้งใน input และ ngModel: yyyy-MM-dd */\nconst DATE_PATTERN = /^(\\d{4})(-\\d{2}-\\d{2})$/;\n\n/** ข้อความ yyyy-MM-dd ค.ศ. → พ.ศ. ; ค่าที่ไม่ตรงรูปแบบคืนตามเดิม */\nexport function toBuddhistText(val: unknown): string {\n const text = val === null || val === undefined ? '' : String(val);\n const m = DATE_PATTERN.exec(text);\n if (!m) return text;\n const year = parseInt(m[1], 10);\n return year > BE_THRESHOLD ? text : `${year + BE_OFFSET}${m[2]}`;\n}\n\n/** ข้อความ yyyy-MM-dd พ.ศ. → ค.ศ. ; ค่าที่ไม่ตรงรูปแบบคืนตามเดิม */\nexport function toChristianText(val: unknown): string {\n const text = val === null || val === undefined ? '' : String(val);\n const m = DATE_PATTERN.exec(text);\n if (!m) return text;\n const year = parseInt(m[1], 10);\n return year > BE_THRESHOLD ? `${year - BE_OFFSET}${m[2]}` : text;\n}\n\n/** ข้อความ yyyy-MM-dd (ค.ศ. หรือ พ.ศ.) → ส่วนประกอบ ค.ศ. ; ค่าที่ไม่ตรงรูปแบบคืน null */\nexport function parseDate(val: unknown): CeDateParts | null {\n if (typeof val !== 'string' || val.length < 10) return null;\n const parts = val.substring(0, 10).split('-').map(p => parseInt(p, 10));\n if (parts.length !== 3 || parts.some(isNaN)) return null;\n let [year, month, day] = parts;\n // รับมาจาก API เป็น ค.ศ. เสมอ เผื่อไว้เฉพาะกรณีมีคนป้อน พ.ศ. เข้ามา\n if (year > BE_THRESHOLD) year -= BE_OFFSET;\n return { year, month: month - 1, day };\n}\n\n/** ส่วนประกอบ ค.ศ. → ข้อความ yyyy-MM-dd ค.ศ. */\nexport function formatDate(d: CeDateParts): string {\n return `${d.year}-${String(d.month + 1).padStart(2, '0')}-${String(d.day).padStart(2, '0')}`;\n}\n","import { Component, computed, signal } from '@angular/core';\nimport { CeDateParts } from '../utils/thai-date';\n\ntype CalendarView = 'day' | 'month' | 'year';\n\n/**\n * ปฏิทินแบบ dropdown ของ appDatePicker (ดู datepicker.directive.ts) — เขียนเองแทน Syncfusion ej2 DatePicker\n * ที่ต้องโหลด bundle 27.7 MB + css 4.8 MB และต้องมี license key\n *\n * directive สร้าง component นี้ตอนเปิดปฏิทินแล้วแปะไว้ที่ <body> (ไม่ใช่ข้าง input) เพื่อไม่ให้โดน overflow:hidden\n * ของกล่อง/modal ที่ครอบช่องตัดทิ้ง และทำลายทิ้งทันทีที่ปิด — ค่าเข้า/ออกผ่าน property กับ callback ที่ directive ตั้งให้\n *\n * ภายในทำงานกับ ค.ศ. ล้วน แปลงเป็น พ.ศ. ตอนแสดงผลเท่านั้น\n * มุมมองเหมือน ej2 เดิม: วัน → (คลิกหัว) เดือน → (คลิกหัว) ปีทีละ 10 ปี — จำเป็นกับช่องวันเกิดที่ต้องย้อนไปหลายสิบปี\n */\n@Component({\n selector: 'app-datepicker-popup',\n standalone: true,\n host: {\n class: 'dpp',\n // กันไม่ให้ mousedown ในปฏิทินขโมย focus ไปจาก input (ไม่งั้นผู้ใช้กด Tab ต่อจากช่องเดิมไม่ได้)\n '(mousedown)': '$event.preventDefault()'\n },\n template: `\n <div class=\"dpp-header\">\n <button type=\"button\" class=\"dpp-nav\" (click)=\"shift(-1)\" aria-label=\"ก่อนหน้า\">‹</button>\n <button type=\"button\" class=\"dpp-title\" [disabled]=\"view() === 'year'\" (click)=\"zoomOut()\">{{ title() }}</button>\n <button type=\"button\" class=\"dpp-nav\" (click)=\"shift(1)\" aria-label=\"ถัดไป\">›</button>\n </div>\n\n @switch (view()) {\n @case ('day') {\n <div class=\"dpp-grid dpp-days\">\n @for (w of weekdays; track w) { <div class=\"dpp-weekday\">{{ w }}</div> }\n @for (day of dayCells(); track $index) {\n @if (day === null) {\n <div></div>\n } @else {\n <button type=\"button\" class=\"dpp-cell\"\n [class.dpp-selected]=\"isSelectedDay(day)\" [class.dpp-today]=\"isToday(day)\"\n (click)=\"pickDay(day)\">{{ day }}</button>\n }\n }\n </div>\n }\n @case ('month') {\n <div class=\"dpp-grid dpp-wide\">\n @for (m of monthsShort; track $index) {\n <button type=\"button\" class=\"dpp-cell\"\n [class.dpp-selected]=\"selected?.year === viewYear() && selected?.month === $index\"\n (click)=\"pickMonth($index)\">{{ m }}</button>\n }\n </div>\n }\n @case ('year') {\n <div class=\"dpp-grid dpp-wide\">\n @for (y of yearCells(); track y; let i = $index) {\n <button type=\"button\" class=\"dpp-cell\"\n [class.dpp-outside]=\"i === 0 || i === 11\"\n [class.dpp-selected]=\"selected?.year === y\"\n (click)=\"pickYear(y)\">{{ y + 543 }}</button>\n }\n </div>\n }\n }\n\n <div class=\"dpp-footer\">\n <button type=\"button\" class=\"dpp-today-btn\" (click)=\"pickToday()\">วันนี้</button>\n </div>\n `,\n styles: [`\n :host {\n position: fixed;\n /* modal/overlay ทั้งระบบใช้ 9000-9999 — ช่องวันที่อยู่ใน modal ได้ (เช่นวันเกิดในหน้าครอบครัว) ต้องอยู่เหนือกว่า */\n z-index: 10000;\n width: 260px;\n max-width: calc(100vw - 16px);\n background: #fff;\n border: 1px solid #ccc;\n border-radius: 6px;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);\n padding: 8px;\n font-size: 13px;\n color: #333;\n user-select: none;\n }\n button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }\n .dpp-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }\n .dpp-nav { width: 30px; height: 30px; border-radius: 50%; font-size: 18px; line-height: 1; }\n .dpp-nav:hover, .dpp-title:not([disabled]):hover, .dpp-cell:hover { background: #eef1fb; }\n .dpp-title { flex: 1; height: 30px; border-radius: 4px; font-weight: 600; }\n .dpp-title[disabled] { cursor: default; }\n .dpp-grid { display: grid; gap: 2px; }\n .dpp-days { grid-template-columns: repeat(7, 1fr); }\n .dpp-wide { grid-template-columns: repeat(3, 1fr); }\n .dpp-weekday { text-align: center; font-size: 12px; color: #888; padding: 4px 0; }\n .dpp-weekday:first-child { color: #d9534f; }\n .dpp-cell { height: 32px; border-radius: 4px; text-align: center; }\n .dpp-wide .dpp-cell { height: 44px; }\n .dpp-today { border: 1px solid #3f51b5; }\n .dpp-selected, .dpp-selected:hover { background: #3f51b5; color: #fff; }\n .dpp-outside { color: #aaa; }\n .dpp-footer { display: flex; justify-content: flex-end; margin-top: 6px; border-top: 1px solid #eee; padding-top: 6px; }\n .dpp-today-btn { color: #3f51b5; font-weight: 600; padding: 4px 10px; border-radius: 4px; }\n .dpp-today-btn:hover { background: #eef1fb; }\n `]\n})\nexport class DatePickerPopupComponent {\n readonly weekdays = ['อา', 'จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส'];\n readonly monthsShort = ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'];\n private readonly monthsFull = ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน',\n 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'];\n\n /** ตั้งโดย directive ตอนสร้าง — วันที่เลือกไว้ (หรือ null) */\n selected: CeDateParts | null = null;\n /** ตั้งโดย directive — เรียกเมื่อผู้ใช้เลือกวัน */\n onPick: (date: CeDateParts) => void = () => {};\n\n view = signal<CalendarView>('day');\n viewYear = signal(new Date().getFullYear());\n viewMonth = signal(new Date().getMonth());\n\n /** เปิดปฏิทินที่เดือนของวันที่เลือกไว้ ถ้าไม่มีให้เปิดที่เดือนปัจจุบัน */\n init(selected: CeDateParts | null): void {\n this.selected = selected;\n const base = selected ?? this.todayParts();\n this.viewYear.set(base.year);\n this.viewMonth.set(base.month);\n this.view.set('day');\n }\n\n title = computed(() => {\n const y = this.viewYear();\n switch (this.view()) {\n case 'day': return `${this.monthsFull[this.viewMonth()]} ${y + 543}`;\n case 'month': return `${y + 543}`;\n default: {\n const start = this.decadeStart(y);\n return `${start + 543} - ${start + 9 + 543}`;\n }\n }\n });\n\n // ช่องว่าง (null) เติมหน้าวันที่ 1 ให้ตรงวันในสัปดาห์\n dayCells = computed<(number | null)[]>(() => {\n const y = this.viewYear();\n const m = this.viewMonth();\n const cells: (number | null)[] = Array(new Date(y, m, 1).getDay()).fill(null);\n const daysInMonth = new Date(y, m + 1, 0).getDate();\n for (let d = 1; d <= daysInMonth; d++) cells.push(d);\n return cells;\n });\n\n // 12 ช่องเหมือน ej2: ปีท้ายทศวรรษก่อน + 10 ปีของทศวรรษนี้ + ปีแรกของทศวรรษถัดไป\n yearCells = computed<number[]>(() => {\n const start = this.decadeStart(this.viewYear());\n return Array.from({ length: 12 }, (_, i) => start - 1 + i);\n });\n\n shift(delta: number): void {\n if (this.view() === 'day') {\n const total = this.viewYear() * 12 + this.viewMonth() + delta;\n this.viewYear.set(Math.floor(total / 12));\n this.viewMonth.set(((total % 12) + 12) % 12);\n } else {\n this.viewYear.update(y => y + delta * (this.view() === 'month' ? 1 : 10));\n }\n }\n\n zoomOut(): void {\n this.view.set(this.view() === 'day' ? 'month' : 'year');\n }\n\n pickYear(year: number): void {\n this.viewYear.set(year);\n this.view.set('month');\n }\n\n pickMonth(month: number): void {\n this.viewMonth.set(month);\n this.view.set('day');\n }\n\n pickDay(day: number): void {\n this.onPick({ year: this.viewYear(), month: this.viewMonth(), day });\n }\n\n pickToday(): void {\n this.onPick(this.todayParts());\n }\n\n isSelectedDay(day: number): boolean {\n const s = this.selected;\n return !!s && s.year === this.viewYear() && s.month === this.viewMonth() && s.day === day;\n }\n\n isToday(day: number): boolean {\n const t = this.todayParts();\n return t.year === this.viewYear() && t.month === this.viewMonth() && t.day === day;\n }\n\n private decadeStart(year: number): number {\n return Math.floor(year / 10) * 10;\n }\n\n private todayParts(): CeDateParts {\n const d = new Date();\n return { year: d.getFullYear(), month: d.getMonth(), day: d.getDate() };\n }\n}\n","import { CSP_NONCE, DOCUMENT, inject } from '@angular/core';\n\nconst STYLE_ID = 'kcore-picker-styles';\n\n/**\n * สไตล์ของช่อง input ที่ใช้ appDatePicker / appClockPicker (เดิมอยู่ใน master.css ของแต่ละโปรเจกต์)\n * ใส่ลง <head> ครั้งเดียวต่อหน้า โปรเจกต์ปลายทางจึงไม่ต้องเพิ่ม css เอง\n *\n * ช่องวันที่: ไอคอนปฏิทินท้ายช่อง + เคอร์เซอร์มือ — ตั้งแค่ background-image ไม่ใช้ shorthand background\n * เพื่อไม่ทับสีพื้นที่แต่ละหน้ากำหนดให้ช่องเอง\n * ช่องเวลา: ไม่ใส่ไอคอนเหมือนช่องวันที่ เพราะหลายหน้ากำหนดช่องกว้างแค่ 70px พอดี \"08:30\"\n */\nexport const PICKER_CSS = `\n.app-datepicker {\n cursor: pointer;\n padding-right: 26px !important;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 7px center;\n background-size: 14px 14px;\n}\n.app-datepicker:disabled { cursor: default; }\n.app-clockpicker { cursor: pointer; }\n.app-clockpicker:disabled { cursor: default; }\n`;\n\n/** เรียกใน injection context (constructor/field ของ directive) — ใส่ <style> ครั้งแรกครั้งเดียว ครั้งถัดไปไม่ทำอะไร */\nexport function ensurePickerStyles(): void {\n const doc = inject(DOCUMENT);\n if (!doc.head || doc.getElementById(STYLE_ID)) return;\n const style = doc.createElement('style');\n style.id = STYLE_ID;\n // รองรับหน้าที่ตั้ง Content-Security-Policy แบบ nonce ไว้ (ตั้งผ่าน CSP_NONCE หรือ ngCspNonce)\n const nonce = inject(CSP_NONCE, { optional: true });\n if (nonce) style.setAttribute('nonce', nonce);\n style.textContent = PICKER_CSS;\n doc.head.appendChild(style);\n}\n","import { ApplicationRef, Directive, ElementRef, EnvironmentInjector, NgZone, OnDestroy, forwardRef, inject } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { AnchoredPopup } from '../utils/anchored-popup';\nimport { CeDateParts, formatDate, parseDate, toBuddhistText, toChristianText } from '../utils/thai-date';\nimport { DatePickerPopupComponent } from './datepicker-popup.component';\nimport { ensurePickerStyles } from './picker-styles';\n\n/**\n * ช่องเลือกวันที่แบบไทย ใช้กับ <input type=\"text\" appDatePicker formControlName=\"...\">\n * - ค่าใน form control เป็น ค.ศ. yyyy-MM-dd เสมอ (ตรงกับที่ API ใช้) ส่วนในช่องแสดงเป็น พ.ศ.\n * - คลิกช่อง = เปิดปฏิทิน (DatePickerPopupComponent) ห้ามพิมพ์เอง เหมือน onkeydown=\"return false\" ของ ASP เดิม\n *\n * เดิมห่อ Syncfusion ej2 DatePicker ซึ่งต้องโหลด js/datepicker.min.js 27.7 MB + css 4.8 MB ไว้ใน <head> ทุกหน้า\n * (หน้า signature-accept ที่เปิดจากแจ้งเตือน IDMS บนมือถือจึงช้ามาก) และต้องมี license key ของ Syncfusion\n * จึงเขียนเองแทน — สัญญาของค่า (ค.ศ. ใน control / พ.ศ. บนจอ) เหมือนเดิมทุกประการ หน้าที่ใช้อยู่ไม่ต้องแก้\n */\n@Directive({\n selector: '[appDatePicker]',\n standalone: true,\n host: {\n class: 'app-datepicker',\n readonly: '',\n autocomplete: 'off',\n placeholder: 'เลือกวันที่',\n '(click)': 'open()',\n '(keydown)': 'onKeydown($event)',\n '(blur)': 'onTouched()',\n },\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => DatePickerDirective),\n multi: true,\n },\n ],\n})\nexport class DatePickerDirective implements OnDestroy, ControlValueAccessor {\n private el = inject<ElementRef<HTMLInputElement>>(ElementRef);\n private popup = new AnchoredPopup(\n this.el.nativeElement, DatePickerPopupComponent,\n inject(ApplicationRef), inject(EnvironmentInjector), inject(NgZone));\n\n private valuePatched = false;\n private disabled = false;\n\n private onChange: (val: string) => void = () => {};\n onTouched: () => void = () => {};\n\n constructor() {\n ensurePickerStyles();\n // ต้องครอบ property `value` ตั้งแต่ตอนสร้าง directive คือก่อน writeValue() ครั้งแรก\n // ไม่งั้นค่าแรกที่ถูกเขียนลงช่องจะโผล่เป็น ค.ศ.\n this.patchInputValue();\n }\n\n open(): void {\n if (this.disabled) return;\n this.popup.open(p => {\n p.init(parseDate(this.el.nativeElement.value));\n p.onPick = (d) => this.select(d);\n });\n }\n\n close(): void {\n this.popup.close();\n }\n\n onKeydown(e: KeyboardEvent): void {\n if (e.key === 'Escape' || e.key === 'Esc' || e.key === 'Tab') {\n this.close();\n return;\n }\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault();\n this.open();\n return;\n }\n // ห้ามพิมพ์เอง ต้องเลือกจากปฏิทินเท่านั้น (เหมือนเดิม) — คีย์ผสม เช่น Ctrl+C ปล่อยผ่าน\n if (!e.ctrlKey && !e.metaKey) e.preventDefault();\n }\n\n ngOnDestroy(): void {\n this.close();\n this.unpatchInputValue();\n }\n\n writeValue(val: string): void {\n const parsed = parseDate(val);\n // เขียนเป็น ค.ศ. เสมอ setter ที่ครอบไว้จะเก็บเป็น พ.ศ. ให้เอง ช่องจึงแสดง พ.ศ. ตั้งแต่แรก\n this.el.nativeElement.value = parsed ? formatDate(parsed) : '';\n this.popup.instance?.init(parsed);\n }\n\n registerOnChange(fn: (val: string) => void): void {\n this.onChange = fn;\n }\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.disabled = isDisabled;\n this.el.nativeElement.disabled = isDisabled;\n if (isDisabled) this.close();\n }\n\n private select(d: CeDateParts): void {\n const ceDate = formatDate(d);\n this.el.nativeElement.value = ceDate;\n this.close();\n this.onChange(ceDate);\n this.onTouched();\n }\n\n /**\n * ครอบ property `value` ของ input ตัวนี้ (เฉพาะ element นี้ ไม่แตะ HTMLInputElement.prototype ส่วนกลาง)\n * โดยห่อ getter/setter เดิมของเบราว์เซอร์ไว้:\n * - setter: บวก 543 ก่อนเก็บจริง → ช่องแสดง พ.ศ. ตลอด ไม่ว่าใครเป็นคนเขียน\n * - getter: ลบ 543 คืนเป็น ค.ศ. เสมอ → โค้ดที่อ่าน input.value ตรง ๆ ได้ ค.ศ. เหมือนค่าใน form control\n */\n private patchInputValue(): void {\n const input = this.el.nativeElement;\n if (!input || this.valuePatched) return;\n\n const desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');\n const nativeGet = desc?.get;\n const nativeSet = desc?.set;\n if (!nativeGet || !nativeSet) return;\n\n Object.defineProperty(input, 'value', {\n configurable: true,\n enumerable: true,\n get: () => toChristianText(nativeGet.call(input)),\n set: (v: unknown) => nativeSet.call(input, toBuddhistText(v)),\n });\n this.valuePatched = true;\n }\n\n /** คืน property `value` ให้กลับไปใช้ตัวเดิมบน prototype */\n private unpatchInputValue(): void {\n if (!this.valuePatched) return;\n this.valuePatched = false;\n const input = this.el.nativeElement;\n if (input) delete (input as any).value;\n }\n}\n","import { Component, computed, signal } from '@angular/core';\n\ntype ClockView = 'hour' | 'minute';\n\n/** ขนาดหน้าปัด (พิกัดใน viewBox ของ SVG) */\nconst SIZE = 220;\nconst CENTER = SIZE / 2;\nconst OUTER_R = 88;\nconst INNER_R = 58;\nconst TICK_R = 15;\n\ninterface Tick { label: string; value: number; x: number; y: number; }\n\n/** ตำแหน่งบนหน้าปัดของช่องที่ index (0 = 12 นาฬิกา ไล่ตามเข็ม ช่องละ 30°) */\nfunction tickPos(index: number, radius: number): { x: number; y: number } {\n const a = (index * Math.PI) / 6;\n return { x: CENTER + radius * Math.sin(a), y: CENTER - radius * Math.cos(a) };\n}\n\nconst pad = (n: number) => String(n).padStart(2, '0');\n\n/**\n * หน้าปัดเลือกเวลาของ appClockPicker (ดู clockpicker.directive.ts) — เขียนเองแทนปลั๊กอิน jQuery clockpicker\n * ใช้งานแบบเดียวกับของเดิม: แตะ/ลากเลือกชั่วโมงก่อน → สลับไปหน้าปัดนาที → เลือกนาทีแล้วปิดเอง (autoclose เดิม)\n *\n * วงชั่วโมงจัดแบบเดียวกับ jQuery clockpicker โหมด 24 ชม. ที่ผู้ใช้คุ้นอยู่แล้ว: วงใน 12, 1–11 / วงนอก 00, 13–23\n * นาทีปัดเป็นทีละ 5 นาที (ข้อมูลเดิมใน DB เกือบทั้งหมดลงตัว 5 นาทีอยู่แล้ว และแตะให้ตรงทีละนาทีบนมือถือแทบไม่ได้)\n */\n@Component({\n selector: 'app-clockpicker-popup',\n standalone: true,\n host: {\n class: 'cpp',\n // กันไม่ให้ mousedown ใน popup ขโมย focus ไปจาก input (ไม่งั้นผู้ใช้กด Tab ต่อจากช่องเดิมไม่ได้)\n '(mousedown)': '$event.preventDefault()'\n },\n template: `\n <div class=\"cpp-header\">\n <button type=\"button\" class=\"cpp-part\" [class.cpp-active]=\"view() === 'hour'\" (click)=\"view.set('hour')\">{{ pad(hour()) }}</button>\n <span class=\"cpp-colon\">:</span>\n <button type=\"button\" class=\"cpp-part\" [class.cpp-active]=\"view() === 'minute'\" (click)=\"view.set('minute')\">{{ pad(minute()) }}</button>\n </div>\n\n <svg class=\"cpp-dial\" [attr.viewBox]=\"'0 0 ' + size + ' ' + size\"\n (pointerdown)=\"onPointerDown($event)\" (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\" (pointercancel)=\"dragging = false\">\n <circle class=\"cpp-face\" [attr.cx]=\"center\" [attr.cy]=\"center\" [attr.r]=\"center - 2\" />\n <line class=\"cpp-hand\" [attr.x1]=\"center\" [attr.y1]=\"center\" [attr.x2]=\"hand().x\" [attr.y2]=\"hand().y\" />\n <circle class=\"cpp-hand-dot\" [attr.cx]=\"center\" [attr.cy]=\"center\" r=\"3\" />\n <circle class=\"cpp-hand-tip\" [attr.cx]=\"hand().x\" [attr.cy]=\"hand().y\" [attr.r]=\"tickR\" />\n @for (t of ticks(); track t.value) {\n <text class=\"cpp-tick\" [class.cpp-tick-inner]=\"view() === 'hour' && t.value >= 1 && t.value <= 12\"\n [class.cpp-tick-selected]=\"t.value === (view() === 'hour' ? hour() : minute())\"\n [attr.x]=\"t.x\" [attr.y]=\"t.y\">{{ t.label }}</text>\n }\n </svg>\n `,\n styles: [`\n :host {\n position: fixed;\n /* modal/overlay ทั้งระบบใช้ 9000-9999 — ช่องเวลาอยู่ใน modal ได้ ต้องอยู่เหนือกว่า */\n z-index: 10000;\n width: 240px;\n max-width: calc(100vw - 16px);\n background: #fff;\n border: 1px solid #ccc;\n border-radius: 6px;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);\n padding: 8px 10px 10px;\n color: #333;\n user-select: none;\n }\n button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }\n .cpp-header { display: flex; justify-content: center; align-items: center; gap: 2px; margin-bottom: 6px; font-size: 26px; }\n .cpp-part { padding: 0 6px; border-radius: 4px; color: #999; }\n .cpp-part:hover { background: #eef1fb; }\n .cpp-active { color: #3f51b5; font-weight: 600; }\n .cpp-colon { color: #999; }\n .cpp-dial { display: block; width: 100%; height: auto; touch-action: none; cursor: pointer; }\n .cpp-face { fill: #f2f3f7; }\n .cpp-hand { stroke: #3f51b5; stroke-width: 2; }\n .cpp-hand-dot, .cpp-hand-tip { fill: #3f51b5; }\n .cpp-tick { font-size: 14px; text-anchor: middle; dominant-baseline: central; fill: #333; pointer-events: none; }\n .cpp-tick-inner { font-size: 12px; fill: #666; }\n .cpp-tick-selected { fill: #fff; }\n `]\n})\nexport class ClockPickerPopupComponent {\n readonly size = SIZE;\n readonly center = CENTER;\n readonly tickR = TICK_R;\n readonly pad = pad;\n\n /** ตั้งโดย directive — เรียกเมื่อเลือกนาทีเสร็จ ส่งเวลาแบบ HH:mm */\n onPick: (time: string) => void = () => {};\n\n view = signal<ClockView>('hour');\n hour = signal(8);\n minute = signal(30);\n dragging = false;\n\n /** เปิดที่เวลาในช่อง (\"HH:mm\" หรือ \"HH:mm:ss\") ถ้าว่าง/อ่านไม่ออกให้เปิดที่เวลาปัจจุบัน */\n init(value: string): void {\n const m = /^(\\d{1,2}):(\\d{2})/.exec(value || '');\n const now = new Date();\n const h = m ? Number(m[1]) : now.getHours();\n const min = m ? Number(m[2]) : now.getMinutes();\n this.hour.set(h >= 0 && h < 24 ? h : 0);\n this.minute.set(min >= 0 && min < 60 ? min : 0);\n this.view.set('hour');\n }\n\n ticks = computed<Tick[]>(() => {\n if (this.view() === 'minute') {\n return Array.from({ length: 12 }, (_, i) => ({ label: pad(i * 5), value: i * 5, ...tickPos(i, OUTER_R) }));\n }\n const outer = Array.from({ length: 12 }, (_, i) => {\n const value = i === 0 ? 0 : i + 12;\n return { label: pad(value), value, ...tickPos(i, OUTER_R) };\n });\n const inner = Array.from({ length: 12 }, (_, i) => {\n const value = i === 0 ? 12 : i;\n return { label: String(value), value, ...tickPos(i, INNER_R) };\n });\n return [...outer, ...inner];\n });\n\n hand = computed(() => {\n if (this.view() === 'minute') {\n // ค่าเดิมที่ไม่ลงตัว 5 นาที (เช่น 08:47) ชี้ตามจริง ไม่ปัด\n return tickPos(this.minute() / 5, OUTER_R);\n }\n const h = this.hour();\n return tickPos(h % 12, h >= 1 && h <= 12 ? INNER_R : OUTER_R);\n });\n\n onPointerDown(e: PointerEvent): void {\n this.dragging = true;\n // ให้ลากออกนอกหน้าปัดแล้วยังได้ pointermove/pointerup — ถ้าเบราว์เซอร์ไม่ยอม (pointer หายไปแล้ว ฯลฯ) ก็แค่ไม่ capture\n try { (e.currentTarget as Element).setPointerCapture?.(e.pointerId); } catch { /* ignore */ }\n this.pick(e);\n }\n\n onPointerMove(e: PointerEvent): void {\n if (this.dragging) this.pick(e);\n }\n\n onPointerUp(e: PointerEvent): void {\n if (!this.dragging) return;\n this.dragging = false;\n this.pick(e);\n if (this.view() === 'hour') {\n this.view.set('minute');\n } else {\n this.onPick(`${pad(this.hour())}:${pad(this.minute())}`);\n }\n }\n\n // แปลงตำแหน่งนิ้ว/เมาส์เป็นค่า: มุมจาก 12 นาฬิกา → ช่อง, ระยะจากจุดกลาง → วงใน/วงนอก (เฉพาะชั่วโมง)\n private pick(e: PointerEvent): void {\n const svg = e.currentTarget as SVGSVGElement;\n const rect = svg.getBoundingClientRect();\n const scale = SIZE / rect.width;\n const dx = (e.clientX - rect.left) * scale - CENTER;\n const dy = (e.clientY - rect.top) * scale - CENTER;\n let angle = Math.atan2(dx, -dy);\n if (angle < 0) angle += 2 * Math.PI;\n\n if (this.view() === 'hour') {\n const index = Math.round(angle / (Math.PI / 6)) % 12;\n const inner = Math.hypot(dx, dy) < (OUTER_R + INNER_R) / 2;\n this.hour.set(inner ? (index === 0 ? 12 : index) : (index === 0 ? 0 : index + 12));\n } else {\n this.minute.set((Math.round(angle / (Math.PI / 6)) % 12) * 5);\n }\n }\n}\n","import { ApplicationRef, Directive, ElementRef, EnvironmentInjector, NgZone, OnDestroy, forwardRef, inject } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { AnchoredPopup } from '../utils/anchored-popup';\nimport { ClockPickerPopupComponent } from './clockpicker-popup.component';\nimport { ensurePickerStyles } from './picker-styles';\n\n/**\n * ช่องเลือกเวลา ใช้กับ <input type=\"text\" appClockPicker formControlName=\"...\"> — ค่าเป็น \"HH:mm\"\n * คลิกช่อง = เปิดหน้าปัด (ClockPickerPopupComponent) ห้ามพิมพ์เอง เหมือนของเดิม (Leaves_Detail.aspx บรรทัด 143-155)\n *\n * เดิมห่อปลั๊กอิน jQuery clockpicker ซึ่งเป็นเหตุผลเดียวที่ทั้งแอปต้องโหลด jQuery ใน <head> ทุกหน้า\n * จึงเขียนเองแทน — ค่าที่เข้า/ออกเหมือนเดิม หน้าที่ใช้อยู่ไม่ต้องแก้\n */\n@Directive({\n selector: '[appClockPicker]',\n standalone: true,\n host: {\n class: 'app-clockpicker',\n readonly: '',\n autocomplete: 'off',\n '(click)': 'open()',\n '(keydown)': 'onKeydown($event)',\n '(blur)': 'onTouched()',\n },\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => ClockPickerDirective),\n multi: true\n }\n ]\n})\nexport class ClockPickerDirective implements OnDestroy, ControlValueAccessor {\n private el = inject<ElementRef<HTMLInputElement>>(ElementRef);\n private popup = new AnchoredPopup(\n this.el.nativeElement, ClockPickerPopupComponent,\n inject(ApplicationRef), inject(EnvironmentInjector), inject(NgZone));\n\n private disabled = false;\n\n private onChange: (val: string) => void = () => {};\n onTouched: () => void = () => {};\n\n constructor() {\n ensurePickerStyles();\n }\n\n open(): void {\n if (this.disabled) return;\n this.popup.open(p => {\n p.init(this.el.nativeElement.value);\n p.onPick = (time) => this.select(time);\n });\n }\n\n close(): void {\n this.popup.close();\n }\n\n onKeydown(e: KeyboardEvent): void {\n if (e.key === 'Escape' || e.key === 'Esc' || e.key === 'Tab') {\n this.close();\n return;\n }\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault();\n this.open();\n return;\n }\n // ห้ามพิมพ์เอง ต้องเลือกจากหน้าปัดเท่านั้น (เหมือนเดิม) — คีย์ผสม เช่น Ctrl+C ปล่อยผ่าน\n if (!e.ctrlKey && !e.metaKey) e.preventDefault();\n }\n\n ngOnDestroy(): void {\n this.close();\n }\n\n writeValue(val: string): void {\n this.el.nativeElement.value = val || '';\n this.popup.instance?.init(val || '');\n }\n\n registerOnChange(fn: (val: string) => void): void { this.onChange = fn; }\n registerOnTouched(fn: () => void): void { this.onTouched = fn; }\n\n setDisabledState(isDisabled: boolean): void {\n this.disabled = isDisabled;\n this.el.nativeElement.disabled = isDisabled;\n if (isDisabled) this.close();\n }\n\n private select(time: string): void {\n this.el.nativeElement.value = time;\n this.close();\n this.onChange(time);\n this.onTouched();\n }\n}\n","import { Component, computed, forwardRef, signal } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\n// ช่องเลือกวันที่แบบไทย (แสดง พ.ศ.) สำหรับหน้าจอมือถือ — เป็นปุ่ม กดแล้วเปิดปฏิทินแบบ bottom sheet เต็มความกว้างจอ\n// (บนจอใหญ่/ช่องที่เป็น <input> ใช้ appDatePicker แทน)\n//\n// สัญญาของค่าเหมือน appDatePicker: ค่าใน form control เป็น \"ค.ศ. yyyy-MM-dd\" เสมอ — ตรงกับที่ API ต้องการ ส่วน พ.ศ. เป็นเรื่องการแสดงผลล้วน ๆ\n// (writeValue ยังรับ พ.ศ. เข้ามาได้ เผื่อค่าที่ส่งมาจากที่อื่นเป็น พ.ศ.)\n@Component({\n selector: 'app-thai-date-picker',\n standalone: true,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => ThaiDatePickerComponent),\n multi: true\n }\n ],\n templateUrl: './thai-date-picker.component.html'\n})\nexport class ThaiDatePickerComponent implements ControlValueAccessor {\n private static readonly monthsFull = [\n 'มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน',\n 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'\n ];\n private static readonly monthsShort = [\n 'ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.',\n 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'\n ];\n\n readonly weekdays = ['อา', 'จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส'];\n\n value = signal<string>('');\n isOpen = signal<boolean>(false);\n isDisabled = signal<boolean>(false);\n\n // เดือน/ปี (ค.ศ.) ที่ปฏิทินกำลังเปิดดูอยู่ — แยกจากค่าที่เลือกไว้ เพราะเลื่อนดูเดือนอื่นได้โดยยังไม่เลือก\n viewYear = signal<number>(new Date().getFullYear());\n viewMonth = signal<number>(new Date().getMonth());\n\n private onChange: (val: string) => void = () => { };\n private onTouched: () => void = () => { };\n\n displayText = computed(() => {\n const parsed = this.parse(this.value());\n if (!parsed) return 'เลือกวันที่';\n return `${parsed.day} ${ThaiDatePickerComponent.monthsShort[parsed.month]} ${parsed.year + 543}`;\n });\n\n headerText = computed(() =>\n `${ThaiDatePickerComponent.monthsFull[this.viewMonth()]} ${this.viewYear() + 543}`\n );\n\n // ตารางวันของเดือนที่เปิดดูอยู่ — ช่องว่าง (null) เติมหน้า/หลังให้ครบสัปดาห์\n weeks = computed<(number | null)[][]>(() => {\n const year = this.viewYear();\n const month = this.viewMonth();\n const firstWeekday = new Date(year, month, 1).getDay();\n const daysInMonth = new Date(year, month + 1, 0).getDate();\n\n const cells: (number | null)[] = Array(firstWeekday).fill(null);\n for (let d = 1; d <= daysInMonth; d++) cells.push(d);\n while (cells.length % 7 !== 0) cells.push(null);\n\n const rows: (number | null)[][] = [];\n for (let i = 0; i < cells.length; i += 7) rows.push(cells.slice(i, i + 7));\n return rows;\n });\n\n open() {\n if (this.isDisabled()) return;\n const parsed = this.parse(this.value()) || this.todayParts();\n this.viewYear.set(parsed.year);\n this.viewMonth.set(parsed.month);\n this.isOpen.set(true);\n }\n\n close() {\n this.isOpen.set(false);\n this.onTouched();\n }\n\n shiftMonth(delta: number) {\n const total = this.viewYear() * 12 + this.viewMonth() + delta;\n this.viewYear.set(Math.floor(total / 12));\n this.viewMonth.set(((total % 12) + 12) % 12);\n }\n\n shiftYear(delta: number) {\n this.viewYear.set(this.viewYear() + delta);\n }\n\n selectDay(day: number | null) {\n if (day === null) return;\n this.commit(this.format(this.viewYear(), this.viewMonth(), day));\n }\n\n selectToday() {\n const t = this.todayParts();\n this.viewYear.set(t.year);\n this.viewMonth.set(t.month);\n this.commit(this.format(t.year, t.month, t.day));\n }\n\n isSelected(day: number | null): boolean {\n if (day === null) return false;\n return this.value() === this.format(this.viewYear(), this.viewMonth(), day);\n }\n\n isToday(day: number | null): boolean {\n if (day === null) return false;\n const t = this.todayParts();\n return t.year === this.viewYear() && t.month === this.viewMonth() && t.day === day;\n }\n\n private commit(ceDate: string) {\n this.value.set(ceDate);\n this.onChange(ceDate);\n this.onTouched();\n this.isOpen.set(false);\n }\n\n private todayParts() {\n const d = new Date();\n return { year: d.getFullYear(), month: d.getMonth(), day: d.getDate() };\n }\n\n private format(year: number, month: number, day: number): string {\n return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;\n }\n\n // รับได้ทั้ง ค.ศ. และ พ.ศ. (ปี >= 2400 ถือเป็น พ.ศ.) คืนเป็นส่วนประกอบ ค.ศ. เสมอ\n private parse(val: string): { year: number; month: number; day: number } | null {\n if (!val) return null;\n const parts = val.substring(0, 10).split('-');\n if (parts.length !== 3) return null;\n\n let year = Number(parts[0]);\n const month = Number(parts[1]) - 1;\n const day = Number(parts[2]);\n if (!year || Number.isNaN(month) || !day) return null;\n if (year >= 2400) year -= 543;\n\n return { year, month, day };\n }\n\n writeValue(val: string): void {\n const parsed = this.parse(val);\n this.value.set(parsed ? this.format(parsed.year, parsed.month, parsed.day) : '');\n }\n\n registerOnChange(fn: (val: string) => void): void { this.onChange = fn; }\n registerOnTouched(fn: () => void): void { this.onTouched = fn; }\n setDisabledState(isDisabled: boolean): void { this.isDisabled.set(isDisabled); }\n}\n","<button type=\"button\" class=\"tdp-field\" [class.tdp-empty]=\"!value()\" [disabled]=\"isDisabled()\" (click)=\"open()\">\n {{ displayText() }}\n</button>\n\n@if (isOpen()) {\n<div class=\"tdp-overlay\" (click)=\"close()\">\n <div class=\"tdp-sheet\" (click)=\"$event.stopPropagation()\">\n\n <div class=\"tdp-header\">\n <button type=\"button\" class=\"tdp-nav\" (click)=\"shiftYear(-1)\" aria-label=\"ปีก่อนหน้า\">«</button>\n <button type=\"button\" class=\"tdp-nav\" (click)=\"shiftMonth(-1)\" aria-label=\"เดือนก่อนหน้า\">‹</button>\n <div class=\"tdp-title\">{{ headerText() }}</div>\n <button type=\"button\" class=\"tdp-nav\" (click)=\"shiftMonth(1)\" aria-label=\"เดือนถัดไป\">›</button>\n <button type=\"button\" class=\"tdp-nav\" (click)=\"shiftYear(1)\" aria-label=\"ปีถัดไป\">»</button>\n </div>\n\n <div class=\"tdp-weekdays\">\n @for (w of weekdays; track w) {\n <div class=\"tdp-weekday\">{{ w }}</div>\n }\n </div>\n\n @for (week of weeks(); track $index) {\n <div class=\"tdp-week\">\n @for (day of week; track $index) {\n <button type=\"button\" class=\"tdp-day\" [class.tdp-blank]=\"day === null\" [class.tdp-selected]=\"isSelected(day)\"\n [class.tdp-today]=\"isToday(day)\" [disabled]=\"day === null\" (click)=\"selectDay(day)\">\n {{ day }}\n </button>\n }\n </div>\n }\n\n <div class=\"tdp-actions\">\n <button type=\"button\" class=\"tdp-btn tdp-btn-today\" (click)=\"selectToday()\">วันนี้</button>\n <button type=\"button\" class=\"tdp-btn tdp-btn-close\" (click)=\"close()\">ปิด</button>\n </div>\n </div>\n</div>\n}\n\n<style>\n .tdp-field {\n width: 100%;\n height: 40px;\n border: 1px solid #ddd;\n border-radius: 8px;\n background: #fff;\n color: #333;\n font-size: 0.9rem;\n text-align: left;\n padding: 0 10px;\n }\n\n .tdp-field.tdp-empty {\n color: #999;\n }\n\n .tdp-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 150;\n }\n\n .tdp-sheet {\n width: 100%;\n max-width: 480px;\n background: #fff;\n border-radius: 16px 16px 0 0;\n padding: 14px 12px 18px;\n }\n\n .tdp-header {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-bottom: 10px;\n }\n\n .tdp-title {\n flex: 1;\n text-align: center;\n font-size: 0.98rem;\n font-weight: 600;\n color: #852991;\n }\n\n /* ปุ่มเลื่อนเดือน/ปี ต้องกว้างพอให้นิ้วแตะโดนบนจอเล็ก (44px ตามแนวทาง touch target) */\n .tdp-nav {\n width: 38px;\n height: 38px;\n flex-shrink: 0;\n border: 1px solid #eee;\n border-radius: 8px;\n background: #fff;\n color: #852991;\n font-size: 1rem;\n line-height: 1;\n }\n\n .tdp-weekdays,\n .tdp-week {\n display: grid;\n grid-template-columns: repeat(7, 1fr);\n gap: 2px;\n }\n\n .tdp-weekday {\n text-align: center;\n font-size: 0.72rem;\n color: #888;\n padding: 4px 0;\n }\n\n .tdp-day {\n height: 42px;\n border: none;\n border-radius: 8px;\n background: #fff;\n color: #333;\n font-size: 0.9rem;\n }\n\n .tdp-day.tdp-blank {\n background: transparent;\n }\n\n .tdp-day.tdp-today {\n border: 1px solid #852991;\n color: #852991;\n font-weight: 600;\n }\n\n .tdp-day.tdp-selected {\n background: #852991;\n color: #fff;\n font-weight: 600;\n }\n\n .tdp-actions {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n\n .tdp-btn {\n flex: 1;\n height: 42px;\n border-radius: 8px;\n font-size: 0.92rem;\n font-weight: 600;\n }\n\n .tdp-btn-today {\n border: 1px solid #852991;\n background: #fff;\n color: #852991;\n }\n\n .tdp-btn-close {\n border: none;\n background: #852991;\n color: #fff;\n }\n</style>\n"],"mappings":";;;AAUA,IAAa,gBAAb,MAA0B;CAWd;CACA;CACA;CACA;CACA;CAdF,MAA8B;CAErB,kBAAkB,MAAY;EAC7C,MAAM,SAAS,EAAE;EACjB,IAAI,KAAK,OAAO,SAAS,MAAM,KAAK,KAAK,KAAK,SAAS,cAAc,SAAS,MAAM,GAAG;EACvF,KAAK,KAAK,UAAU,KAAK,MAAK,CAAE;CAClC;CACiB,0BAA0B,KAAK,SAAQ;CAExD,YACU,QACA,WACA,QACA,UACA,MAAY;EAJZ,KAAA,SAAA;EACA,KAAA,YAAA;EACA,KAAA,SAAA;EACA,KAAA,WAAA;EACA,KAAA,OAAA;CACP;CAEH,IAAI,SAAM;EACR,OAAO,KAAK,QAAQ;CACtB;CAEA,IAAI,WAAQ;EACV,OAAO,KAAK,KAAK,YAAY;CAC/B;CAGA,KAAK,OAA4B;EAC/B,IAAI,KAAK,KAAK;EAEd,MAAM,MAAM,gBAAgB,KAAK,WAAW,EAAE,qBAAqB,KAAK,SAAQ,CAAE;EAClF,MAAM,IAAI,QAAQ;EAClB,KAAK,OAAO,WAAW,IAAI,QAAQ;EACnC,SAAS,KAAK,YAAY,IAAI,SAAS,aAAa;EACpD,IAAI,kBAAkB,cAAa;EACnC,KAAK,MAAM;EACX,KAAK,SAAQ;EAEb,KAAK,KAAK,wBAAuB;GAC/B,SAAS,iBAAiB,aAAa,KAAK,gBAAgB,IAAI;GAChE,SAAS,iBAAiB,cAAc,KAAK,gBAAgB,IAAI;GACjE,OAAO,iBAAiB,UAAU,KAAK,mBAAmB,IAAI;GAC9D,OAAO,iBAAiB,UAAU,KAAK,iBAAiB;EAC1D,CAAC;CACH;CAEA,QAAK;EACH,IAAI,CAAC,KAAK,KAAK;EACf,SAAS,oBAAoB,aAAa,KAAK,gBAAgB,IAAI;EACnE,SAAS,oBAAoB,cAAc,KAAK,gBAAgB,IAAI;EACpE,OAAO,oBAAoB,UAAU,KAAK,mBAAmB,IAAI;EACjE,OAAO,oBAAoB,UAAU,KAAK,iBAAiB;EAC3D,KAAK,OAAO,WAAW,KAAK,IAAI,QAAQ;EACxC,KAAK,IAAI,QAAO;EAChB,KAAK,MAAM;CACb;CAEA,WAAQ;EACN,MAAM,QAAQ,KAAK,KAAK,SAAS;EACjC,IAAI,CAAC,OAAO;EACZ,MAAM,OAAO,KAAK,OAAO,sBAAqB;EAC9C,MAAM,IAAI,MAAM;EAChB,MAAM,IAAI,MAAM;EAChB,MAAM,MAAM;EACZ,MAAM,SAAS;EAEf,IAAI,MAAM,KAAK,SAAS;EACxB,IAAI,MAAM,IAAI,OAAO,cAAc,UAAU,KAAK,MAAM,MAAM,KAAK,QACjE,MAAM,KAAK,MAAM,MAAM;EAEzB,MAAM,OAAO,KAAK,IAAI,QAAQ,KAAK,IAAI,KAAK,MAAM,OAAO,aAAa,IAAI,MAAM,CAAC;EAEjF,MAAM,MAAM,MAAM,GAAG,IAAG;EACxB,MAAM,MAAM,OAAO,GAAG,KAAI;CAC5B;AACD;ACjFD,MAAM,YAAY;AAElB,MAAM,eAAe;AAErB,MAAM,eAAe;AAGrB,SAAgB,eAAe,KAAY;CACzC,MAAM,OAAO,QAAQ,QAAQ,QAAQ,SAAY,KAAK,OAAO,GAAG;CAChE,MAAM,IAAI,aAAa,KAAK,IAAI;CAChC,IAAI,CAAC,GAAG,OAAO;CACf,MAAM,OAAO,SAAS,EAAE,IAAI,EAAE;CAC9B,OAAO,OAAO,eAAe,OAAO,GAAG,OAAO,YAAY,EAAE;AAC9D;AAGA,SAAgB,gBAAgB,KAAY;CAC1C,MAAM,OAAO,QAAQ,QAAQ,QAAQ,SAAY,KAAK,OAAO,GAAG;CAChE,MAAM,IAAI,aAAa,KAAK,IAAI;CAChC,IAAI,CAAC,GAAG,OAAO;CACf,MAAM,OAAO,SAAS,EAAE,IAAI,EAAE;CAC9B,OAAO,OAAO,eAAe,GAAG,OAAO,YAAY,EAAE,OAAO;AAC9D;AAGA,SAAgB,UAAU,KAAY;CACpC,IAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,IAAI,OAAO;CACvD,MAAM,QAAQ,IAAI,UAAU,GAAG,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAI,MAAK,SAAS,GAAG,EAAE,CAAC;CACtE,IAAI,MAAM,WAAW,KAAK,MAAM,KAAK,KAAK,GAAG,OAAO;CACpD,IAAI,CAAC,MAAM,OAAO,OAAO;CAEzB,IAAI,OAAO,cAAc,QAAQ;CACjC,OAAO;EAAE;EAAM,OAAO,QAAQ;EAAG;CAAG;AACtC;AAGA,SAAgB,WAAW,GAAc;CACvC,OAAO,GAAG,EAAE,KAAI,GAAI,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,EAAC,GAAI,OAAO,EAAE,GAAG,CAAC,CAAC,SAAS,GAAG,GAAG;AAC3F;ACiEA,IAAa,2BAAb,MAAa,yBAAwB;CAC1B,WAAW;EAAC;EAAM;EAAK;EAAK;EAAK;EAAM;EAAK;CAAG;CAC/C,cAAc;EAAC;EAAQ;EAAQ;EAAS;EAAS;EAAQ;EAAS;EAAQ;EAAQ;EAAQ;EAAQ;EAAQ;CAAM;CACxG,aAAa;EAAC;EAAU;EAAc;EAAU;EAAU;EAAW;EACpF;EAAW;EAAW;EAAW;EAAU;EAAa;CAAS;CAGnE,WAA+B;CAE/B,eAA2C,CAAE;CAE7C,OAAO,OAAqB,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAClC,WAAW,wBAAO,IAAI,KAAI,EAAE,CAAC,YAAW,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAC3C,YAAY,wBAAO,IAAI,KAAI,EAAE,CAAC,SAAQ,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGzC,KAAK,UAA4B;EAC/B,KAAK,WAAW;EAChB,MAAM,OAAO,YAAY,KAAK,WAAU;EACxC,KAAK,SAAS,IAAI,KAAK,IAAI;EAC3B,KAAK,UAAU,IAAI,KAAK,KAAK;EAC7B,KAAK,KAAK,IAAI,KAAK;CACrB;CAEA,QAAQ,eAAc;EACpB,MAAM,IAAI,KAAK,SAAQ;EACvB,QAAQ,KAAK,KAAI,GAAjB;GACE,KAAK,OAAO,OAAO,GAAG,KAAK,WAAW,KAAK,UAAS,GAAG,GAAI,IAAI;GAC/D,KAAK,SAAS,OAAO,GAAG,IAAI;GAC5B,SAAS;IACP,MAAM,QAAQ,KAAK,YAAY,CAAC;IAChC,OAAO,GAAG,QAAQ,IAAG,KAAM,QAAQ,IAAI;GACzC;EACF;CACF,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGF,WAAW,eAAiC;EAC1C,MAAM,IAAI,KAAK,SAAQ;EACvB,MAAM,IAAI,KAAK,UAAS;EACxB,MAAM,QAA2B,MAAM,IAAI,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,OAAM,CAAE,CAAC,CAAC,KAAK,IAAI;EAC5E,MAAM,cAAc,IAAI,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,QAAO;EACjD,KAAK,IAAI,IAAI,GAAG,KAAK,aAAa,KAAK,MAAM,KAAK,CAAC;EACnD,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAGF,YAAY,eAAwB;EAClC,MAAM,QAAQ,KAAK,YAAY,KAAK,SAAQ,CAAE;EAC9C,OAAO,MAAM,KAAK,EAAE,QAAQ,GAAE,IAAK,GAAG,MAAM,QAAQ,IAAI,CAAC;CAC3D,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,MAAM,OAAa;EACjB,IAAI,KAAK,KAAI,MAAO,OAAO;GACzB,MAAM,QAAQ,KAAK,SAAQ,IAAK,KAAK,KAAK,UAAS,IAAK;GACxD,KAAK,SAAS,IAAI,KAAK,MAAM,QAAQ,EAAE,CAAC;GACxC,KAAK,UAAU,KAAM,QAAQ,KAAM,MAAM,EAAE;EAC7C,OACE,KAAK,SAAS,QAAO,MAAK,IAAI,SAAS,KAAK,KAAI,MAAO,UAAU,IAAI,GAAG;CAE5E;CAEA,UAAO;EACL,KAAK,KAAK,IAAI,KAAK,KAAI,MAAO,QAAQ,UAAU,MAAM;CACxD;CAEA,SAAS,MAAY;EACnB,KAAK,SAAS,IAAI,IAAI;EACtB,KAAK,KAAK,IAAI,OAAO;CACvB;CAEA,UAAU,OAAa;EACrB,KAAK,UAAU,IAAI,KAAK;EACxB,KAAK,KAAK,IAAI,KAAK;CACrB;CAEA,QAAQ,KAAW;EACjB,KAAK,OAAO;GAAE,MAAM,KAAK,SAAQ;GAAI,OAAO,KAAK,UAAS;GAAI;EAAG,CAAE;CACrE;CAEA,YAAS;EACP,KAAK,OAAO,KAAK,WAAU,CAAE;CAC/B;CAEA,cAAc,KAAW;EACvB,MAAM,IAAI,KAAK;EACf,OAAO,CAAC,CAAC,KAAK,EAAE,SAAS,KAAK,SAAQ,KAAM,EAAE,UAAU,KAAK,UAAS,KAAM,EAAE,QAAQ;CACxF;CAEA,QAAQ,KAAW;EACjB,MAAM,IAAI,KAAK,WAAU;EACzB,OAAO,EAAE,SAAS,KAAK,SAAQ,KAAM,EAAE,UAAU,KAAK,UAAS,KAAM,EAAE,QAAQ;CACjF;CAEQ,YAAY,MAAY;EAC9B,OAAO,KAAK,MAAM,OAAO,EAAE,IAAI;CACjC;CAEQ,aAAU;EAChB,MAAM,oBAAI,IAAI,KAAI;EAClB,OAAO;GAAE,MAAM,EAAE,YAAW;GAAI,OAAO,EAAE,SAAQ;GAAI,KAAK,EAAE,QAAO;EAAE;CACvE;CArGW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAwB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAxB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAwB,cAAA;EAAA,UAAA;EAAA,MAAA;GAAA,WAAA,EAAA,aAAA,0BAAA;GAAA,gBAAA;EAAA;EAAA,UAAA;EAAA,UApFzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8CT,UAAA;EAAA,QAAA,CAAA,s0CAAA;CAAA,CAAA;;AAsCU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAwB,YAAA,CAAA;EA5FpC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAsB,YACpB;GAAI,MACV;IACJ,OAAO;IAEP,eAAe;GAChB;GAAA,UACS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CT,QAAA,CAAA,s0CAAA;EAAA,CAAA;;;ACnEH,MAAM,WAAW;AAUjB,MAAa,aAAa;;;;;;;;;;;;;AAe1B,SAAgB,qBAAkB;CAChC,MAAM,MAAM,OAAO,QAAQ;CAC3B,IAAI,CAAC,IAAI,QAAQ,IAAI,eAAe,QAAQ,GAAG;CAC/C,MAAM,QAAQ,IAAI,cAAc,OAAO;CACvC,MAAM,KAAK;CAEX,MAAM,QAAQ,OAAO,WAAW,EAAE,UAAU,KAAI,CAAE;CAClD,IAAI,OAAO,MAAM,aAAa,SAAS,KAAK;CAC5C,MAAM,cAAc;CACpB,IAAI,KAAK,YAAY,KAAK;AAC5B;ACDA,IAAa,sBAAb,MAAa,oBAAmB;CACtB,KAAK,OAAqC,UAAU;CACpD,QAAQ,IAAI,cAClB,KAAK,GAAG,eAAe,0BACvB,OAAO,cAAc,GAAG,OAAO,mBAAmB,GAAG,OAAO,MAAM,CAAC;CAE7D,eAAe;CACf,WAAW;CAEX,iBAAuC,CAAE;CACjD,kBAA6B,CAAE;CAE/B,cAAA;EACE,mBAAkB;EAGlB,KAAK,gBAAe;CACtB;CAEA,OAAI;EACF,IAAI,KAAK,UAAU;EACnB,KAAK,MAAM,MAAK,MAAI;GAClB,EAAE,KAAK,UAAU,KAAK,GAAG,cAAc,KAAK,CAAC;GAC7C,EAAE,UAAU,MAAM,KAAK,OAAO,CAAC;EACjC,CAAC;CACH;CAEA,QAAK;EACH,KAAK,MAAM,MAAK;CAClB;CAEA,UAAU,GAAgB;EACxB,IAAI,EAAE,QAAQ,YAAY,EAAE,QAAQ,SAAS,EAAE,QAAQ,OAAO;GAC5D,KAAK,MAAK;GACV;EACF;EACA,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,OAAO,EAAE,QAAQ,aAAa;GAC/D,EAAE,eAAc;GAChB,KAAK,KAAI;GACT;EACF;EAEA,IAAI,CAAC,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,eAAc;CAChD;CAEA,cAAW;EACT,KAAK,MAAK;EACV,KAAK,kBAAiB;CACxB;CAEA,WAAW,KAAW;EACpB,MAAM,SAAS,UAAU,GAAG;EAE5B,KAAK,GAAG,cAAc,QAAQ,SAAS,WAAW,MAAM,IAAI;EAC5D,KAAK,MAAM,UAAU,KAAK,MAAM;CAClC;CAEA,iBAAiB,IAAyB;EACxC,KAAK,WAAW;CAClB;CACA,kBAAkB,IAAc;EAC9B,KAAK,YAAY;CACnB;CAEA,iBAAiB,YAAmB;EAClC,KAAK,WAAW;EAChB,KAAK,GAAG,cAAc,WAAW;EACjC,IAAI,YAAY,KAAK,MAAK;CAC5B;CAEQ,OAAO,GAAc;EAC3B,MAAM,SAAS,WAAW,CAAC;EAC3B,KAAK,GAAG,cAAc,QAAQ;EAC9B,KAAK,MAAK;EACV,KAAK,SAAS,MAAM;EACpB,KAAK,UAAS;CAChB;CAQQ,kBAAe;EACrB,MAAM,QAAQ,KAAK,GAAG;EACtB,IAAI,CAAC,SAAS,KAAK,cAAc;EAEjC,MAAM,OAAO,OAAO,yBAAyB,iBAAiB,WAAW,OAAO;EAChF,MAAM,YAAY,MAAM;EACxB,MAAM,YAAY,MAAM;EACxB,IAAI,CAAC,aAAa,CAAC,WAAW;EAE9B,OAAO,eAAe,OAAO,SAAS;GACpC,cAAc;GACd,YAAY;GACZ,WAAW,gBAAgB,UAAU,KAAK,KAAK,CAAC;GAChD,MAAM,MAAe,UAAU,KAAK,OAAO,eAAe,CAAC,CAAC;EAC7D,CAAA;EACD,KAAK,eAAe;CACtB;CAGQ,oBAAiB;EACvB,IAAI,CAAC,KAAK,cAAc;EACxB,KAAK,eAAe;EACpB,MAAM,QAAQ,KAAK,GAAG;EACtB,IAAI,OAAO,OAAQ,MAAc;CACnC;CA5GW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAmB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAnB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAmB,cAAA;EAAA,UAAA;EAAA,MAAA;GAAA,YAAA;IAAA,YAAA;IAAA,gBAAA;IAAA,eAAA;GAAA;GAAA,WAAA;IAAA,SAAA;IAAA,WAAA;IAAA,QAAA;GAAA;GAAA,gBAAA;EAAA;EAAA,WARnB,CACT;GACE,SAAS;GACT,aAAa,iBAAiB,mBAAmB;GACjD,OAAO;EACR,CACF;EAAA,UAAA;CAAA,CAAA;;AAEU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAmB,YAAA,CAAA;EApB/B,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,YAAY;GACZ,MAAM;IACJ,OAAO;IACP,UAAU;IACV,cAAc;IACd,aAAa;IACb,WAAW;IACX,aAAa;IACb,UAAU;GACX;GACD,WAAW,CACT;IACE,SAAS;IACT,aAAa,iBAAgB,mBAAoB;IACjD,OAAO;GACR,CACF;EACF,CAAA;;;;AC9BD,MAAM,OAAO;AACb,MAAM,SAAS,OAAO;AACtB,MAAM,UAAU;AAChB,MAAM,UAAU;AAChB,MAAM,SAAS;AAKf,SAAS,QAAQ,OAAe,QAAc;CAC5C,MAAM,IAAK,QAAQ,KAAK,KAAM;CAC9B,OAAO;EAAE,GAAG,SAAS,SAAS,KAAK,IAAI,CAAC;EAAG,GAAG,SAAS,SAAS,KAAK,IAAI,CAAC;CAAC;AAC7E;AAEA,MAAM,OAAO,MAAc,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;AAoEpD,IAAa,4BAAb,MAAa,0BAAyB;CAC3B,OAAO;CACP,SAAS;CACT,QAAQ;CACR,MAAM;CAGf,eAAsC,CAAE;CAExC,OAAO,OAAkB,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAChC,OAAO,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAChB,SAAS,OAAO,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CACnB,WAAW;CAGX,KAAK,OAAa;EAChB,MAAM,IAAI,qBAAqB,KAAK,SAAS,EAAE;EAC/C,MAAM,sBAAM,IAAI,KAAI;EACpB,MAAM,IAAI,IAAI,OAAO,EAAE,EAAE,IAAI,IAAI,SAAQ;EACzC,MAAM,MAAM,IAAI,OAAO,EAAE,EAAE,IAAI,IAAI,WAAU;EAC7C,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,IAAI,CAAC;EACtC,KAAK,OAAO,IAAI,OAAO,KAAK,MAAM,KAAK,MAAM,CAAC;EAC9C,KAAK,KAAK,IAAI,MAAM;CACtB;CAEA,QAAQ,eAAsB;EAC5B,IAAI,KAAK,KAAI,MAAO,UAClB,OAAO,MAAM,KAAK,EAAE,QAAQ,GAAE,IAAK,GAAG,OAAO;GAAE,OAAO,IAAI,IAAI,CAAC;GAAG,OAAO,IAAI;GAAG,GAAG,QAAQ,GAAG,OAAO;EAAC,EAAG;EAE3G,MAAM,QAAQ,MAAM,KAAK,EAAE,QAAQ,GAAE,IAAK,GAAG,MAAK;GAChD,MAAM,QAAQ,MAAM,IAAI,IAAI,IAAI;GAChC,OAAO;IAAE,OAAO,IAAI,KAAK;IAAG;IAAO,GAAG,QAAQ,GAAG,OAAO;GAAC;EAC3D,CAAC;EACD,MAAM,QAAQ,MAAM,KAAK,EAAE,QAAQ,GAAE,IAAK,GAAG,MAAK;GAChD,MAAM,QAAQ,MAAM,IAAI,KAAK;GAC7B,OAAO;IAAE,OAAO,OAAO,KAAK;IAAG;IAAO,GAAG,QAAQ,GAAG,OAAO;GAAC;EAC9D,CAAC;EACD,OAAO,CAAC,GAAG,OAAO,GAAG,KAAK;CAC5B,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,OAAO,eAAc;EACnB,IAAI,KAAK,KAAI,MAAO,UAElB,OAAO,QAAQ,KAAK,OAAM,IAAK,GAAG,OAAO;EAE3C,MAAM,IAAI,KAAK,KAAI;EACnB,OAAO,QAAQ,IAAI,IAAI,KAAK,KAAK,KAAK,KAAK,UAAU,OAAO;CAC9D,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,cAAc,GAAe;EAC3B,KAAK,WAAW;EAEhB,IAAI;GAAG,EAAE,cAA0B,oBAAoB,EAAE,SAAS;EAAG,QAAQ,CAAe;EAC5F,KAAK,KAAK,CAAC;CACb;CAEA,cAAc,GAAe;EAC3B,IAAI,KAAK,UAAU,KAAK,KAAK,CAAC;CAChC;CAEA,YAAY,GAAe;EACzB,IAAI,CAAC,KAAK,UAAU;EACpB,KAAK,WAAW;EAChB,KAAK,KAAK,CAAC;EACX,IAAI,KAAK,KAAI,MAAO,QAClB,KAAK,KAAK,IAAI,QAAQ;OAEtB,KAAK,OAAO,GAAG,IAAI,KAAK,KAAI,CAAE,EAAC,GAAI,IAAI,KAAK,OAAM,CAAE,GAAG;CAE3D;CAGQ,KAAK,GAAe;EAE1B,MAAM,OADM,EAAE,cACG,sBAAqB;EACtC,MAAM,QAAQ,OAAO,KAAK;EAC1B,MAAM,MAAM,EAAE,UAAU,KAAK,QAAQ,QAAQ;EAC7C,MAAM,MAAM,EAAE,UAAU,KAAK,OAAO,QAAQ;EAC5C,IAAI,QAAQ,KAAK,MAAM,IAAI,CAAC,EAAE;EAC9B,IAAI,QAAQ,GAAG,SAAS,IAAI,KAAK;EAEjC,IAAI,KAAK,KAAI,MAAO,QAAQ;GAC1B,MAAM,QAAQ,KAAK,MAAM,SAAS,KAAK,KAAK,EAAE,IAAI;GAClD,MAAM,QAAQ,KAAK,MAAM,IAAI,EAAE,IAAA;GAC/B,KAAK,KAAK,IAAI,QAAS,UAAU,IAAI,KAAK,QAAU,UAAU,IAAI,IAAI,QAAQ,EAAG;EACnF,OACE,KAAK,OAAO,IAAK,KAAK,MAAM,SAAS,KAAK,KAAK,EAAE,IAAI,KAAM,CAAC;CAEhE;CAxFW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAyB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAzB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAyB,cAAA;EAAA,UAAA;EAAA,MAAA;GAAA,WAAA,EAAA,aAAA,0BAAA;GAAA,gBAAA;EAAA;EAAA,UAAA;EAAA,UAnD1B;;;;;;;;;;;;;;;;;;;;;EAoBT,UAAA;EAAA,QAAA,CAAA,m6BAAA;CAAA,CAAA;;AA+BU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAyB,YAAA,CAAA;EA3DrC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAuB,YACrB;GAAI,MACV;IACJ,OAAO;IAEP,eAAe;GAChB;GAAA,UACS;;;;;;;;;;;;;;;;;;;;;GAoBT,QAAA,CAAA,m6BAAA;EAAA,CAAA;;;ACxBH,IAAa,uBAAb,MAAa,qBAAoB;CACvB,KAAK,OAAqC,UAAU;CACpD,QAAQ,IAAI,cAClB,KAAK,GAAG,eAAe,2BACvB,OAAO,cAAc,GAAG,OAAO,mBAAmB,GAAG,OAAO,MAAM,CAAC;CAE7D,WAAW;CAEX,iBAAuC,CAAE;CACjD,kBAA6B,CAAE;CAE/B,cAAA;EACE,mBAAkB;CACpB;CAEA,OAAI;EACF,IAAI,KAAK,UAAU;EACnB,KAAK,MAAM,MAAK,MAAI;GAClB,EAAE,KAAK,KAAK,GAAG,cAAc,KAAK;GAClC,EAAE,UAAU,SAAS,KAAK,OAAO,IAAI;EACvC,CAAC;CACH;CAEA,QAAK;EACH,KAAK,MAAM,MAAK;CAClB;CAEA,UAAU,GAAgB;EACxB,IAAI,EAAE,QAAQ,YAAY,EAAE,QAAQ,SAAS,EAAE,QAAQ,OAAO;GAC5D,KAAK,MAAK;GACV;EACF;EACA,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,OAAO,EAAE,QAAQ,aAAa;GAC/D,EAAE,eAAc;GAChB,KAAK,KAAI;GACT;EACF;EAEA,IAAI,CAAC,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,eAAc;CAChD;CAEA,cAAW;EACT,KAAK,MAAK;CACZ;CAEA,WAAW,KAAW;EACpB,KAAK,GAAG,cAAc,QAAQ,OAAO;EACrC,KAAK,MAAM,UAAU,KAAK,OAAO,EAAE;CACrC;CAEA,iBAAiB,IAAyB;EAAU,KAAK,WAAW;CAAI;CACxE,kBAAkB,IAAc;EAAU,KAAK,YAAY;CAAI;CAE/D,iBAAiB,YAAmB;EAClC,KAAK,WAAW;EAChB,KAAK,GAAG,cAAc,WAAW;EACjC,IAAI,YAAY,KAAK,MAAK;CAC5B;CAEQ,OAAO,MAAY;EACzB,KAAK,GAAG,cAAc,QAAQ;EAC9B,KAAK,MAAK;EACV,KAAK,SAAS,IAAI;EAClB,KAAK,UAAS;CAChB;CAhEW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAoB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAApB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAoB,cAAA;EAAA,UAAA;EAAA,MAAA;GAAA,YAAA;IAAA,YAAA;IAAA,gBAAA;GAAA;GAAA,WAAA;IAAA,SAAA;IAAA,WAAA;IAAA,QAAA;GAAA;GAAA,gBAAA;EAAA;EAAA,WARpB,CACT;GACE,SAAS;GACT,aAAa,iBAAiB,oBAAoB;GAClD,OAAO;EACR,CACF;EAAA,UAAA;CAAA,CAAA;;AAEU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EAnBhC,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,YAAY;GACZ,MAAM;IACJ,OAAO;IACP,UAAU;IACV,cAAc;IACd,WAAW;IACX,aAAa;IACb,UAAU;GACX;GACD,WAAW,CACT;IACE,SAAS;IACT,aAAa,iBAAgB,oBAAqB;IAClD,OAAO;GACR,CACF;EACF,CAAA;;;;ACXD,IAAa,0BAAb,MAAa,wBAAuB;CAC1B,OAAgB,aAAa;EACnC;EAAU;EAAc;EAAU;EAAU;EAAW;EACvD;EAAW;EAAW;EAAW;EAAU;EAAa;CACzD;CACO,OAAgB,cAAc;EACpC;EAAQ;EAAQ;EAAS;EAAS;EAAQ;EAC1C;EAAQ;EAAQ;EAAQ;EAAQ;EAAQ;CACzC;CAEQ,WAAW;EAAC;EAAM;EAAK;EAAK;EAAK;EAAM;EAAK;CAAG;CAExD,QAAQ,OAAe,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAC1B,SAAS,OAAgB,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAC/B,aAAa,OAAgB,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGnC,WAAW,wBAAe,IAAI,KAAI,EAAE,CAAC,YAAW,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CACnD,YAAY,wBAAe,IAAI,KAAI,EAAE,CAAC,SAAQ,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEzC,iBAAuC,CAAG;CAC1C,kBAA6B,CAAG;CAExC,cAAc,eAAc;EAC1B,MAAM,SAAS,KAAK,MAAM,KAAK,MAAK,CAAE;EACtC,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO,GAAG,OAAO,IAAG,GAAI,wBAAwB,YAAY,OAAO,OAAM,GAAI,OAAO,OAAO;CAC7F,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,aAAa,eACX,GAAG,wBAAwB,WAAW,KAAK,UAAS,GAAG,GAAI,KAAK,SAAQ,IAAK,OAC9E,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGD,QAAQ,eAAmC;EACzC,MAAM,OAAO,KAAK,SAAQ;EAC1B,MAAM,QAAQ,KAAK,UAAS;EAC5B,MAAM,eAAe,IAAI,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,OAAM;EACpD,MAAM,cAAc,IAAI,KAAK,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,QAAO;EAExD,MAAM,QAA2B,MAAM,YAAY,CAAC,CAAC,KAAK,IAAI;EAC9D,KAAK,IAAI,IAAI,GAAG,KAAK,aAAa,KAAK,MAAM,KAAK,CAAC;EACnD,OAAO,MAAM,SAAS,MAAM,GAAG,MAAM,KAAK,IAAI;EAE9C,MAAM,OAA4B,CAAA;EAClC,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG,KAAK,KAAK,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC;EACzE,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,OAAI;EACF,IAAI,KAAK,WAAU,GAAI;EACvB,MAAM,SAAS,KAAK,MAAM,KAAK,MAAK,CAAE,KAAK,KAAK,WAAU;EAC1D,KAAK,SAAS,IAAI,OAAO,IAAI;EAC7B,KAAK,UAAU,IAAI,OAAO,KAAK;EAC/B,KAAK,OAAO,IAAI,IAAI;CACtB;CAEA,QAAK;EACH,KAAK,OAAO,IAAI,KAAK;EACrB,KAAK,UAAS;CAChB;CAEA,WAAW,OAAa;EACtB,MAAM,QAAQ,KAAK,SAAQ,IAAK,KAAK,KAAK,UAAS,IAAK;EACxD,KAAK,SAAS,IAAI,KAAK,MAAM,QAAQ,EAAE,CAAC;EACxC,KAAK,UAAU,KAAM,QAAQ,KAAM,MAAM,EAAE;CAC7C;CAEA,UAAU,OAAa;EACrB,KAAK,SAAS,IAAI,KAAK,SAAQ,IAAK,KAAK;CAC3C;CAEA,UAAU,KAAkB;EAC1B,IAAI,QAAQ,MAAM;EAClB,KAAK,OAAO,KAAK,OAAO,KAAK,SAAQ,GAAI,KAAK,UAAS,GAAI,GAAG,CAAC;CACjE;CAEA,cAAW;EACT,MAAM,IAAI,KAAK,WAAU;EACzB,KAAK,SAAS,IAAI,EAAE,IAAI;EACxB,KAAK,UAAU,IAAI,EAAE,KAAK;EAC1B,KAAK,OAAO,KAAK,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC;CACjD;CAEA,WAAW,KAAkB;EAC3B,IAAI,QAAQ,MAAM,OAAO;EACzB,OAAO,KAAK,MAAK,MAAO,KAAK,OAAO,KAAK,SAAQ,GAAI,KAAK,UAAS,GAAI,GAAG;CAC5E;CAEA,QAAQ,KAAkB;EACxB,IAAI,QAAQ,MAAM,OAAO;EACzB,MAAM,IAAI,KAAK,WAAU;EACzB,OAAO,EAAE,SAAS,KAAK,SAAQ,KAAM,EAAE,UAAU,KAAK,UAAS,KAAM,EAAE,QAAQ;CACjF;CAEQ,OAAO,QAAc;EAC3B,KAAK,MAAM,IAAI,MAAM;EACrB,KAAK,SAAS,MAAM;EACpB,KAAK,UAAS;EACd,KAAK,OAAO,IAAI,KAAK;CACvB;CAEQ,aAAU;EAChB,MAAM,oBAAI,IAAI,KAAI;EAClB,OAAO;GAAE,MAAM,EAAE,YAAW;GAAI,OAAO,EAAE,SAAQ;GAAI,KAAK,EAAE,QAAO;EAAE;CACvE;CAEQ,OAAO,MAAc,OAAe,KAAW;EACrD,OAAO,GAAG,KAAI,GAAI,OAAO,QAAQ,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,EAAC,GAAI,OAAO,GAAG,CAAC,CAAC,SAAS,GAAG,GAAG;CACrF;CAGQ,MAAM,KAAW;EACvB,IAAI,CAAC,KAAK,OAAO;EACjB,MAAM,QAAQ,IAAI,UAAU,GAAG,EAAE,CAAC,CAAC,MAAM,GAAG;EAC5C,IAAI,MAAM,WAAW,GAAG,OAAO;EAE/B,IAAI,OAAO,OAAO,MAAM,EAAE;EAC1B,MAAM,QAAQ,OAAO,MAAM,EAAE,IAAI;EACjC,MAAM,MAAM,OAAO,MAAM,EAAE;EAC3B,IAAI,CAAC,QAAQ,OAAO,MAAM,KAAK,KAAK,CAAC,KAAK,OAAO;EACjD,IAAI,QAAQ,MAAM,QAAQ;EAE1B,OAAO;GAAE;GAAM;GAAO;EAAG;CAC3B;CAEA,WAAW,KAAW;EACpB,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,KAAK,MAAM,IAAI,SAAS,KAAK,OAAO,OAAO,MAAM,OAAO,OAAO,OAAO,GAAG,IAAI,EAAE;CACjF;CAEA,iBAAiB,IAAyB;EAAU,KAAK,WAAW;CAAI;CACxE,kBAAkB,IAAc;EAAU,KAAK,YAAY;CAAI;CAC/D,iBAAiB,YAAmB;EAAU,KAAK,WAAW,IAAI,UAAU;CAAG;CArIpE,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAuB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAvB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAuB,cAAA;EAAA,UAAA;EAAA,WATvB,CACT;GACE,SAAS;GACT,aAAa,iBAAiB,uBAAuB;GACrD,OAAO;EACR,CACF;EAAA,UAAA;EAAA,UCjBH;EAyKA,QAAA,CAAA,s3CAAA;CAAA,CAAA;;ADrJa,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAuB,YAAA,CAAA;EAZnC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAsB,YACpB;GAAI,WACL,CACT;IACE,SAAS;IACT,aAAa,iBAAgB,uBAAwB;IACrD,OAAO;GACR,CACF;GAAA,UAAA;EAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"kengcom202-kcore-ui.mjs","names":[],"sources":["../../../projects/ui/src/lib/utils/anchored-popup.ts","../../../projects/ui/src/lib/utils/thai-date.ts","../../../projects/ui/src/lib/pickers/datepicker-popup.component.ts","../../../projects/ui/src/lib/pickers/picker-styles.ts","../../../projects/ui/src/lib/pickers/datepicker.directive.ts","../../../projects/ui/src/lib/pickers/clockpicker-popup.component.ts","../../../projects/ui/src/lib/pickers/clockpicker.directive.ts","../../../projects/ui/src/lib/pickers/thai-date-picker.component.ts","../../../projects/ui/src/lib/pickers/thai-date-picker.component.html","../../../projects/ui/src/lib/search-select/search-select.component.ts","../../../projects/ui/src/lib/utils/thai-text.ts","../../../projects/ui/src/lib/auth/auth.config.ts","../../../projects/ui/src/lib/auth/auth.models.ts","../../../projects/ui/src/lib/auth/auth.service.ts","../../../projects/ui/src/lib/auth/auth.guards.ts","../../../projects/ui/src/lib/auth/auth-styles.ts","../../../projects/ui/src/lib/auth/password-toggle.component.ts","../../../projects/ui/src/lib/auth/login.component.ts","../../../projects/ui/src/lib/auth/auth-validation.ts","../../../projects/ui/src/lib/auth/register.component.ts","../../../projects/ui/src/lib/auth/sso-callback.component.ts","../../../projects/ui/src/lib/auth/auth.routes.ts","../../../projects/ui/src/lib/auth/profile.component.ts","../../../projects/ui/src/lib/auth/config-users.component.ts","../../../projects/ui/src/lib/auth/config-kcllu.component.ts"],"sourcesContent":["import { ApplicationRef, ComponentRef, EnvironmentInjector, NgZone, Type, createComponent } from '@angular/core';\n\n/**\n * popup ที่ผูกกับช่อง input หนึ่งช่อง — ใช้ร่วมกันโดย appDatePicker และ appClockPicker\n *\n * สร้าง component ตอนเปิดแล้วแปะไว้ที่ <body> (ไม่ใช่ข้าง input) เพื่อไม่ให้โดน overflow:hidden ของกล่อง/modal\n * ที่ครอบช่องตัดทิ้ง และทำลายทิ้งทันทีที่ปิด ตัว component ต้องเป็น position:fixed เอง (ตำแหน่ง top/left ตั้งจากที่นี่)\n * - วางใต้ช่อง ถ้าด้านล่างไม่พอแต่ด้านบนพอให้เปิดขึ้นด้านบน และไม่ให้ล้นขอบซ้าย/ขวาของจอ (จอมือถือ)\n * - ปิดเองเมื่อคลิก/แตะนอกช่องและนอก popup, วางตำแหน่งใหม่เมื่อเลื่อนหน้าหรือย่อขยายจอ\n */\nexport class AnchoredPopup<T> {\n private ref: ComponentRef<T> | null = null;\n\n private readonly outsideHandler = (e: Event) => {\n const target = e.target as Node;\n if (this.anchor.contains(target) || this.ref?.location.nativeElement.contains(target)) return;\n this.zone.run(() => this.close());\n };\n private readonly repositionHandler = () => this.position();\n\n constructor(\n private anchor: HTMLElement,\n private component: Type<T>,\n private appRef: ApplicationRef,\n private injector: EnvironmentInjector,\n private zone: NgZone,\n ) {}\n\n get isOpen(): boolean {\n return this.ref !== null;\n }\n\n get instance(): T | null {\n return this.ref?.instance ?? null;\n }\n\n /** setup ใช้ตั้งค่าเริ่มต้น/callback ให้ component ก่อนเรนเดอร์ครั้งแรก */\n open(setup: (instance: T) => void): void {\n if (this.ref) return;\n\n const ref = createComponent(this.component, { environmentInjector: this.injector });\n setup(ref.instance);\n this.appRef.attachView(ref.hostView);\n document.body.appendChild(ref.location.nativeElement);\n ref.changeDetectorRef.detectChanges();\n this.ref = ref;\n this.position();\n\n this.zone.runOutsideAngular(() => {\n document.addEventListener('mousedown', this.outsideHandler, true);\n document.addEventListener('touchstart', this.outsideHandler, true);\n window.addEventListener('scroll', this.repositionHandler, true);\n window.addEventListener('resize', this.repositionHandler);\n });\n }\n\n close(): void {\n if (!this.ref) return;\n document.removeEventListener('mousedown', this.outsideHandler, true);\n document.removeEventListener('touchstart', this.outsideHandler, true);\n window.removeEventListener('scroll', this.repositionHandler, true);\n window.removeEventListener('resize', this.repositionHandler);\n this.appRef.detachView(this.ref.hostView);\n this.ref.destroy();\n this.ref = null;\n }\n\n position(): void {\n const popup = this.ref?.location.nativeElement as HTMLElement | undefined;\n if (!popup) return;\n const rect = this.anchor.getBoundingClientRect();\n const h = popup.offsetHeight;\n const w = popup.offsetWidth;\n const gap = 2;\n const margin = 8;\n\n let top = rect.bottom + gap;\n if (top + h > window.innerHeight - margin && rect.top - gap - h >= margin) {\n top = rect.top - gap - h;\n }\n const left = Math.max(margin, Math.min(rect.left, window.innerWidth - w - margin));\n\n popup.style.top = `${top}px`;\n popup.style.left = `${left}px`;\n }\n}\n","/** ส่วนประกอบวันที่แบบ ค.ศ. (month เริ่มที่ 0 แบบ Date) */\nexport interface CeDateParts { year: number; month: number; day: number; }\n\n/** ผลต่างระหว่างปี พ.ศ. กับ ค.ศ. */\nconst BE_OFFSET = 543;\n/** ปีที่เกินค่านี้ถือว่าเป็น พ.ศ. อยู่แล้ว ใช้กันบวกทบเวลาถูกเรียกซ้ำ */\nconst BE_THRESHOLD = 2400;\n/** รูปแบบค่าที่ใช้ทั้งใน input และ ngModel: yyyy-MM-dd */\nconst DATE_PATTERN = /^(\\d{4})(-\\d{2}-\\d{2})$/;\n\n/** ข้อความ yyyy-MM-dd ค.ศ. → พ.ศ. ; ค่าที่ไม่ตรงรูปแบบคืนตามเดิม */\nexport function toBuddhistText(val: unknown): string {\n const text = val === null || val === undefined ? '' : String(val);\n const m = DATE_PATTERN.exec(text);\n if (!m) return text;\n const year = parseInt(m[1], 10);\n return year > BE_THRESHOLD ? text : `${year + BE_OFFSET}${m[2]}`;\n}\n\n/** ข้อความ yyyy-MM-dd พ.ศ. → ค.ศ. ; ค่าที่ไม่ตรงรูปแบบคืนตามเดิม */\nexport function toChristianText(val: unknown): string {\n const text = val === null || val === undefined ? '' : String(val);\n const m = DATE_PATTERN.exec(text);\n if (!m) return text;\n const year = parseInt(m[1], 10);\n return year > BE_THRESHOLD ? `${year - BE_OFFSET}${m[2]}` : text;\n}\n\n/** ข้อความ yyyy-MM-dd (ค.ศ. หรือ พ.ศ.) → ส่วนประกอบ ค.ศ. ; ค่าที่ไม่ตรงรูปแบบคืน null */\nexport function parseDate(val: unknown): CeDateParts | null {\n if (typeof val !== 'string' || val.length < 10) return null;\n const parts = val.substring(0, 10).split('-').map(p => parseInt(p, 10));\n if (parts.length !== 3 || parts.some(isNaN)) return null;\n let [year, month, day] = parts;\n // รับมาจาก API เป็น ค.ศ. เสมอ เผื่อไว้เฉพาะกรณีมีคนป้อน พ.ศ. เข้ามา\n if (year > BE_THRESHOLD) year -= BE_OFFSET;\n return { year, month: month - 1, day };\n}\n\n/** ส่วนประกอบ ค.ศ. → ข้อความ yyyy-MM-dd ค.ศ. */\nexport function formatDate(d: CeDateParts): string {\n return `${d.year}-${String(d.month + 1).padStart(2, '0')}-${String(d.day).padStart(2, '0')}`;\n}\n","import { Component, computed, signal } from '@angular/core';\nimport { CeDateParts } from '../utils/thai-date';\n\ntype CalendarView = 'day' | 'month' | 'year';\n\n/**\n * ปฏิทินแบบ dropdown ของ appDatePicker (ดู datepicker.directive.ts) — เขียนเองแทน Syncfusion ej2 DatePicker\n * ที่ต้องโหลด bundle 27.7 MB + css 4.8 MB และต้องมี license key\n *\n * directive สร้าง component นี้ตอนเปิดปฏิทินแล้วแปะไว้ที่ <body> (ไม่ใช่ข้าง input) เพื่อไม่ให้โดน overflow:hidden\n * ของกล่อง/modal ที่ครอบช่องตัดทิ้ง และทำลายทิ้งทันทีที่ปิด — ค่าเข้า/ออกผ่าน property กับ callback ที่ directive ตั้งให้\n *\n * ภายในทำงานกับ ค.ศ. ล้วน แปลงเป็น พ.ศ. ตอนแสดงผลเท่านั้น\n * มุมมองเหมือน ej2 เดิม: วัน → (คลิกหัว) เดือน → (คลิกหัว) ปีทีละ 10 ปี — จำเป็นกับช่องวันเกิดที่ต้องย้อนไปหลายสิบปี\n */\n@Component({\n selector: 'app-datepicker-popup',\n standalone: true,\n host: {\n class: 'dpp',\n // กันไม่ให้ mousedown ในปฏิทินขโมย focus ไปจาก input (ไม่งั้นผู้ใช้กด Tab ต่อจากช่องเดิมไม่ได้)\n '(mousedown)': '$event.preventDefault()'\n },\n template: `\n <div class=\"dpp-header\">\n <button type=\"button\" class=\"dpp-nav\" (click)=\"shift(-1)\" aria-label=\"ก่อนหน้า\">‹</button>\n <button type=\"button\" class=\"dpp-title\" [disabled]=\"view() === 'year'\" (click)=\"zoomOut()\">{{ title() }}</button>\n <button type=\"button\" class=\"dpp-nav\" (click)=\"shift(1)\" aria-label=\"ถัดไป\">›</button>\n </div>\n\n @switch (view()) {\n @case ('day') {\n <div class=\"dpp-grid dpp-days\">\n @for (w of weekdays; track w) { <div class=\"dpp-weekday\">{{ w }}</div> }\n @for (day of dayCells(); track $index) {\n @if (day === null) {\n <div></div>\n } @else {\n <button type=\"button\" class=\"dpp-cell\"\n [class.dpp-selected]=\"isSelectedDay(day)\" [class.dpp-today]=\"isToday(day)\"\n (click)=\"pickDay(day)\">{{ day }}</button>\n }\n }\n </div>\n }\n @case ('month') {\n <div class=\"dpp-grid dpp-wide\">\n @for (m of monthsShort; track $index) {\n <button type=\"button\" class=\"dpp-cell\"\n [class.dpp-selected]=\"selected?.year === viewYear() && selected?.month === $index\"\n (click)=\"pickMonth($index)\">{{ m }}</button>\n }\n </div>\n }\n @case ('year') {\n <div class=\"dpp-grid dpp-wide\">\n @for (y of yearCells(); track y; let i = $index) {\n <button type=\"button\" class=\"dpp-cell\"\n [class.dpp-outside]=\"i === 0 || i === 11\"\n [class.dpp-selected]=\"selected?.year === y\"\n (click)=\"pickYear(y)\">{{ y + 543 }}</button>\n }\n </div>\n }\n }\n\n <div class=\"dpp-footer\">\n <button type=\"button\" class=\"dpp-today-btn\" (click)=\"pickToday()\">วันนี้</button>\n </div>\n `,\n styles: [`\n :host {\n position: fixed;\n /* modal/overlay ทั้งระบบใช้ 9000-9999 — ช่องวันที่อยู่ใน modal ได้ (เช่นวันเกิดในหน้าครอบครัว) ต้องอยู่เหนือกว่า */\n z-index: 10000;\n width: 260px;\n max-width: calc(100vw - 16px);\n background: #fff;\n border: 1px solid #ccc;\n border-radius: 6px;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);\n padding: 8px;\n font-size: 13px;\n color: #333;\n user-select: none;\n }\n button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }\n .dpp-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }\n .dpp-nav { width: 30px; height: 30px; border-radius: 50%; font-size: 18px; line-height: 1; }\n .dpp-nav:hover, .dpp-title:not([disabled]):hover, .dpp-cell:hover { background: #eef1fb; }\n .dpp-title { flex: 1; height: 30px; border-radius: 4px; font-weight: 600; }\n .dpp-title[disabled] { cursor: default; }\n .dpp-grid { display: grid; gap: 2px; }\n .dpp-days { grid-template-columns: repeat(7, 1fr); }\n .dpp-wide { grid-template-columns: repeat(3, 1fr); }\n .dpp-weekday { text-align: center; font-size: 12px; color: #888; padding: 4px 0; }\n .dpp-weekday:first-child { color: #d9534f; }\n .dpp-cell { height: 32px; border-radius: 4px; text-align: center; }\n .dpp-wide .dpp-cell { height: 44px; }\n .dpp-today { border: 1px solid #3f51b5; }\n .dpp-selected, .dpp-selected:hover { background: #3f51b5; color: #fff; }\n .dpp-outside { color: #aaa; }\n .dpp-footer { display: flex; justify-content: flex-end; margin-top: 6px; border-top: 1px solid #eee; padding-top: 6px; }\n .dpp-today-btn { color: #3f51b5; font-weight: 600; padding: 4px 10px; border-radius: 4px; }\n .dpp-today-btn:hover { background: #eef1fb; }\n `]\n})\nexport class DatePickerPopupComponent {\n readonly weekdays = ['อา', 'จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส'];\n readonly monthsShort = ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'];\n private readonly monthsFull = ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน',\n 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'];\n\n /** ตั้งโดย directive ตอนสร้าง — วันที่เลือกไว้ (หรือ null) */\n selected: CeDateParts | null = null;\n /** ตั้งโดย directive — เรียกเมื่อผู้ใช้เลือกวัน */\n onPick: (date: CeDateParts) => void = () => {};\n\n view = signal<CalendarView>('day');\n viewYear = signal(new Date().getFullYear());\n viewMonth = signal(new Date().getMonth());\n\n /** เปิดปฏิทินที่เดือนของวันที่เลือกไว้ ถ้าไม่มีให้เปิดที่เดือนปัจจุบัน */\n init(selected: CeDateParts | null): void {\n this.selected = selected;\n const base = selected ?? this.todayParts();\n this.viewYear.set(base.year);\n this.viewMonth.set(base.month);\n this.view.set('day');\n }\n\n title = computed(() => {\n const y = this.viewYear();\n switch (this.view()) {\n case 'day': return `${this.monthsFull[this.viewMonth()]} ${y + 543}`;\n case 'month': return `${y + 543}`;\n default: {\n const start = this.decadeStart(y);\n return `${start + 543} - ${start + 9 + 543}`;\n }\n }\n });\n\n // ช่องว่าง (null) เติมหน้าวันที่ 1 ให้ตรงวันในสัปดาห์\n dayCells = computed<(number | null)[]>(() => {\n const y = this.viewYear();\n const m = this.viewMonth();\n const cells: (number | null)[] = Array(new Date(y, m, 1).getDay()).fill(null);\n const daysInMonth = new Date(y, m + 1, 0).getDate();\n for (let d = 1; d <= daysInMonth; d++) cells.push(d);\n return cells;\n });\n\n // 12 ช่องเหมือน ej2: ปีท้ายทศวรรษก่อน + 10 ปีของทศวรรษนี้ + ปีแรกของทศวรรษถัดไป\n yearCells = computed<number[]>(() => {\n const start = this.decadeStart(this.viewYear());\n return Array.from({ length: 12 }, (_, i) => start - 1 + i);\n });\n\n shift(delta: number): void {\n if (this.view() === 'day') {\n const total = this.viewYear() * 12 + this.viewMonth() + delta;\n this.viewYear.set(Math.floor(total / 12));\n this.viewMonth.set(((total % 12) + 12) % 12);\n } else {\n this.viewYear.update(y => y + delta * (this.view() === 'month' ? 1 : 10));\n }\n }\n\n zoomOut(): void {\n this.view.set(this.view() === 'day' ? 'month' : 'year');\n }\n\n pickYear(year: number): void {\n this.viewYear.set(year);\n this.view.set('month');\n }\n\n pickMonth(month: number): void {\n this.viewMonth.set(month);\n this.view.set('day');\n }\n\n pickDay(day: number): void {\n this.onPick({ year: this.viewYear(), month: this.viewMonth(), day });\n }\n\n pickToday(): void {\n this.onPick(this.todayParts());\n }\n\n isSelectedDay(day: number): boolean {\n const s = this.selected;\n return !!s && s.year === this.viewYear() && s.month === this.viewMonth() && s.day === day;\n }\n\n isToday(day: number): boolean {\n const t = this.todayParts();\n return t.year === this.viewYear() && t.month === this.viewMonth() && t.day === day;\n }\n\n private decadeStart(year: number): number {\n return Math.floor(year / 10) * 10;\n }\n\n private todayParts(): CeDateParts {\n const d = new Date();\n return { year: d.getFullYear(), month: d.getMonth(), day: d.getDate() };\n }\n}\n","import { CSP_NONCE, DOCUMENT, inject } from '@angular/core';\n\nconst STYLE_ID = 'kcore-picker-styles';\n\n/**\n * สไตล์ของช่อง input ที่ใช้ appDatePicker / appClockPicker (เดิมอยู่ใน master.css ของแต่ละโปรเจกต์)\n * ใส่ลง <head> ครั้งเดียวต่อหน้า โปรเจกต์ปลายทางจึงไม่ต้องเพิ่ม css เอง\n *\n * ช่องวันที่: ไอคอนปฏิทินท้ายช่อง + เคอร์เซอร์มือ — ตั้งแค่ background-image ไม่ใช้ shorthand background\n * เพื่อไม่ทับสีพื้นที่แต่ละหน้ากำหนดให้ช่องเอง\n * ช่องเวลา: ไม่ใส่ไอคอนเหมือนช่องวันที่ เพราะหลายหน้ากำหนดช่องกว้างแค่ 70px พอดี \"08:30\"\n */\nexport const PICKER_CSS = `\n.app-datepicker {\n cursor: pointer;\n padding-right: 26px !important;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 7px center;\n background-size: 14px 14px;\n}\n.app-datepicker:disabled { cursor: default; }\n.app-clockpicker { cursor: pointer; }\n.app-clockpicker:disabled { cursor: default; }\n`;\n\n/** เรียกใน injection context (constructor/field ของ directive) — ใส่ <style> ครั้งแรกครั้งเดียว ครั้งถัดไปไม่ทำอะไร */\nexport function ensurePickerStyles(): void {\n const doc = inject(DOCUMENT);\n if (!doc.head || doc.getElementById(STYLE_ID)) return;\n const style = doc.createElement('style');\n style.id = STYLE_ID;\n // รองรับหน้าที่ตั้ง Content-Security-Policy แบบ nonce ไว้ (ตั้งผ่าน CSP_NONCE หรือ ngCspNonce)\n const nonce = inject(CSP_NONCE, { optional: true });\n if (nonce) style.setAttribute('nonce', nonce);\n style.textContent = PICKER_CSS;\n doc.head.appendChild(style);\n}\n","import { ApplicationRef, Directive, ElementRef, EnvironmentInjector, NgZone, OnDestroy, forwardRef, inject } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { AnchoredPopup } from '../utils/anchored-popup';\nimport { CeDateParts, formatDate, parseDate, toBuddhistText, toChristianText } from '../utils/thai-date';\nimport { DatePickerPopupComponent } from './datepicker-popup.component';\nimport { ensurePickerStyles } from './picker-styles';\n\n/**\n * ช่องเลือกวันที่แบบไทย ใช้กับ <input type=\"text\" appDatePicker formControlName=\"...\">\n * - ค่าใน form control เป็น ค.ศ. yyyy-MM-dd เสมอ (ตรงกับที่ API ใช้) ส่วนในช่องแสดงเป็น พ.ศ.\n * - คลิกช่อง = เปิดปฏิทิน (DatePickerPopupComponent) ห้ามพิมพ์เอง เหมือน onkeydown=\"return false\" ของ ASP เดิม\n *\n * เดิมห่อ Syncfusion ej2 DatePicker ซึ่งต้องโหลด js/datepicker.min.js 27.7 MB + css 4.8 MB ไว้ใน <head> ทุกหน้า\n * (หน้า signature-accept ที่เปิดจากแจ้งเตือน IDMS บนมือถือจึงช้ามาก) และต้องมี license key ของ Syncfusion\n * จึงเขียนเองแทน — สัญญาของค่า (ค.ศ. ใน control / พ.ศ. บนจอ) เหมือนเดิมทุกประการ หน้าที่ใช้อยู่ไม่ต้องแก้\n */\n@Directive({\n selector: '[appDatePicker]',\n standalone: true,\n host: {\n class: 'app-datepicker',\n readonly: '',\n autocomplete: 'off',\n placeholder: 'เลือกวันที่',\n '(click)': 'open()',\n '(keydown)': 'onKeydown($event)',\n '(blur)': 'onTouched()',\n },\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => DatePickerDirective),\n multi: true,\n },\n ],\n})\nexport class DatePickerDirective implements OnDestroy, ControlValueAccessor {\n private el = inject<ElementRef<HTMLInputElement>>(ElementRef);\n private popup = new AnchoredPopup(\n this.el.nativeElement, DatePickerPopupComponent,\n inject(ApplicationRef), inject(EnvironmentInjector), inject(NgZone));\n\n private valuePatched = false;\n private disabled = false;\n\n private onChange: (val: string) => void = () => {};\n onTouched: () => void = () => {};\n\n constructor() {\n ensurePickerStyles();\n // ต้องครอบ property `value` ตั้งแต่ตอนสร้าง directive คือก่อน writeValue() ครั้งแรก\n // ไม่งั้นค่าแรกที่ถูกเขียนลงช่องจะโผล่เป็น ค.ศ.\n this.patchInputValue();\n }\n\n open(): void {\n if (this.disabled) return;\n this.popup.open(p => {\n p.init(parseDate(this.el.nativeElement.value));\n p.onPick = (d) => this.select(d);\n });\n }\n\n close(): void {\n this.popup.close();\n }\n\n onKeydown(e: KeyboardEvent): void {\n if (e.key === 'Escape' || e.key === 'Esc' || e.key === 'Tab') {\n this.close();\n return;\n }\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault();\n this.open();\n return;\n }\n // ห้ามพิมพ์เอง ต้องเลือกจากปฏิทินเท่านั้น (เหมือนเดิม) — คีย์ผสม เช่น Ctrl+C ปล่อยผ่าน\n if (!e.ctrlKey && !e.metaKey) e.preventDefault();\n }\n\n ngOnDestroy(): void {\n this.close();\n this.unpatchInputValue();\n }\n\n writeValue(val: string): void {\n const parsed = parseDate(val);\n // เขียนเป็น ค.ศ. เสมอ setter ที่ครอบไว้จะเก็บเป็น พ.ศ. ให้เอง ช่องจึงแสดง พ.ศ. ตั้งแต่แรก\n this.el.nativeElement.value = parsed ? formatDate(parsed) : '';\n this.popup.instance?.init(parsed);\n }\n\n registerOnChange(fn: (val: string) => void): void {\n this.onChange = fn;\n }\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.disabled = isDisabled;\n this.el.nativeElement.disabled = isDisabled;\n if (isDisabled) this.close();\n }\n\n private select(d: CeDateParts): void {\n const ceDate = formatDate(d);\n this.el.nativeElement.value = ceDate;\n this.close();\n this.onChange(ceDate);\n this.onTouched();\n }\n\n /**\n * ครอบ property `value` ของ input ตัวนี้ (เฉพาะ element นี้ ไม่แตะ HTMLInputElement.prototype ส่วนกลาง)\n * โดยห่อ getter/setter เดิมของเบราว์เซอร์ไว้:\n * - setter: บวก 543 ก่อนเก็บจริง → ช่องแสดง พ.ศ. ตลอด ไม่ว่าใครเป็นคนเขียน\n * - getter: ลบ 543 คืนเป็น ค.ศ. เสมอ → โค้ดที่อ่าน input.value ตรง ๆ ได้ ค.ศ. เหมือนค่าใน form control\n */\n private patchInputValue(): void {\n const input = this.el.nativeElement;\n if (!input || this.valuePatched) return;\n\n const desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');\n const nativeGet = desc?.get;\n const nativeSet = desc?.set;\n if (!nativeGet || !nativeSet) return;\n\n Object.defineProperty(input, 'value', {\n configurable: true,\n enumerable: true,\n get: () => toChristianText(nativeGet.call(input)),\n set: (v: unknown) => nativeSet.call(input, toBuddhistText(v)),\n });\n this.valuePatched = true;\n }\n\n /** คืน property `value` ให้กลับไปใช้ตัวเดิมบน prototype */\n private unpatchInputValue(): void {\n if (!this.valuePatched) return;\n this.valuePatched = false;\n const input = this.el.nativeElement;\n if (input) delete (input as any).value;\n }\n}\n","import { Component, computed, signal } from '@angular/core';\n\ntype ClockView = 'hour' | 'minute';\n\n/** ขนาดหน้าปัด (พิกัดใน viewBox ของ SVG) */\nconst SIZE = 220;\nconst CENTER = SIZE / 2;\nconst OUTER_R = 88;\nconst INNER_R = 58;\nconst TICK_R = 15;\n\ninterface Tick { label: string; value: number; x: number; y: number; }\n\n/** ตำแหน่งบนหน้าปัดของช่องที่ index (0 = 12 นาฬิกา ไล่ตามเข็ม ช่องละ 30°) */\nfunction tickPos(index: number, radius: number): { x: number; y: number } {\n const a = (index * Math.PI) / 6;\n return { x: CENTER + radius * Math.sin(a), y: CENTER - radius * Math.cos(a) };\n}\n\nconst pad = (n: number) => String(n).padStart(2, '0');\n\n/**\n * หน้าปัดเลือกเวลาของ appClockPicker (ดู clockpicker.directive.ts) — เขียนเองแทนปลั๊กอิน jQuery clockpicker\n * ใช้งานแบบเดียวกับของเดิม: แตะ/ลากเลือกชั่วโมงก่อน → สลับไปหน้าปัดนาที → เลือกนาทีแล้วปิดเอง (autoclose เดิม)\n *\n * วงชั่วโมงจัดแบบเดียวกับ jQuery clockpicker โหมด 24 ชม. ที่ผู้ใช้คุ้นอยู่แล้ว: วงใน 12, 1–11 / วงนอก 00, 13–23\n * นาทีปัดเป็นทีละ 5 นาที (ข้อมูลเดิมใน DB เกือบทั้งหมดลงตัว 5 นาทีอยู่แล้ว และแตะให้ตรงทีละนาทีบนมือถือแทบไม่ได้)\n */\n@Component({\n selector: 'app-clockpicker-popup',\n standalone: true,\n host: {\n class: 'cpp',\n // กันไม่ให้ mousedown ใน popup ขโมย focus ไปจาก input (ไม่งั้นผู้ใช้กด Tab ต่อจากช่องเดิมไม่ได้)\n '(mousedown)': '$event.preventDefault()'\n },\n template: `\n <div class=\"cpp-header\">\n <button type=\"button\" class=\"cpp-part\" [class.cpp-active]=\"view() === 'hour'\" (click)=\"view.set('hour')\">{{ pad(hour()) }}</button>\n <span class=\"cpp-colon\">:</span>\n <button type=\"button\" class=\"cpp-part\" [class.cpp-active]=\"view() === 'minute'\" (click)=\"view.set('minute')\">{{ pad(minute()) }}</button>\n </div>\n\n <svg class=\"cpp-dial\" [attr.viewBox]=\"'0 0 ' + size + ' ' + size\"\n (pointerdown)=\"onPointerDown($event)\" (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\" (pointercancel)=\"dragging = false\">\n <circle class=\"cpp-face\" [attr.cx]=\"center\" [attr.cy]=\"center\" [attr.r]=\"center - 2\" />\n <line class=\"cpp-hand\" [attr.x1]=\"center\" [attr.y1]=\"center\" [attr.x2]=\"hand().x\" [attr.y2]=\"hand().y\" />\n <circle class=\"cpp-hand-dot\" [attr.cx]=\"center\" [attr.cy]=\"center\" r=\"3\" />\n <circle class=\"cpp-hand-tip\" [attr.cx]=\"hand().x\" [attr.cy]=\"hand().y\" [attr.r]=\"tickR\" />\n @for (t of ticks(); track t.value) {\n <text class=\"cpp-tick\" [class.cpp-tick-inner]=\"view() === 'hour' && t.value >= 1 && t.value <= 12\"\n [class.cpp-tick-selected]=\"t.value === (view() === 'hour' ? hour() : minute())\"\n [attr.x]=\"t.x\" [attr.y]=\"t.y\">{{ t.label }}</text>\n }\n </svg>\n `,\n styles: [`\n :host {\n position: fixed;\n /* modal/overlay ทั้งระบบใช้ 9000-9999 — ช่องเวลาอยู่ใน modal ได้ ต้องอยู่เหนือกว่า */\n z-index: 10000;\n width: 240px;\n max-width: calc(100vw - 16px);\n background: #fff;\n border: 1px solid #ccc;\n border-radius: 6px;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);\n padding: 8px 10px 10px;\n color: #333;\n user-select: none;\n }\n button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }\n .cpp-header { display: flex; justify-content: center; align-items: center; gap: 2px; margin-bottom: 6px; font-size: 26px; }\n .cpp-part { padding: 0 6px; border-radius: 4px; color: #999; }\n .cpp-part:hover { background: #eef1fb; }\n .cpp-active { color: #3f51b5; font-weight: 600; }\n .cpp-colon { color: #999; }\n .cpp-dial { display: block; width: 100%; height: auto; touch-action: none; cursor: pointer; }\n .cpp-face { fill: #f2f3f7; }\n .cpp-hand { stroke: #3f51b5; stroke-width: 2; }\n .cpp-hand-dot, .cpp-hand-tip { fill: #3f51b5; }\n .cpp-tick { font-size: 14px; text-anchor: middle; dominant-baseline: central; fill: #333; pointer-events: none; }\n .cpp-tick-inner { font-size: 12px; fill: #666; }\n .cpp-tick-selected { fill: #fff; }\n `]\n})\nexport class ClockPickerPopupComponent {\n readonly size = SIZE;\n readonly center = CENTER;\n readonly tickR = TICK_R;\n readonly pad = pad;\n\n /** ตั้งโดย directive — เรียกเมื่อเลือกนาทีเสร็จ ส่งเวลาแบบ HH:mm */\n onPick: (time: string) => void = () => {};\n\n view = signal<ClockView>('hour');\n hour = signal(8);\n minute = signal(30);\n dragging = false;\n\n /** เปิดที่เวลาในช่อง (\"HH:mm\" หรือ \"HH:mm:ss\") ถ้าว่าง/อ่านไม่ออกให้เปิดที่เวลาปัจจุบัน */\n init(value: string): void {\n const m = /^(\\d{1,2}):(\\d{2})/.exec(value || '');\n const now = new Date();\n const h = m ? Number(m[1]) : now.getHours();\n const min = m ? Number(m[2]) : now.getMinutes();\n this.hour.set(h >= 0 && h < 24 ? h : 0);\n this.minute.set(min >= 0 && min < 60 ? min : 0);\n this.view.set('hour');\n }\n\n ticks = computed<Tick[]>(() => {\n if (this.view() === 'minute') {\n return Array.from({ length: 12 }, (_, i) => ({ label: pad(i * 5), value: i * 5, ...tickPos(i, OUTER_R) }));\n }\n const outer = Array.from({ length: 12 }, (_, i) => {\n const value = i === 0 ? 0 : i + 12;\n return { label: pad(value), value, ...tickPos(i, OUTER_R) };\n });\n const inner = Array.from({ length: 12 }, (_, i) => {\n const value = i === 0 ? 12 : i;\n return { label: String(value), value, ...tickPos(i, INNER_R) };\n });\n return [...outer, ...inner];\n });\n\n hand = computed(() => {\n if (this.view() === 'minute') {\n // ค่าเดิมที่ไม่ลงตัว 5 นาที (เช่น 08:47) ชี้ตามจริง ไม่ปัด\n return tickPos(this.minute() / 5, OUTER_R);\n }\n const h = this.hour();\n return tickPos(h % 12, h >= 1 && h <= 12 ? INNER_R : OUTER_R);\n });\n\n onPointerDown(e: PointerEvent): void {\n this.dragging = true;\n // ให้ลากออกนอกหน้าปัดแล้วยังได้ pointermove/pointerup — ถ้าเบราว์เซอร์ไม่ยอม (pointer หายไปแล้ว ฯลฯ) ก็แค่ไม่ capture\n try { (e.currentTarget as Element).setPointerCapture?.(e.pointerId); } catch { /* ignore */ }\n this.pick(e);\n }\n\n onPointerMove(e: PointerEvent): void {\n if (this.dragging) this.pick(e);\n }\n\n onPointerUp(e: PointerEvent): void {\n if (!this.dragging) return;\n this.dragging = false;\n this.pick(e);\n if (this.view() === 'hour') {\n this.view.set('minute');\n } else {\n this.onPick(`${pad(this.hour())}:${pad(this.minute())}`);\n }\n }\n\n // แปลงตำแหน่งนิ้ว/เมาส์เป็นค่า: มุมจาก 12 นาฬิกา → ช่อง, ระยะจากจุดกลาง → วงใน/วงนอก (เฉพาะชั่วโมง)\n private pick(e: PointerEvent): void {\n const svg = e.currentTarget as SVGSVGElement;\n const rect = svg.getBoundingClientRect();\n const scale = SIZE / rect.width;\n const dx = (e.clientX - rect.left) * scale - CENTER;\n const dy = (e.clientY - rect.top) * scale - CENTER;\n let angle = Math.atan2(dx, -dy);\n if (angle < 0) angle += 2 * Math.PI;\n\n if (this.view() === 'hour') {\n const index = Math.round(angle / (Math.PI / 6)) % 12;\n const inner = Math.hypot(dx, dy) < (OUTER_R + INNER_R) / 2;\n this.hour.set(inner ? (index === 0 ? 12 : index) : (index === 0 ? 0 : index + 12));\n } else {\n this.minute.set((Math.round(angle / (Math.PI / 6)) % 12) * 5);\n }\n }\n}\n","import { ApplicationRef, Directive, ElementRef, EnvironmentInjector, NgZone, OnDestroy, forwardRef, inject } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { AnchoredPopup } from '../utils/anchored-popup';\nimport { ClockPickerPopupComponent } from './clockpicker-popup.component';\nimport { ensurePickerStyles } from './picker-styles';\n\n/**\n * ช่องเลือกเวลา ใช้กับ <input type=\"text\" appClockPicker formControlName=\"...\"> — ค่าเป็น \"HH:mm\"\n * คลิกช่อง = เปิดหน้าปัด (ClockPickerPopupComponent) ห้ามพิมพ์เอง เหมือนของเดิม (Leaves_Detail.aspx บรรทัด 143-155)\n *\n * เดิมห่อปลั๊กอิน jQuery clockpicker ซึ่งเป็นเหตุผลเดียวที่ทั้งแอปต้องโหลด jQuery ใน <head> ทุกหน้า\n * จึงเขียนเองแทน — ค่าที่เข้า/ออกเหมือนเดิม หน้าที่ใช้อยู่ไม่ต้องแก้\n */\n@Directive({\n selector: '[appClockPicker]',\n standalone: true,\n host: {\n class: 'app-clockpicker',\n readonly: '',\n autocomplete: 'off',\n '(click)': 'open()',\n '(keydown)': 'onKeydown($event)',\n '(blur)': 'onTouched()',\n },\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => ClockPickerDirective),\n multi: true\n }\n ]\n})\nexport class ClockPickerDirective implements OnDestroy, ControlValueAccessor {\n private el = inject<ElementRef<HTMLInputElement>>(ElementRef);\n private popup = new AnchoredPopup(\n this.el.nativeElement, ClockPickerPopupComponent,\n inject(ApplicationRef), inject(EnvironmentInjector), inject(NgZone));\n\n private disabled = false;\n\n private onChange: (val: string) => void = () => {};\n onTouched: () => void = () => {};\n\n constructor() {\n ensurePickerStyles();\n }\n\n open(): void {\n if (this.disabled) return;\n this.popup.open(p => {\n p.init(this.el.nativeElement.value);\n p.onPick = (time) => this.select(time);\n });\n }\n\n close(): void {\n this.popup.close();\n }\n\n onKeydown(e: KeyboardEvent): void {\n if (e.key === 'Escape' || e.key === 'Esc' || e.key === 'Tab') {\n this.close();\n return;\n }\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault();\n this.open();\n return;\n }\n // ห้ามพิมพ์เอง ต้องเลือกจากหน้าปัดเท่านั้น (เหมือนเดิม) — คีย์ผสม เช่น Ctrl+C ปล่อยผ่าน\n if (!e.ctrlKey && !e.metaKey) e.preventDefault();\n }\n\n ngOnDestroy(): void {\n this.close();\n }\n\n writeValue(val: string): void {\n this.el.nativeElement.value = val || '';\n this.popup.instance?.init(val || '');\n }\n\n registerOnChange(fn: (val: string) => void): void { this.onChange = fn; }\n registerOnTouched(fn: () => void): void { this.onTouched = fn; }\n\n setDisabledState(isDisabled: boolean): void {\n this.disabled = isDisabled;\n this.el.nativeElement.disabled = isDisabled;\n if (isDisabled) this.close();\n }\n\n private select(time: string): void {\n this.el.nativeElement.value = time;\n this.close();\n this.onChange(time);\n this.onTouched();\n }\n}\n","import { Component, computed, forwardRef, signal } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\n// ช่องเลือกวันที่แบบไทย (แสดง พ.ศ.) สำหรับหน้าจอมือถือ — เป็นปุ่ม กดแล้วเปิดปฏิทินแบบ bottom sheet เต็มความกว้างจอ\n// (บนจอใหญ่/ช่องที่เป็น <input> ใช้ appDatePicker แทน)\n//\n// สัญญาของค่าเหมือน appDatePicker: ค่าใน form control เป็น \"ค.ศ. yyyy-MM-dd\" เสมอ — ตรงกับที่ API ต้องการ ส่วน พ.ศ. เป็นเรื่องการแสดงผลล้วน ๆ\n// (writeValue ยังรับ พ.ศ. เข้ามาได้ เผื่อค่าที่ส่งมาจากที่อื่นเป็น พ.ศ.)\n@Component({\n selector: 'app-thai-date-picker',\n standalone: true,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => ThaiDatePickerComponent),\n multi: true\n }\n ],\n templateUrl: './thai-date-picker.component.html'\n})\nexport class ThaiDatePickerComponent implements ControlValueAccessor {\n private static readonly monthsFull = [\n 'มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน',\n 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'\n ];\n private static readonly monthsShort = [\n 'ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.',\n 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'\n ];\n\n readonly weekdays = ['อา', 'จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส'];\n\n value = signal<string>('');\n isOpen = signal<boolean>(false);\n isDisabled = signal<boolean>(false);\n\n // เดือน/ปี (ค.ศ.) ที่ปฏิทินกำลังเปิดดูอยู่ — แยกจากค่าที่เลือกไว้ เพราะเลื่อนดูเดือนอื่นได้โดยยังไม่เลือก\n viewYear = signal<number>(new Date().getFullYear());\n viewMonth = signal<number>(new Date().getMonth());\n\n private onChange: (val: string) => void = () => { };\n private onTouched: () => void = () => { };\n\n displayText = computed(() => {\n const parsed = this.parse(this.value());\n if (!parsed) return 'เลือกวันที่';\n return `${parsed.day} ${ThaiDatePickerComponent.monthsShort[parsed.month]} ${parsed.year + 543}`;\n });\n\n headerText = computed(() =>\n `${ThaiDatePickerComponent.monthsFull[this.viewMonth()]} ${this.viewYear() + 543}`\n );\n\n // ตารางวันของเดือนที่เปิดดูอยู่ — ช่องว่าง (null) เติมหน้า/หลังให้ครบสัปดาห์\n weeks = computed<(number | null)[][]>(() => {\n const year = this.viewYear();\n const month = this.viewMonth();\n const firstWeekday = new Date(year, month, 1).getDay();\n const daysInMonth = new Date(year, month + 1, 0).getDate();\n\n const cells: (number | null)[] = Array(firstWeekday).fill(null);\n for (let d = 1; d <= daysInMonth; d++) cells.push(d);\n while (cells.length % 7 !== 0) cells.push(null);\n\n const rows: (number | null)[][] = [];\n for (let i = 0; i < cells.length; i += 7) rows.push(cells.slice(i, i + 7));\n return rows;\n });\n\n open() {\n if (this.isDisabled()) return;\n const parsed = this.parse(this.value()) || this.todayParts();\n this.viewYear.set(parsed.year);\n this.viewMonth.set(parsed.month);\n this.isOpen.set(true);\n }\n\n close() {\n this.isOpen.set(false);\n this.onTouched();\n }\n\n shiftMonth(delta: number) {\n const total = this.viewYear() * 12 + this.viewMonth() + delta;\n this.viewYear.set(Math.floor(total / 12));\n this.viewMonth.set(((total % 12) + 12) % 12);\n }\n\n shiftYear(delta: number) {\n this.viewYear.set(this.viewYear() + delta);\n }\n\n selectDay(day: number | null) {\n if (day === null) return;\n this.commit(this.format(this.viewYear(), this.viewMonth(), day));\n }\n\n selectToday() {\n const t = this.todayParts();\n this.viewYear.set(t.year);\n this.viewMonth.set(t.month);\n this.commit(this.format(t.year, t.month, t.day));\n }\n\n isSelected(day: number | null): boolean {\n if (day === null) return false;\n return this.value() === this.format(this.viewYear(), this.viewMonth(), day);\n }\n\n isToday(day: number | null): boolean {\n if (day === null) return false;\n const t = this.todayParts();\n return t.year === this.viewYear() && t.month === this.viewMonth() && t.day === day;\n }\n\n private commit(ceDate: string) {\n this.value.set(ceDate);\n this.onChange(ceDate);\n this.onTouched();\n this.isOpen.set(false);\n }\n\n private todayParts() {\n const d = new Date();\n return { year: d.getFullYear(), month: d.getMonth(), day: d.getDate() };\n }\n\n private format(year: number, month: number, day: number): string {\n return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;\n }\n\n // รับได้ทั้ง ค.ศ. และ พ.ศ. (ปี >= 2400 ถือเป็น พ.ศ.) คืนเป็นส่วนประกอบ ค.ศ. เสมอ\n private parse(val: string): { year: number; month: number; day: number } | null {\n if (!val) return null;\n const parts = val.substring(0, 10).split('-');\n if (parts.length !== 3) return null;\n\n let year = Number(parts[0]);\n const month = Number(parts[1]) - 1;\n const day = Number(parts[2]);\n if (!year || Number.isNaN(month) || !day) return null;\n if (year >= 2400) year -= 543;\n\n return { year, month, day };\n }\n\n writeValue(val: string): void {\n const parsed = this.parse(val);\n this.value.set(parsed ? this.format(parsed.year, parsed.month, parsed.day) : '');\n }\n\n registerOnChange(fn: (val: string) => void): void { this.onChange = fn; }\n registerOnTouched(fn: () => void): void { this.onTouched = fn; }\n setDisabledState(isDisabled: boolean): void { this.isDisabled.set(isDisabled); }\n}\n","<button type=\"button\" class=\"tdp-field\" [class.tdp-empty]=\"!value()\" [disabled]=\"isDisabled()\" (click)=\"open()\">\n {{ displayText() }}\n</button>\n\n@if (isOpen()) {\n<div class=\"tdp-overlay\" (click)=\"close()\">\n <div class=\"tdp-sheet\" (click)=\"$event.stopPropagation()\">\n\n <div class=\"tdp-header\">\n <button type=\"button\" class=\"tdp-nav\" (click)=\"shiftYear(-1)\" aria-label=\"ปีก่อนหน้า\">«</button>\n <button type=\"button\" class=\"tdp-nav\" (click)=\"shiftMonth(-1)\" aria-label=\"เดือนก่อนหน้า\">‹</button>\n <div class=\"tdp-title\">{{ headerText() }}</div>\n <button type=\"button\" class=\"tdp-nav\" (click)=\"shiftMonth(1)\" aria-label=\"เดือนถัดไป\">›</button>\n <button type=\"button\" class=\"tdp-nav\" (click)=\"shiftYear(1)\" aria-label=\"ปีถัดไป\">»</button>\n </div>\n\n <div class=\"tdp-weekdays\">\n @for (w of weekdays; track w) {\n <div class=\"tdp-weekday\">{{ w }}</div>\n }\n </div>\n\n @for (week of weeks(); track $index) {\n <div class=\"tdp-week\">\n @for (day of week; track $index) {\n <button type=\"button\" class=\"tdp-day\" [class.tdp-blank]=\"day === null\" [class.tdp-selected]=\"isSelected(day)\"\n [class.tdp-today]=\"isToday(day)\" [disabled]=\"day === null\" (click)=\"selectDay(day)\">\n {{ day }}\n </button>\n }\n </div>\n }\n\n <div class=\"tdp-actions\">\n <button type=\"button\" class=\"tdp-btn tdp-btn-today\" (click)=\"selectToday()\">วันนี้</button>\n <button type=\"button\" class=\"tdp-btn tdp-btn-close\" (click)=\"close()\">ปิด</button>\n </div>\n </div>\n</div>\n}\n\n<style>\n .tdp-field {\n width: 100%;\n height: 40px;\n border: 1px solid #ddd;\n border-radius: 8px;\n background: #fff;\n color: #333;\n font-size: 0.9rem;\n text-align: left;\n padding: 0 10px;\n }\n\n .tdp-field.tdp-empty {\n color: #999;\n }\n\n .tdp-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n display: flex;\n align-items: flex-end;\n justify-content: center;\n z-index: 150;\n }\n\n .tdp-sheet {\n width: 100%;\n max-width: 480px;\n background: #fff;\n border-radius: 16px 16px 0 0;\n padding: 14px 12px 18px;\n }\n\n .tdp-header {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-bottom: 10px;\n }\n\n .tdp-title {\n flex: 1;\n text-align: center;\n font-size: 0.98rem;\n font-weight: 600;\n color: #852991;\n }\n\n /* ปุ่มเลื่อนเดือน/ปี ต้องกว้างพอให้นิ้วแตะโดนบนจอเล็ก (44px ตามแนวทาง touch target) */\n .tdp-nav {\n width: 38px;\n height: 38px;\n flex-shrink: 0;\n border: 1px solid #eee;\n border-radius: 8px;\n background: #fff;\n color: #852991;\n font-size: 1rem;\n line-height: 1;\n }\n\n .tdp-weekdays,\n .tdp-week {\n display: grid;\n grid-template-columns: repeat(7, 1fr);\n gap: 2px;\n }\n\n .tdp-weekday {\n text-align: center;\n font-size: 0.72rem;\n color: #888;\n padding: 4px 0;\n }\n\n .tdp-day {\n height: 42px;\n border: none;\n border-radius: 8px;\n background: #fff;\n color: #333;\n font-size: 0.9rem;\n }\n\n .tdp-day.tdp-blank {\n background: transparent;\n }\n\n .tdp-day.tdp-today {\n border: 1px solid #852991;\n color: #852991;\n font-weight: 600;\n }\n\n .tdp-day.tdp-selected {\n background: #852991;\n color: #fff;\n font-weight: 600;\n }\n\n .tdp-actions {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n\n .tdp-btn {\n flex: 1;\n height: 42px;\n border-radius: 8px;\n font-size: 0.92rem;\n font-weight: 600;\n }\n\n .tdp-btn-today {\n border: 1px solid #852991;\n background: #fff;\n color: #852991;\n }\n\n .tdp-btn-close {\n border: none;\n background: #852991;\n color: #fff;\n }\n</style>\n","import {\n Component, DOCUMENT, DestroyRef, ElementRef, ViewChild, ViewEncapsulation,\n computed, forwardRef, inject, input, signal,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\n/** ตัวเลือกของ dropdown ค้นหาได้ — value ว่าง ('') ใช้เป็นตัวเลือก \"ไม่ระบุ\" ได้ */\nexport interface SearchOption { value: string; label: string; }\n\n/**\n * dropdown ที่พิมพ์ค้นหาได้ (แทน <select> ที่มีตัวเลือกยาว) — ใช้กับ ngModel / formControlName\n * <app-search-select [options]=\"opts()\" [(ngModel)]=\"x\" inputId=\"...\" placeholder=\"...\" />\n * คลิกช่อง = เปิดรายการทั้งหมด, พิมพ์ = กรองแบบ \"มีคำนี้\" (ไม่สนตัวพิมพ์ใหญ่เล็ก, คั่นด้วยช่องว่างได้หลายคำ),\n * ↑/↓ + Enter เลือก, Esc ปิด — ออกจากช่องโดยไม่เลือก = คงค่าเดิม\n * รายการแสดงแบบ position: fixed เพื่อไม่ให้ถูกตัดโดยกล่องที่ overflow: hidden หรือ modal\n *\n * สไตล์ฝังมากับ component (ViewEncapsulation.None, คลาส .kc-ss-*) — selector ตั้งใจให้ specificity ต่ำ\n * โปรเจกต์ปลายทางเขียนทับได้ง่าย หรือปรับผ่าน CSS variable --kc-ss-* (ตั้งที่ :root หรือกล่องแม่ใดก็ได้)\n */\n@Component({\n selector: 'app-search-select',\n standalone: true,\n encapsulation: ViewEncapsulation.None,\n providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SearchSelectComponent), multi: true }],\n host: { class: 'kc-ss', '[class.open]': 'open()', '[class.disabled]': 'isDisabled()' },\n template: `\n <input #box type=\"text\" class=\"kc-ss-input\" autocomplete=\"off\" role=\"combobox\"\n [attr.id]=\"inputId() || null\" [attr.aria-expanded]=\"open()\" [disabled]=\"isDisabled()\"\n [placeholder]=\"open() ? searchHint() : placeholder()\" [value]=\"open() ? query() : selectedLabel()\"\n (focus)=\"openList()\" (click)=\"openList()\" (input)=\"onInput($any($event.target).value)\"\n (keydown)=\"onKey($event)\" (blur)=\"onBlur()\" />\n <svg class=\"kc-ss-arrow\" viewBox=\"0 0 16 16\" aria-hidden=\"true\" (mousedown)=\"toggle($event)\">\n <path d=\"M3.5 6 8 10.5 12.5 6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n @if (open()) {\n <ul #list class=\"kc-ss-list\" role=\"listbox\" [style.left.px]=\"pos().left\" [style.width.px]=\"pos().width\"\n [style.top]=\"pos().top\" [style.bottom]=\"pos().bottom\" [style.max-height.px]=\"pos().maxHeight\"\n (mousedown)=\"$event.preventDefault()\">\n @for (o of filtered(); track o.value; let i = $index) {\n <li role=\"option\" [class.active]=\"i === active()\" [class.selected]=\"o.value === value()\"\n [attr.aria-selected]=\"o.value === value()\" (mouseenter)=\"active.set(i)\" (click)=\"pick(o)\">{{ o.label }}</li>\n } @empty {\n <li class=\"kc-ss-empty\">ไม่พบรายการที่ตรงกับ \"{{ query() }}\"</li>\n }\n </ul>\n }\n `,\n // ค่า fallback ของตัวแปรใส่ไว้ตรงที่ใช้ (ไม่ประกาศตัวแปรบน .kc-ss) เพื่อให้ค่าที่ตั้งจากกล่องแม่สืบทอดลงมาได้\n // รายการ z-index 10050 สูงกว่า modal (≤ 9999) และ popup ปฏิทิน (10000)\n styles: `\n .kc-ss {\n position: relative;\n display: block;\n width: 100%;\n min-width: 0;\n }\n .kc-ss-input {\n width: 100%;\n height: var(--kc-ss-height, 36px);\n box-sizing: border-box;\n margin: 0;\n padding: 0 30px 0 10px;\n font-family: inherit;\n font-size: var(--kc-ss-font-size, 14px);\n color: var(--kc-ss-text, #212529);\n background: var(--kc-ss-bg, #fff);\n border: 1px solid var(--kc-ss-border, #ced4da);\n border-radius: var(--kc-ss-radius, 4px);\n text-overflow: ellipsis;\n cursor: pointer;\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .kc-ss-input:focus {\n border-color: var(--kc-ss-accent, #3f51b5);\n box-shadow: 0 0 0 3px var(--kc-ss-accent-soft, rgba(63, 81, 181, 0.15));\n }\n .kc-ss.open .kc-ss-input { cursor: text; }\n .kc-ss.disabled .kc-ss-input {\n cursor: not-allowed;\n color: var(--kc-ss-muted, #6c757d);\n background: var(--kc-ss-disabled-bg, #f0f0f0);\n }\n .kc-ss-arrow {\n position: absolute;\n right: 10px;\n top: 50%;\n width: 12px;\n height: 12px;\n transform: translateY(-50%);\n color: var(--kc-ss-muted, #6c757d);\n cursor: pointer;\n transition: transform 0.15s;\n }\n .kc-ss.open .kc-ss-arrow { transform: translateY(-50%) rotate(180deg); }\n .kc-ss.disabled .kc-ss-arrow { pointer-events: none; opacity: 0.5; }\n .kc-ss-list {\n position: fixed;\n z-index: var(--kc-ss-z-index, 10050);\n margin: 0;\n padding: 4px 0;\n list-style: none;\n overflow-y: auto;\n box-sizing: border-box;\n background: var(--kc-ss-bg, #fff);\n border: 1px solid var(--kc-ss-border, #ced4da);\n border-radius: 6px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);\n font-size: var(--kc-ss-font-size, 14px);\n line-height: 1.45;\n color: var(--kc-ss-text, #212529);\n text-align: left;\n white-space: normal;\n font-weight: 400;\n }\n .kc-ss-list li {\n padding: 6px 12px;\n cursor: pointer;\n overflow-wrap: anywhere;\n }\n .kc-ss-list li.selected { color: var(--kc-ss-accent, #3f51b5); font-weight: 600; }\n .kc-ss-list li.active { background: var(--kc-ss-accent-soft, rgba(63, 81, 181, 0.12)); }\n .kc-ss-list li.kc-ss-empty { color: var(--kc-ss-muted, #6c757d); cursor: default; background: none; }\n `,\n})\nexport class SearchSelectComponent implements ControlValueAccessor {\n options = input<SearchOption[]>([]);\n placeholder = input('-- เลือก --');\n searchHint = input('พิมพ์เพื่อค้นหา...');\n inputId = input('');\n\n @ViewChild('box') box?: ElementRef<HTMLInputElement>;\n @ViewChild('list') list?: ElementRef<HTMLUListElement>;\n\n value = signal('');\n query = signal('');\n open = signal(false);\n active = signal(0);\n isDisabled = signal(false);\n pos = signal<{ left: number; width: number; top: string; bottom: string; maxHeight: number }>(\n { left: 0, width: 0, top: 'auto', bottom: 'auto', maxHeight: 300 });\n\n selectedLabel = computed(() => this.options().find(o => o.value === this.value())?.label ?? '');\n filtered = computed(() => {\n const words = this.query().toLowerCase().split(/\\s+/).filter(w => w);\n if (!words.length) return this.options();\n return this.options().filter(o => { const t = o.label.toLowerCase(); return words.every(w => t.includes(w)); });\n });\n\n private readonly doc = inject(DOCUMENT);\n private onChange: (v: string) => void = () => {};\n private onTouched: () => void = () => {};\n\n writeValue(v: unknown) { this.value.set(v == null ? '' : String(v)); }\n registerOnChange(fn: (v: string) => void) { this.onChange = fn; }\n registerOnTouched(fn: () => void) { this.onTouched = fn; }\n setDisabledState(d: boolean) { this.isDisabled.set(d); if (d) this.open.set(false); }\n\n openList() {\n if (this.open() || this.isDisabled()) return;\n this.query.set('');\n const i = this.options().findIndex(o => o.value === this.value());\n this.active.set(Math.max(i, 0));\n this.place();\n this.open.set(true);\n this.scrollToActive();\n }\n\n toggle(e: MouseEvent) {\n e.preventDefault();\n if (this.isDisabled()) return;\n if (this.open()) { this.open.set(false); return; }\n this.box?.nativeElement.focus();\n this.openList();\n }\n\n onInput(text: string) {\n if (!this.open()) this.openList();\n this.query.set(text);\n this.active.set(0);\n this.place();\n }\n\n onKey(e: KeyboardEvent) {\n const n = this.filtered().length;\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowUp':\n e.preventDefault();\n if (!this.open()) { this.openList(); return; }\n if (n) this.active.set((this.active() + (e.key === 'ArrowDown' ? 1 : n - 1)) % n);\n this.scrollToActive();\n break;\n case 'Enter':\n if (!this.open()) return;\n e.preventDefault();\n if (this.filtered()[this.active()]) this.pick(this.filtered()[this.active()]);\n break;\n case 'Escape':\n if (this.open()) { e.preventDefault(); e.stopPropagation(); this.open.set(false); }\n break;\n case 'Tab':\n this.open.set(false);\n break;\n }\n }\n\n onBlur() {\n this.open.set(false);\n this.onTouched();\n }\n\n pick(o: SearchOption) {\n this.open.set(false);\n if (o.value !== this.value()) {\n this.value.set(o.value);\n this.onChange(o.value);\n }\n }\n\n // เลื่อน (หน้า/modal/กล่องใดๆ — ฟังแบบ capture) หรือย่อขยายหน้าระหว่างเปิด = คำนวณตำแหน่งใหม่ (ยกเว้นเลื่อนในรายการเอง)\n private readonly reposition = (e?: Event) => {\n if (!this.open() || (e?.target instanceof Node && this.list?.nativeElement.contains(e.target))) return;\n this.place();\n };\n constructor() {\n const win = this.doc.defaultView;\n this.doc.addEventListener('scroll', this.reposition, true);\n win?.addEventListener('resize', this.reposition);\n inject(DestroyRef).onDestroy(() => {\n this.doc.removeEventListener('scroll', this.reposition, true);\n win?.removeEventListener('resize', this.reposition);\n });\n }\n\n /** วางรายการใต้ช่อง (หรือเหนือช่อง ถ้าที่ด้านล่างไม่พอ) */\n private place() {\n const el = this.box?.nativeElement;\n const win = this.doc.defaultView;\n if (!el || !win) return;\n const r = el.getBoundingClientRect();\n const below = win.innerHeight - r.bottom - 8;\n const above = r.top - 8;\n const up = below < 200 && above > below;\n const width = Math.min(Math.max(r.width, 220), win.innerWidth - 16);\n const left = Math.max(8, Math.min(r.left, win.innerWidth - width - 8));\n this.pos.set({\n left, width,\n top: up ? 'auto' : `${r.bottom + 2}px`,\n bottom: up ? `${win.innerHeight - r.top + 2}px` : 'auto',\n maxHeight: Math.min(300, up ? above : below),\n });\n }\n\n private scrollToActive() {\n setTimeout(() => this.list?.nativeElement.querySelector('li.active')?.scrollIntoView?.({ block: 'nearest' }));\n }\n}\n","import { DestroyRef, DOCUMENT, EnvironmentProviders, inject, provideEnvironmentInitializer } from '@angular/core';\n\n/**\n * รหัส Private Use Area ที่ฟอนต์ไทยบางตัว (เช่น TH SarabunPSK) ใช้เก็บสระบน/ล่างและวรรณยุกต์ที่ขยับตำแหน่ง\n * พอคัดลอกจาก PDF มาวางในเว็บจึงขึ้นเป็น □ — แปลงกลับเป็นตัวอักษรไทยปกติ\n * ตารางนี้ต้องตรงกับฝั่ง API (C#) ของแต่ละโปรเจกต์ ห้ามเปลี่ยนค่า\n */\nconst PUA_MAP: Record<string, string> = {\n '': 'ฐ', // ฐ (ตัดเชิง)\n '': 'ิ', '': 'ี', '': 'ึ', '': 'ื', // ิ ี ึ ื (ขยับซ้าย)\n '': '่', '': '้', '': '๊', '': '๋', '': '์', // ่ ้ ๊ ๋ ์ (ต่ำ-ซ้าย)\n '': '่', '': '้', '': '๊', '': '๋', '': '์', // ่ ้ ๊ ๋ ์ (ต่ำ)\n '': 'ญ', // ญ (ตัดเชิง)\n '': 'ั', '': 'ํ', '': '็', // ั ํ ็ (ขยับซ้าย)\n '': '่', '': '้', '': '๊', '': '๋', '': '์', // ่ ้ ๊ ๋ ์ (ซ้าย)\n '': 'ุ', '': 'ู', '': 'ฺ' // ุ ู ฺ (ต่ำ)\n};\n\n/**\n * แก้ข้อความไทยที่คัดลอกมาจาก PDF: แปลงรหัส PUA (□) กลับเป็นสระ/วรรณยุกต์,\n * ลบช่องว่างที่แทรกหน้าสระบน/ล่าง/วรรณยุกต์ (\"เชื ่อม\") และรวม \"ํา\" (นิคหิต + สระอา) เป็น \"ำ\"\n * ข้อความที่ไม่มีปัญหาคืนตามเดิม\n */\nexport function fixThaiText(text: string): string {\n if (!text) return text;\n return text\n .replace(/[-]/g, c => PUA_MAP[c] ?? c)\n .replace(/[ ]+(?=[ัิ-ฺ็-๎])/g, '') // ช่องว่างหน้าสระบน/ล่าง/วรรณยุกต์\n .replace(/ํา/g, 'ำ'); // ํา → ำ\n}\n\n/**\n * แก้ข้อความไทยจาก PDF ตอนวาง (paste) ลงทุกช่อง input / textarea ของแอป — ใส่ใน app.config.ts ครั้งเดียว\n *\n * ```ts\n * providers: [provideThaiPasteFix()]\n * ```\n *\n * ถ้าข้อความไม่ต้องแก้จะปล่อยให้วางตามปกติ ถ้าต้องแก้จะวางข้อความที่แก้แล้วแทน\n * ผ่าน execCommand('insertText') เพื่อให้ ngModel / formControl ได้ event input และ Ctrl+Z ย้อนได้\n */\nexport function provideThaiPasteFix(): EnvironmentProviders {\n return provideEnvironmentInitializer(() => {\n const doc = inject(DOCUMENT);\n const listener = (e: Event) => onPaste(doc, e as ClipboardEvent);\n doc.addEventListener('paste', listener, true);\n inject(DestroyRef).onDestroy(() => doc.removeEventListener('paste', listener, true));\n });\n}\n\nfunction onPaste(doc: Document, e: ClipboardEvent): void {\n const el = e.target;\n const view = doc.defaultView;\n if (!view || !(el instanceof view.HTMLInputElement || el instanceof view.HTMLTextAreaElement)) return;\n if (el.readOnly || el.disabled) return;\n\n const text = e.clipboardData?.getData('text/plain');\n if (!text) return;\n const fixed = fixThaiText(text);\n if (fixed === text) return;\n\n e.preventDefault();\n // execCommand เลิกแนะนำแล้วแต่ยังเป็นทางเดียวที่แทรกข้อความแล้วเบราว์เซอร์จำไว้ให้ Ctrl+Z ย้อนได้\n if (doc.execCommand?.('insertText', false, fixed)) return;\n // เบราว์เซอร์/สภาพแวดล้อมที่ไม่รองรับ: แทรกเองแล้วแจ้ง Angular ด้วย event input\n // (input บางชนิดเช่น email/number ไม่มี selection และ setRangeText จะ throw — ใส่ต่อท้ายแทน)\n if (el.selectionStart === null || el.selectionEnd === null) el.value += fixed;\n else el.setRangeText(fixed, el.selectionStart, el.selectionEnd, 'end');\n el.dispatchEvent(new Event('input', { bubbles: true }));\n}\n","import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders } from '@angular/core';\n\n/** ค่าที่โปรเจกต์ส่งให้ provideKcAuth() — ทุกค่ายกเว้น appName มีค่าเริ่มต้น */\nexport interface KcAuthOptions {\n /** ชื่อระบบ แสดงบนหน้าเข้าสู่ระบบ/ลงทะเบียน */\n appName: string;\n /** โลโก้บนหน้าเข้าสู่ระบบ/ลงทะเบียน เช่น 'images/logo.png' (ไม่ใส่ = ไม่แสดง) */\n logoUrl?: string;\n /** ต้นทางของ API ค่าเริ่มต้น '/api' — token แนบเฉพาะคำขอที่ขึ้นต้นด้วยค่านี้ */\n apiBase?: string;\n /** หน้าที่พาไปหลังเข้าสู่ระบบ (ถ้าไม่มี returnUrl) และหน้าที่ผู้ไม่ใช่ผู้ดูแลถูกพาไปจากหน้า config ค่าเริ่มต้น '/' */\n homeUrl?: string;\n /** เปิดปุ่ม SSO (OIDC เช่น Keycloak) — label คือชื่อที่แสดงบนปุ่ม ค่าเริ่มต้น 'SSO'; false/ไม่ใส่ = ปิด */\n sso?: false | { label?: string };\n /**\n * เข้าสู่ระบบด้วยชื่อผู้ใช้/รหัสผ่าน ค่าเริ่มต้น true — false = หน้าเข้าสู่ระบบมีแต่ปุ่ม SSO ตรงกลาง\n * (มีผลเฉพาะเมื่อเปิด sso ไว้ ถ้าปิดทั้งคู่จะยังแสดงช่องรหัสผ่าน กันไม่ให้ไม่มีทางเข้าระบบ)\n */\n passwordLogin?: boolean;\n /**\n * การลงทะเบียนผู้ใช้ใหม่\n * 'none' — ไม่เปิดให้สมัครเอง (ผู้ดูแลเพิ่มให้) — ค่าเริ่มต้น\n * 'form' — กรอกฟอร์มเองที่ /register\n * 'sso' — ยืนยันตัวตนผ่าน SSO ก่อน แล้ว API ส่งตั๋วลงทะเบียนมาให้กรอกต่อที่ /register\n */\n register?: 'none' | 'form' | 'sso';\n /** ชื่อเรียกหน่วยงานที่สังกัด เช่น 'หน่วยงาน', 'โรงพยาบาล', 'แผนก' ค่าเริ่มต้น 'หน่วยงาน' */\n orgLabel?: string;\n /** ค่า role ที่ถือว่าเป็นผู้ดูแล (เข้า kcAdminGuard ได้) ค่าเริ่มต้น ['admin'] */\n adminRoles?: string[];\n /** ความยาวรหัสผ่านขั้นต่ำ ค่าเริ่มต้น 4 */\n passwordMinLength?: number;\n /** คำอธิบายคอลัมน์ของแต่ละกลุ่มในหน้า kcllu เช่น { hospital: 'k_code = รหัสหน่วยงาน, k_name = ชื่อหน่วยงาน' } */\n kclluHints?: Record<string, string>;\n}\n\n/** ค่าที่ใช้จริงหลังเติมค่าเริ่มต้น */\nexport interface KcAuthConfig {\n appName: string;\n logoUrl: string;\n apiBase: string;\n homeUrl: string;\n sso: { label: string } | null;\n passwordLogin: boolean;\n register: 'none' | 'form' | 'sso';\n orgLabel: string;\n adminRoles: string[];\n passwordMinLength: number;\n kclluHints: Record<string, string>;\n}\n\nexport const KC_AUTH_CONFIG = new InjectionToken<KcAuthConfig>('KC_AUTH_CONFIG');\n\n/** path ของหน้าที่ไม่ต้องเข้าสู่ระบบ — kcAuthRoutes() สร้าง route ตามนี้ (ห้ามเปลี่ยน = major) */\nexport const KC_AUTH_PATHS = {\n login: '/login',\n register: '/register',\n ssoCallback: '/sso-callback',\n} as const;\n\nexport function resolveKcAuthConfig(o: KcAuthOptions): KcAuthConfig {\n return {\n appName: o.appName,\n logoUrl: o.logoUrl ?? '',\n apiBase: (o.apiBase ?? '/api').replace(/\\/+$/, ''),\n homeUrl: o.homeUrl || '/',\n sso: o.sso ? { label: o.sso.label || 'SSO' } : null,\n passwordLogin: o.passwordLogin !== false || !o.sso,\n register: o.register ?? 'none',\n orgLabel: o.orgLabel || 'หน่วยงาน',\n adminRoles: o.adminRoles ?? ['admin'],\n passwordMinLength: o.passwordMinLength ?? 4,\n kclluHints: o.kclluHints ?? {},\n };\n}\n\n/**\n * ตั้งค่าชุดหน้าบัญชีผู้ใช้ — ใส่ใน app.config.ts ครั้งเดียว คู่กับ kcAuthInterceptor\n * providers: [\n * provideHttpClient(withInterceptors([kcAuthInterceptor])),\n * provideKcAuth({ appName: 'ระบบ...', sso: { label: 'DMS SSO' }, register: 'sso' }),\n * ]\n */\nexport function provideKcAuth(options: KcAuthOptions): EnvironmentProviders {\n return makeEnvironmentProviders([{ provide: KC_AUTH_CONFIG, useValue: resolveKcAuthConfig(options) }]);\n}\n","import { HttpErrorResponse } from '@angular/common/http';\nimport { SearchOption } from '../search-select/search-select.component';\n\n// สัญญากลางกับ API ของทุกโปรเจกต์ (ดู README หัวข้อ \"สัญญา API\") — เปลี่ยนชื่อ/รูปฟิลด์ = major\n// ข้อผิดพลาดทุก endpoint ตอบ { message: string } พร้อม status 4xx/5xx — หน้าจอแสดง message ตรง ๆ\n\n/** ผู้ใช้ที่เข้าสู่ระบบอยู่ (เก็บใน localStorage คู่กับ token) */\nexport interface KcUser {\n id: string;\n username: string;\n fullName: string;\n role: string;\n org: string;\n orgName: string;\n}\n\n/** POST {api}/user/login → KcSession */\nexport interface KcSession {\n token: string;\n user: KcUser;\n}\n\n/** GET {api}/user/sso/authorize-url */\nexport interface KcSsoAuthorize {\n url: string;\n state: string;\n}\n\n/** POST {api}/user/sso/callback { code } */\nexport type KcSsoCallbackResult =\n | ({ mode: 'signed-in' } & KcSession)\n | { mode: 'register'; ticket: string };\n\n/** GET {api}/user/register-info[?ticket=] — ตั๋วจาก SSO ทำให้ได้ชื่อ-สกุล (และหน่วยงาน) ที่ยืนยันแล้ว */\nexport interface KcRegisterInfo {\n /** ชื่อ-สกุลจาก SSO — มีค่า = ช่องชื่อแก้ไม่ได้ */\n fullName?: string;\n /** เลขบัตรประชาชนจาก SSO — มีค่า = แสดงแบบอ่านอย่างเดียว */\n cid?: string;\n /** ข้อความแจ้งเหนือฟอร์ม เช่น \"พบข้อมูลบุคลากรของท่านในระบบแล้ว แต่ยังไม่มีบัญชีผู้ใช้...\" */\n notice?: string;\n org?: string;\n /** true = หน่วยงานถูกกำหนดแล้ว (เช่นมีข้อมูลบุคคลเดิม) แก้ไม่ได้ */\n orgLocked?: boolean;\n /** หน่วยงานที่เลือกได้ — ว่าง = ระบบนี้ไม่ใช้หน่วยงาน (ซ่อนช่อง) */\n orgs: SearchOption[];\n}\n\n/** POST {api}/user/register */\nexport interface KcRegisterRequest {\n ticket?: string;\n fullName: string;\n username: string;\n password: string;\n org: string;\n}\n\n/** มี token + user = เข้าใช้งานได้ทันที, ไม่มี = แสดง message (เช่น รอผู้ดูแลอนุมัติ) */\nexport interface KcRegisterResult {\n message?: string;\n token?: string;\n user?: KcUser;\n}\n\n/** GET {api}/user/me */\nexport interface KcProfile {\n username: string;\n fullName: string;\n org: string;\n orgName: string;\n email: string;\n mobile: string;\n}\n\n/** PUT {api}/user/me — password ว่าง = ไม่เปลี่ยน */\nexport interface KcProfileUpdate {\n username: string;\n email: string;\n mobile: string;\n password: string;\n}\n\n/** แถวในหน้า config ผู้ใช้ */\nexport interface KcConfigUser {\n id: string;\n username: string;\n fullName: string;\n role: string;\n active: boolean;\n org: string;\n orgName: string;\n email: string;\n mobile: string;\n /** ข้อความที่ API จัดรูปแบบแล้ว (แสดงตรง ๆ) หรือ null */\n lastLogin: string | null;\n}\n\n/** GET {api}/config/users */\nexport interface KcConfigUsersResponse {\n users: KcConfigUser[];\n orgs: SearchOption[];\n roles: SearchOption[];\n}\n\n/** PUT {api}/config/users/{id} — password ว่าง = ไม่เปลี่ยน */\nexport interface KcConfigUserUpdate {\n username: string;\n password: string;\n role: string;\n active: boolean;\n org: string;\n}\n\n/** GET {api}/config/kcllu/titles */\nexport interface KclluTitle {\n title: string;\n count: number;\n /** true = ดูได้อย่างเดียว (เช่นกลุ่ม menu) */\n readOnly: boolean;\n}\n\n/** GET {api}/config/kcllu?title= — POST {api}/config/kcllu และ PUT {api}/config/kcllu/{id} ส่งรูปเดียวกัน (+ title) */\nexport interface KclluRow {\n /** k_ser — 0 = แถวใหม่ */\n id: number;\n code: string;\n name: string;\n name1: string;\n value: string;\n grp: string;\n /** k_no เป็นข้อความตัวเลข ('' = ไม่ระบุ) */\n no: string;\n mark: string;\n active: boolean;\n}\n\n/** คำตอบของ endpoint ที่บันทึกข้อมูล */\nexport interface KcMessage {\n message?: string;\n}\n\n/** ข้อความจาก API ({ message }) หรือข้อความสำรอง */\nexport function kcErrorMessage(err: unknown, fallback: string): string {\n if (err instanceof HttpErrorResponse) {\n const m = (err.error as { message?: unknown } | null)?.message;\n if (typeof m === 'string' && m.trim()) return m;\n if (err.status === 0) return 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้ กรุณาลองใหม่อีกครั้ง';\n }\n return fallback;\n}\n","import { DOCUMENT, DestroyRef, Injectable, computed, inject, signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { KC_AUTH_CONFIG, KC_AUTH_PATHS } from './auth.config';\nimport { KcSession, KcUser } from './auth.models';\n\nconst SESSION_KEY = 'kc_session';\n\n// ใช้ระหว่างเดินทางไป-กลับ SSO จึงเก็บใน sessionStorage\nexport const KC_SSO_STATE_KEY = 'kc_sso_state';\nexport const KC_SSO_TICKET_KEY = 'kc_sso_ticket';\nexport const KC_RETURN_URL_KEY = 'kc_return_url';\n\n/** token เป็น JWT ที่มี exp และเลยเวลาแล้ว — token รูปแบบอื่นถือว่ายังไม่หมดอายุ (ให้ API ตัดสินด้วย 401) */\nexport function kcTokenExpired(token: string, now = Date.now()): boolean {\n const part = token.split('.')[1];\n if (!part) return false;\n try {\n const b64 = part.replace(/-/g, '+').replace(/_/g, '/');\n const payload = JSON.parse(atob(b64.padEnd(Math.ceil(b64.length / 4) * 4, '=')));\n return typeof payload?.exp === 'number' && payload.exp * 1000 <= now;\n } catch {\n return false;\n }\n}\n\n/**\n * session ของผู้ใช้ที่เข้าสู่ระบบ — เก็บ { token, user } ใน localStorage คีย์ kc_session\n * แท็บอื่นเข้า/ออกจากระบบ แท็บนี้รู้ด้วย (event storage)\n */\n@Injectable({ providedIn: 'root' })\nexport class KcAuthService {\n private readonly config = inject(KC_AUTH_CONFIG);\n private readonly router = inject(Router);\n private readonly win = inject(DOCUMENT).defaultView;\n\n private readonly state = signal<KcSession | null>(this.read());\n\n readonly session = this.state.asReadonly();\n readonly user = computed<KcUser | null>(() => this.state()?.user ?? null);\n readonly isAdmin = computed(() => {\n const u = this.user();\n return !!u && this.config.adminRoles.includes(u.role);\n });\n\n constructor() {\n const onStorage = (e: StorageEvent) => {\n if (e.key === SESSION_KEY || e.key === null) this.state.set(this.read());\n };\n this.win?.addEventListener('storage', onStorage);\n inject(DestroyRef).onDestroy(() => this.win?.removeEventListener('storage', onStorage));\n }\n\n /** มี session และ token ยังไม่หมดอายุ */\n isLoggedIn(): boolean {\n const s = this.state();\n return !!s && !kcTokenExpired(s.token);\n }\n\n token(): string {\n return this.state()?.token ?? '';\n }\n\n /** เริ่ม session ใหม่ (ล้างของผู้ใช้คนก่อนทั้งหมด) */\n start(session: KcSession): void {\n this.clear();\n this.state.set({ token: session.token, user: { ...session.user } });\n this.storage('localStorage')?.setItem(SESSION_KEY, JSON.stringify(this.state()));\n }\n\n /** แก้ข้อมูลผู้ใช้ใน session เช่น หลังเปลี่ยนชื่อผู้ใช้ที่หน้าข้อมูลส่วนตัว */\n updateUser(patch: Partial<KcUser>): void {\n const s = this.state();\n if (!s) return;\n this.state.set({ token: s.token, user: { ...s.user, ...patch } });\n this.storage('localStorage')?.setItem(SESSION_KEY, JSON.stringify(this.state()));\n }\n\n /** ล้าง session โดยไม่เปลี่ยนหน้า */\n clear(): void {\n this.state.set(null);\n this.storage('localStorage')?.removeItem(SESSION_KEY);\n for (const k of [KC_SSO_STATE_KEY, KC_SSO_TICKET_KEY, KC_RETURN_URL_KEY]) this.storage('sessionStorage')?.removeItem(k);\n }\n\n /** ออกจากระบบแล้วไปหน้าเข้าสู่ระบบ */\n logout(): Promise<boolean> {\n this.clear();\n return this.router.navigateByUrl(KC_AUTH_PATHS.login);\n }\n\n /** url ที่ปลอดภัยสำหรับพากลับหลังเข้าสู่ระบบ — ต้องเป็น path ภายในแอป ไม่ใช่หน้า login/register */\n safeReturnUrl(url: string | null | undefined): string {\n if (!url || !url.startsWith('/') || url.startsWith('//') || url.includes('\\\\')) return this.config.homeUrl;\n const path = url.split(/[?#]/)[0];\n return Object.values(KC_AUTH_PATHS).includes(path as never) ? this.config.homeUrl : url;\n }\n\n // ---- sessionStorage ชั่วคราว (state / ตั๋วของ SSO) ----\n\n setTemp(key: string, value: string): void {\n this.storage('sessionStorage')?.setItem(key, value);\n }\n\n getTemp(key: string): string {\n return this.storage('sessionStorage')?.getItem(key) ?? '';\n }\n\n /** อ่านแล้วลบทิ้ง */\n takeTemp(key: string): string {\n const v = this.getTemp(key);\n this.storage('sessionStorage')?.removeItem(key);\n return v;\n }\n\n /** เปลี่ยนหน้าไป url ภายนอก (หน้า SSO) */\n redirect(url: string): void {\n this.win?.location.assign(url);\n }\n\n private read(): KcSession | null {\n try {\n const s = JSON.parse(this.storage('localStorage')?.getItem(SESSION_KEY) ?? 'null');\n return s && typeof s.token === 'string' && s.user && typeof s.user === 'object' ? s : null;\n } catch {\n return null;\n }\n }\n\n // เบราว์เซอร์ที่ปิด storage (เช่นโหมดส่วนตัวบางตัว) โยน error ตอนเข้าถึง — ถือว่าไม่มี\n private storage(kind: 'localStorage' | 'sessionStorage'): Storage | null {\n try {\n return this.win?.[kind] ?? null;\n } catch {\n return null;\n }\n }\n}\n","import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';\nimport { catchError, throwError } from 'rxjs';\nimport { KC_AUTH_CONFIG, KC_AUTH_PATHS } from './auth.config';\nimport { KcAuthService } from './auth.service';\n\nfunction loginTree(state: RouterStateSnapshot): UrlTree {\n const auth = inject(KcAuthService);\n // มี session แต่ token หมดอายุ = บอกผู้ใช้ว่าหมดเวลา ไม่ใช่แค่เด้งไปหน้าเข้าสู่ระบบเฉย ๆ\n const expired = !!auth.session();\n return inject(Router).createUrlTree([KC_AUTH_PATHS.login], {\n queryParams: { returnUrl: state.url, ...(expired ? { expired: 1 } : {}) },\n });\n}\n\n/** หน้าที่ต้องเข้าสู่ระบบก่อน — ไม่ได้เข้า = ไป /login?returnUrl=... */\nexport const kcAuthGuard: CanActivateFn = (_route, state) =>\n inject(KcAuthService).isLoggedIn() || loginTree(state);\n\n/** หน้าเฉพาะผู้ดูแล (role อยู่ใน adminRoles) — ผู้ใช้ทั่วไปถูกพาไป homeUrl (API ต้องตรวจสิทธิ์ซ้ำเสมอ) */\nexport const kcAdminGuard: CanActivateFn = (_route, state) => {\n const auth = inject(KcAuthService);\n if (!auth.isLoggedIn()) return loginTree(state);\n return auth.isAdmin() || inject(Router).parseUrl(inject(KC_AUTH_CONFIG).homeUrl);\n};\n\nfunction onAnonymousPage(url: string): boolean {\n const path = url.split(/[?#]/)[0];\n return Object.values(KC_AUTH_PATHS).includes(path as never);\n}\n\n/**\n * แนบ Authorization: Bearer <token> กับคำขอที่ขึ้นต้นด้วย apiBase และถ้า API ตอบ 401 ให้ล้าง session\n * แล้วกลับไปหน้าเข้าสู่ระบบ — ยกเว้นตอนอยู่หน้า login / register / sso-callback ที่ให้หน้านั้นแสดงข้อความเอง\n * (เช่นรหัสผ่านผิด หรือตั๋วลงทะเบียนหมดอายุ)\n */\nexport const kcAuthInterceptor: HttpInterceptorFn = (req, next) => {\n const auth = inject(KcAuthService);\n const router = inject(Router);\n const { apiBase } = inject(KC_AUTH_CONFIG);\n\n const isApi = req.url === apiBase || req.url.startsWith(apiBase + '/');\n const token = auth.token();\n const authed = isApi && token && !req.headers.has('Authorization')\n ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })\n : req;\n\n return next(authed).pipe(\n catchError((err: unknown) => {\n if (isApi && err instanceof HttpErrorResponse && err.status === 401 && !onAnonymousPage(router.url)) {\n const returnUrl = router.url;\n auth.clear();\n router.navigate([KC_AUTH_PATHS.login], { queryParams: { expired: 1, returnUrl } });\n }\n return throwError(() => err);\n }),\n );\n};\n","import { CSP_NONCE, DOCUMENT, inject } from '@angular/core';\n\nconst STYLE_ID = 'kcore-auth-styles';\n\n/**\n * สไตล์ของชุดหน้าบัญชีผู้ใช้ (คลาส .kc-auth-*) — ไม่พึ่ง Bootstrap\n * ค่า fallback ของตัวแปรใส่ไว้ตรงที่ใช้ โปรเจกต์ตั้ง --kc-auth-* ที่ :root หรือกล่องแม่เพื่อเปลี่ยนสีได้\n * selector เป็นคลาสเดียว (specificity ต่ำ) เขียนทับใน CSS ของโปรเจกต์ได้ง่าย\n */\nexport const AUTH_CSS = `\n.kc-auth-screen, .kc-auth-page, .kc-auth-modal-bg {\n font-size: var(--kc-auth-font-size, 14px);\n color: var(--kc-auth-text, #3a2a3a);\n line-height: 1.5;\n}\n.kc-auth-screen *, .kc-auth-page *, .kc-auth-modal-bg * { box-sizing: border-box; }\n\n/* ---- หน้าเต็มจอ (login / register / sso-callback) ---- */\n.kc-auth-screen {\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px 16px;\n background: var(--kc-auth-page-bg, linear-gradient(160deg, #fff7ff 0%, #ffe6ff 100%));\n}\n.kc-auth-card {\n width: 100%;\n max-width: 380px;\n padding: 28px 28px 24px;\n background: var(--kc-auth-bg, #fff);\n border: 1px solid var(--kc-auth-border, #f3e1f3);\n border-radius: var(--kc-auth-radius, 14px);\n box-shadow: 0 12px 36px rgba(204, 102, 204, 0.12);\n}\n.kc-auth-card.wide { max-width: 560px; }\n.kc-auth-card.center { text-align: center; }\n.kc-auth-logo { display: block; max-height: 72px; max-width: 100%; margin: 0 auto 12px; }\n.kc-auth-title { margin: 0; font-size: 1.35em; font-weight: 600; text-align: center; }\n.kc-auth-subtitle { margin: 4px 0 20px; color: var(--kc-auth-muted, #806f80); text-align: center; }\n.kc-auth-footer { margin: 18px 0 0; text-align: center; color: var(--kc-auth-muted, #806f80); font-size: 0.93em; }\n\n/* ---- หน้าในแอป (ข้อมูลส่วนตัว / config) ---- */\n.kc-auth-page { display: block; max-width: var(--kc-auth-page-width, 1100px); margin: 0 auto; }\n.kc-auth-page.narrow { max-width: 640px; }\n.kc-auth-page.single { max-width: 460px; }\n.kc-auth-page.single .kc-auth-page-title { margin-bottom: 16px; }\n.kc-auth-page-header {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-end;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 14px;\n}\n.kc-auth-page-title { margin: 0; font-size: 1.3em; font-weight: 600; }\n.kc-auth-page-subtitle { margin: 2px 0 0; color: var(--kc-auth-muted, #806f80); }\n.kc-auth-panel {\n position: relative;\n margin-bottom: 14px;\n padding: 18px;\n background: var(--kc-auth-bg, #fff);\n border: 1px solid var(--kc-auth-border, #f3e1f3);\n border-radius: var(--kc-auth-radius, 14px);\n}\n.kc-auth-panel.flush { padding: 0; overflow: hidden; }\n.kc-auth-panel-title { margin: 0 0 12px; font-size: 1.05em; font-weight: 600; }\n\n/* ---- ฟอร์ม ---- */\n.kc-auth-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 14px; }\n.kc-auth-grid > .full { grid-column: 1 / -1; }\n.kc-auth-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; min-width: 0; }\n.kc-auth-field > span, .kc-auth-label { font-size: 0.93em; font-weight: 500; }\n.kc-auth-field .req { color: var(--kc-auth-danger, #c62828); }\n.kc-auth-input {\n width: 100%;\n height: var(--kc-auth-input-height, 40px);\n margin: 0;\n padding: 0 10px;\n font: inherit;\n color: inherit;\n background: var(--kc-auth-bg, #fff);\n border: 1px solid var(--kc-auth-input-border, #e9cde9);\n border-radius: 8px;\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n}\n.kc-auth-input:focus {\n border-color: var(--kc-auth-accent, #a23ea2);\n box-shadow: 0 0 0 3px var(--kc-auth-accent-soft, rgba(230, 140, 230, 0.22));\n}\n.kc-auth-input:disabled, .kc-auth-input[readonly] {\n color: var(--kc-auth-muted, #806f80);\n background: var(--kc-auth-disabled-bg, #f8f1f8);\n}\n/* ช่องรหัสผ่าน + ปุ่มดวงตา (<kc-password>) */\n.kc-auth-pw { position: relative; display: block; }\n.kc-auth-pw > .kc-auth-input { padding-right: 42px; }\n.kc-auth-pw > input::-ms-reveal, .kc-auth-pw > input::-ms-clear { display: none; }\n.kc-auth-pw-toggle {\n position: absolute;\n top: 50%;\n right: 5px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n color: var(--kc-auth-muted, #806f80);\n background: none;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n transform: translateY(-50%);\n}\n.kc-auth-pw-toggle:hover, .kc-auth-pw-toggle[aria-pressed=\"true\"] { color: var(--kc-auth-accent, #a23ea2); }\n.kc-auth-pw-toggle:focus-visible { outline: 2px solid var(--kc-auth-accent, #a23ea2); outline-offset: 1px; }\n.kc-auth-pw-toggle svg { width: 19px; height: 19px; }\n.kc-auth-input.invalid { border-color: var(--kc-auth-danger, #c62828); }\n.kc-auth-field .kc-ss {\n --kc-ss-height: var(--kc-auth-input-height, 40px);\n --kc-ss-radius: 8px;\n --kc-ss-accent: var(--kc-auth-accent, #a23ea2);\n --kc-ss-accent-soft: var(--kc-auth-accent-soft, rgba(230, 140, 230, 0.22));\n --kc-ss-border: var(--kc-auth-input-border, #e9cde9);\n --kc-ss-text: var(--kc-auth-text, #3a2a3a);\n}\n.kc-auth-value { padding: 8px 0; }\n.kc-auth-field-error { color: var(--kc-auth-danger, #c62828); font-size: 0.86em; }\n.kc-auth-hint { margin: 2px 0 0; color: var(--kc-auth-muted, #806f80); font-size: 0.86em; }\n.kc-auth-check { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }\n\n/* ---- ปุ่ม ---- */\n.kc-auth-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 6px; }\n.kc-auth-actions.center { justify-content: center; }\n.kc-auth-actions.center > .kc-auth-btn { min-width: 120px; }\n.kc-auth-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: var(--kc-auth-input-height, 40px);\n padding: 0 16px;\n font: inherit;\n font-weight: 500;\n color: var(--kc-auth-text, #3a2a3a);\n background: var(--kc-auth-bg, #fff);\n border: 1px solid var(--kc-auth-input-border, #e9cde9);\n border-radius: 8px;\n cursor: pointer;\n text-decoration: none;\n white-space: nowrap;\n transition: filter 0.15s, background 0.15s;\n}\n.kc-auth-btn:hover:not(:disabled) { filter: brightness(0.96); }\n.kc-auth-btn:disabled, .kc-auth-btn.disabled { opacity: 0.6; cursor: not-allowed; pointer-events: none; }\n.kc-auth-btn.primary {\n color: var(--kc-auth-primary-text, #6b2c6b);\n background: var(--kc-auth-primary-bg, #ffccff);\n border-color: var(--kc-auth-primary-border, #f0aef0);\n font-weight: 600;\n box-shadow: 0 4px 12px rgba(230, 140, 230, 0.25);\n}\n.kc-auth-btn.outline {\n color: var(--kc-auth-accent, #a23ea2);\n background: var(--kc-auth-accent-tint, #fff);\n border-color: var(--kc-auth-accent-border, #f0c2f0);\n}\n/* ปุ่มเข้าสู่ระบบ + SSO บรรทัดเดียวกัน แบ่งเท่ากัน */\n.kc-auth-btn-row { display: flex; gap: 10px; margin-top: 6px; }\n.kc-auth-btn-row > .kc-auth-btn { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n/* ปิดรหัสผ่าน: ปุ่ม SSO ปุ่มเดียวตรงกลาง */\n.kc-auth-sso-only { display: flex; justify-content: center; padding: 8px 0 4px; }\n.kc-auth-sso-only > .kc-auth-btn { min-width: 75%; height: 46px; padding: 0 24px; font-size: 1.05em; }\n.kc-auth-btn.block { display: flex; width: 100%; }\n.kc-auth-btn.sm { height: 30px; padding: 0 10px; font-size: 0.93em; }\n.kc-auth-link { color: var(--kc-auth-accent, #a23ea2); text-decoration: none; cursor: pointer; }\n.kc-auth-link:hover { text-decoration: underline; }\n\n/* ---- ข้อความแจ้ง ---- */\n.kc-auth-alert {\n margin-bottom: 14px;\n padding: 10px 12px;\n border-radius: 6px;\n border: 1px solid transparent;\n font-size: 0.93em;\n}\n.kc-auth-alert.error { color: #8a1c1c; background: #fdecea; border-color: #f5c2c0; }\n.kc-auth-alert.success { color: #1e5b2a; background: #e8f5e9; border-color: #b9dfbf; }\n.kc-auth-alert.warn { color: #6d4c1f; background: #fff8e6; border-color: #f3dfb0; }\n.kc-auth-alert.info { color: #6b2c6b; background: #fff0ff; border-color: #f5d0f5; }\n.kc-auth-loading {\n position: absolute;\n inset: 0;\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--kc-auth-muted, #806f80);\n background: rgba(255, 255, 255, 0.7);\n}\n\n/* ---- ตาราง (จอแคบเรียงเป็นการ์ด) ---- */\n.kc-auth-table-wrap { overflow-x: auto; }\n.kc-auth-table { width: 100%; border-collapse: collapse; }\n.kc-auth-table th, .kc-auth-table td {\n padding: 9px 12px;\n text-align: left;\n vertical-align: top;\n border-bottom: 1px solid var(--kc-auth-border, #f3e1f3);\n}\n.kc-auth-table th {\n font-size: 0.9em;\n font-weight: 600;\n color: var(--kc-auth-muted, #806f80);\n background: var(--kc-auth-head-bg, #fdf5fd);\n white-space: nowrap;\n}\n.kc-auth-table tbody tr.clickable { cursor: pointer; }\n.kc-auth-table tbody tr.clickable:hover { background: var(--kc-auth-row-hover, #fff0ff); }\n.kc-auth-table tbody tr.inactive { opacity: 0.55; }\n.kc-auth-table .c { text-align: center; }\n.kc-auth-table .empty { padding: 24px; text-align: center; color: var(--kc-auth-muted, #806f80); }\n.kc-auth-badge {\n display: inline-block;\n padding: 1px 10px;\n border-radius: 999px;\n font-size: 0.86em;\n white-space: nowrap;\n}\n.kc-auth-badge.on { color: #1e5b2a; background: #e8f5e9; }\n.kc-auth-badge.off { color: #806f80; background: #eceff1; }\n\n/* ---- popup แก้ไข ---- */\n.kc-auth-modal-bg {\n position: fixed;\n inset: 0;\n z-index: var(--kc-auth-z-index, 9000);\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding: 6vh 16px 16px;\n overflow-y: auto;\n background: rgba(70, 30, 70, 0.30);\n}\n.kc-auth-modal {\n width: 100%;\n max-width: 620px;\n background: var(--kc-auth-bg, #fff);\n border-radius: var(--kc-auth-radius, 14px);\n box-shadow: 0 20px 50px rgba(120, 50, 120, 0.20);\n}\n.kc-auth-modal-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 14px 18px;\n border-bottom: 1px solid var(--kc-auth-border, #f3e1f3);\n font-weight: 600;\n}\n.kc-auth-modal-body { padding: 16px 18px 18px; }\n.kc-auth-close {\n width: 30px;\n height: 30px;\n padding: 0;\n font-size: 20px;\n line-height: 1;\n color: var(--kc-auth-muted, #806f80);\n background: none;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.kc-auth-close:hover { background: var(--kc-auth-disabled-bg, #f8f1f8); }\n\n@media (max-width: 640px) {\n .kc-auth-grid { grid-template-columns: minmax(0, 1fr); }\n .kc-auth-card { padding: 22px 18px 18px; }\n .kc-auth-table.stack thead { display: none; }\n .kc-auth-table.stack tr { display: block; padding: 8px 0; border-bottom: 1px solid var(--kc-auth-border, #f3e1f3); }\n .kc-auth-table.stack td { display: flex; gap: 10px; padding: 3px 12px; border: 0; text-align: left; }\n .kc-auth-table.stack td[data-label]::before {\n content: attr(data-label);\n flex: 0 0 110px;\n color: var(--kc-auth-muted, #806f80);\n }\n .kc-auth-table.stack td.empty { display: block; }\n}\n`;\n\n/** เรียกใน injection context (field ของ component) — ใส่ <style> ครั้งแรกครั้งเดียว */\nexport function ensureAuthStyles(): void {\n const doc = inject(DOCUMENT);\n if (!doc.head || doc.getElementById(STYLE_ID)) return;\n const style = doc.createElement('style');\n style.id = STYLE_ID;\n const nonce = inject(CSP_NONCE, { optional: true });\n if (nonce) style.setAttribute('nonce', nonce);\n style.textContent = AUTH_CSS;\n doc.head.appendChild(style);\n}\n","import { AfterContentInit, Component, ElementRef, inject, signal } from '@angular/core';\nimport { ensureAuthStyles } from './auth-styles';\n\n/**\n * ครอบช่องรหัสผ่านเพื่อเพิ่มปุ่มรูปดวงตาท้ายช่อง กดสลับดู/ซ่อนรหัสที่พิมพ์\n * <kc-password><input class=\"kc-auth-input\" type=\"password\" formControlName=\"password\" /></kc-password>\n * ไม่ยุ่งกับค่าในฟอร์ม (ใช้ได้ทั้ง formControlName / ngModel) — แค่สลับ type ของ <input> ข้างใน\n */\n@Component({\n selector: 'kc-password',\n standalone: true,\n host: { class: 'kc-auth-pw' },\n template: `\n <ng-content />\n <button type=\"button\" class=\"kc-auth-pw-toggle\" [attr.aria-label]=\"shown() ? 'ซ่อนรหัสผ่าน' : 'แสดงรหัสผ่าน'\"\n [attr.aria-pressed]=\"shown()\" [attr.title]=\"shown() ? 'ซ่อนรหัสผ่าน' : 'แสดงรหัสผ่าน'\"\n (mousedown)=\"$event.preventDefault()\" (click)=\"toggle()\">\n @if (shown()) {\n <!-- ตาปิด (กด = ซ่อน) -->\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M3 3l18 18\" />\n <path d=\"M10.6 5.1A9.8 9.8 0 0 1 12 5c5 0 8.6 4.2 9.7 6.2a1.7 1.7 0 0 1 0 1.6 15 15 0 0 1-2.6 3.4\" />\n <path d=\"M6.6 6.6A15.4 15.4 0 0 0 2.3 11.2a1.7 1.7 0 0 0 0 1.6C3.4 14.8 7 19 12 19a9.6 9.6 0 0 0 5.4-1.6\" />\n <path d=\"M9.9 9.9a3 3 0 0 0 4.2 4.2\" />\n </svg>\n } @else {\n <!-- ตาเปิด (กด = แสดง) -->\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M2.3 12.8a1.7 1.7 0 0 1 0-1.6C3.4 9.2 7 5 12 5s8.6 4.2 9.7 6.2a1.7 1.7 0 0 1 0 1.6C20.6 14.8 17 19 12 19s-8.6-4.2-9.7-6.2z\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n }\n </button>\n `,\n})\nexport class KcPasswordToggleComponent implements AfterContentInit {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n protected readonly shown = signal(false);\n private input: HTMLInputElement | null = null;\n\n constructor() {\n ensureAuthStyles();\n }\n\n ngAfterContentInit(): void {\n this.input = this.host.nativeElement.querySelector('input');\n }\n\n protected toggle(): void {\n if (!this.input) return;\n this.shown.update((v) => !v);\n this.input.type = this.shown() ? 'text' : 'password';\n // mousedown ถูกกันไว้ เคอร์เซอร์จึงยังอยู่ในช่อง พิมพ์ต่อได้ทันที\n }\n}\n","import { HttpClient, HttpErrorResponse } from '@angular/common/http';\nimport { Component, OnInit, inject, signal } from '@angular/core';\nimport { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { ActivatedRoute, Router, RouterLink } from '@angular/router';\nimport { ensureAuthStyles } from './auth-styles';\nimport { KcPasswordToggleComponent } from './password-toggle.component';\nimport { KC_AUTH_CONFIG, KC_AUTH_PATHS } from './auth.config';\nimport { KcSession, KcSsoAuthorize, kcErrorMessage } from './auth.models';\nimport { KC_RETURN_URL_KEY, KC_SSO_STATE_KEY, KcAuthService } from './auth.service';\n\n/**\n * หน้าเข้าสู่ระบบ (route /login จาก kcAuthRoutes())\n * ชื่อผู้ใช้/รหัสผ่าน → POST {api}/user/login → KcSession\n * ปุ่ม SSO (ถ้าเปิด) → GET {api}/user/sso/authorize-url → ไปหน้า SSO แล้วกลับมาที่ /sso-callback\n * query: returnUrl = หน้าที่จะกลับไปหลังเข้าสู่ระบบ, expired=1 = แจ้งหมดเวลาการใช้งาน\n * เนื้อหาที่ใส่ไว้ใน <kc-login>...</kc-login> แสดงท้ายการ์ด (เช่นเบอร์ติดต่อผู้ดูแล)\n */\n@Component({\n selector: 'kc-login',\n standalone: true,\n imports: [ReactiveFormsModule, RouterLink, KcPasswordToggleComponent],\n template: `\n <div class=\"kc-auth-screen\">\n <div class=\"kc-auth-card\">\n @if (config.logoUrl) {\n <img class=\"kc-auth-logo\" [src]=\"config.logoUrl\" alt=\"\" />\n }\n <h1 class=\"kc-auth-title\">{{ config.appName }}</h1>\n <p class=\"kc-auth-subtitle\">ลงชื่อเข้าสู่ระบบ</p>\n\n @if (message(); as m) {\n <div class=\"kc-auth-alert\" [class.error]=\"m.error\" [class.info]=\"!m.error\" role=\"alert\">{{ m.text }}</div>\n }\n\n @if (config.passwordLogin) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"kc-auth-field\">\n <span>ชื่อผู้ใช้</span>\n <input class=\"kc-auth-input\" type=\"text\" formControlName=\"username\" autocomplete=\"username\" />\n </label>\n <label class=\"kc-auth-field\">\n <span>รหัสผ่าน</span>\n <kc-password><input class=\"kc-auth-input\" type=\"password\" formControlName=\"password\" autocomplete=\"current-password\" /></kc-password>\n </label>\n <!-- ปุ่มเข้าสู่ระบบกับ SSO อยู่บรรทัดเดียวกัน แบ่งครึ่ง — ปุ่ม SSO ใช้แค่ชื่อเพื่อให้พอดีครึ่งการ์ด -->\n <div class=\"kc-auth-btn-row\">\n <button type=\"submit\" class=\"kc-auth-btn primary\" [disabled]=\"!!busy()\">\n {{ busy() === 'password' ? 'กำลังเข้าสู่ระบบ...' : 'เข้าสู่ระบบ' }}\n </button>\n @if (config.sso; as sso) {\n <button type=\"button\" class=\"kc-auth-btn outline\" [disabled]=\"!!busy()\" (click)=\"startSso()\"\n [attr.title]=\"'เข้าสู่ระบบด้วย ' + sso.label\">\n {{ busy() === 'sso' ? 'กำลังเชื่อมต่อ...' : sso.label }}\n </button>\n }\n </div>\n </form>\n } @else if (config.sso; as sso) {\n <!-- ปิดรหัสผ่าน: มีแต่ปุ่ม SSO ตรงกลาง -->\n <div class=\"kc-auth-sso-only\">\n <button type=\"button\" class=\"kc-auth-btn primary\" [disabled]=\"!!busy()\" (click)=\"startSso()\">\n {{ busy() === 'sso' ? 'กำลังเชื่อมต่อ...' : 'เข้าสู่ระบบด้วย ' + sso.label }}\n </button>\n </div>\n }\n\n @if (config.register === 'form') {\n <p class=\"kc-auth-footer\">ยังไม่มีบัญชี? <a class=\"kc-auth-link\" [routerLink]=\"paths.register\">ลงทะเบียน</a></p>\n } @else if (config.register === 'sso' && config.sso) {\n <p class=\"kc-auth-footer\">ผู้ใช้ใหม่ กดปุ่ม {{ config.sso.label }} เพื่อลงทะเบียน</p>\n }\n <ng-content />\n </div>\n </div>\n `,\n})\nexport class KcLoginComponent implements OnInit {\n protected readonly config = inject(KC_AUTH_CONFIG);\n protected readonly paths = KC_AUTH_PATHS;\n private readonly auth = inject(KcAuthService);\n private readonly http = inject(HttpClient);\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n\n protected readonly busy = signal<'' | 'password' | 'sso'>('');\n protected readonly message = signal<{ text: string; error: boolean } | null>(null);\n\n protected readonly form = inject(FormBuilder).nonNullable.group({\n username: ['', Validators.required],\n password: ['', Validators.required],\n });\n\n constructor() {\n ensureAuthStyles();\n }\n\n private get returnUrl(): string {\n return this.auth.safeReturnUrl(this.route.snapshot.queryParamMap.get('returnUrl'));\n }\n\n ngOnInit(): void {\n if (this.route.snapshot.queryParamMap.get('expired') === '1') {\n this.message.set({ text: 'หมดเวลาการใช้งาน กรุณาเข้าสู่ระบบใหม่', error: false });\n } else if (this.auth.isLoggedIn()) {\n // เข้าสู่ระบบอยู่แล้ว (เช่นกด back กลับมา) — ไม่ต้องกรอกซ้ำ\n this.router.navigateByUrl(this.returnUrl);\n }\n }\n\n protected submit(): void {\n if (this.busy()) return;\n const v = this.form.getRawValue();\n if (!v.username.trim() || !v.password) {\n this.message.set({ text: 'กรุณากรอกชื่อผู้ใช้และรหัสผ่าน', error: true });\n return;\n }\n\n this.busy.set('password');\n this.message.set(null);\n this.http.post<KcSession>(`${this.config.apiBase}/user/login`, { username: v.username.trim(), password: v.password }).subscribe({\n next: (res) => {\n this.auth.start(res);\n this.router.navigateByUrl(this.returnUrl);\n },\n error: (err) => {\n this.busy.set('');\n this.message.set({ text: kcErrorMessage(err, 'เข้าสู่ระบบไม่สำเร็จ'), error: true });\n },\n });\n }\n\n protected startSso(): void {\n const sso = this.config.sso;\n if (!sso || this.busy()) return;\n this.busy.set('sso');\n this.message.set(null);\n this.http.get<KcSsoAuthorize>(`${this.config.apiBase}/user/sso/authorize-url`).subscribe({\n next: ({ url, state }) => {\n this.auth.setTemp(KC_SSO_STATE_KEY, state);\n this.auth.setTemp(KC_RETURN_URL_KEY, this.returnUrl);\n this.auth.redirect(url);\n },\n error: (err: HttpErrorResponse) => {\n this.busy.set('');\n // API ยังไม่ได้ตั้งค่า client ของ SSO จะตอบ 404 — บอกตรง ๆ แทนพาไปเจอ error ที่หน้า SSO\n this.message.set({\n text: err.status === 404\n ? `ระบบยังไม่ได้ตั้งค่าการเข้าสู่ระบบด้วย ${sso.label}`\n : kcErrorMessage(err, `ไม่สามารถเชื่อมต่อ ${sso.label} ได้ กรุณาลองใหม่อีกครั้ง`),\n error: true,\n });\n },\n });\n }\n}\n","// ตรวจข้อมูลฝั่งหน้าจอเพื่อบอกผู้ใช้ได้ทันที — API ต้องตรวจซ้ำทุกข้อเสมอ\n// ทุกฟังก์ชันคืนข้อความปัญหาแรกที่เจอ หรือ '' ถ้าผ่าน\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst MOBILE_RE = /^0\\d{9}$/;\n\nexport function kcUsernameProblem(username: string): string {\n const u = username.trim();\n if (!u) return 'กรุณากรอกชื่อผู้ใช้';\n if (/\\s/.test(u)) return 'ชื่อผู้ใช้ห้ามมีช่องว่าง';\n if (u.length > 50) return 'ชื่อผู้ใช้ยาวได้ไม่เกิน 50 ตัวอักษร';\n return '';\n}\n\n/** required = false: เว้นว่างได้ (= ไม่เปลี่ยนรหัสผ่าน) */\nexport function kcPasswordProblem(password: string, confirm: string, minLength: number, required: boolean): string {\n if (!password && !required && !confirm) return '';\n if (password.length < minLength) return `กรุณากรอกรหัสผ่านอย่างน้อย ${minLength} ตัวอักษร`;\n if (password !== confirm) return 'รหัสผ่านทั้งสองช่องไม่ตรงกัน';\n return '';\n}\n\nexport function kcContactProblem(email: string, mobile: string): string {\n if (!EMAIL_RE.test(email.trim())) return 'กรุณากรอกอีเมลให้ถูกต้อง';\n if (!MOBILE_RE.test(mobile.trim())) return 'กรุณากรอกเบอร์มือถือ 10 หลัก ขึ้นต้นด้วย 0';\n return '';\n}\n","import { HttpClient } from '@angular/common/http';\nimport { Component, OnInit, computed, inject, signal } from '@angular/core';\nimport { FormBuilder, ReactiveFormsModule } from '@angular/forms';\nimport { Router, RouterLink } from '@angular/router';\nimport { SearchOption, SearchSelectComponent } from '../search-select/search-select.component';\nimport { ensureAuthStyles } from './auth-styles';\nimport { KcPasswordToggleComponent } from './password-toggle.component';\nimport { kcPasswordProblem, kcUsernameProblem } from './auth-validation';\nimport { KC_AUTH_CONFIG, KC_AUTH_PATHS } from './auth.config';\nimport { KcRegisterInfo, KcRegisterRequest, KcRegisterResult, kcErrorMessage } from './auth.models';\nimport { KC_SSO_TICKET_KEY, KcAuthService } from './auth.service';\n\n/**\n * หน้าลงทะเบียนผู้ใช้ใหม่ (route /register) — รูปแบบตาม ictprocure: กล่องเดียว ช่องเรียงแถวเดียว\n * ทำงานตาม register ใน provideKcAuth()\n * 'sso' — ต้องมาจาก /sso-callback พร้อมตั๋ว ชื่อ-สกุล / เลขบัตรประชาชน (และหน่วยงานถ้า orgLocked) มาจาก API แก้ไม่ได้\n * 'form' — กรอกชื่อ-สกุลเอง\n * 'none' — แจ้งว่าไม่เปิดให้ลงทะเบียน\n * อีเมล / มือถือไม่ถามตอนลงทะเบียน — ผู้ใช้กรอกต่อที่หน้าบัญชีของฉัน\n * GET {api}/user/register-info[?ticket=] → KcRegisterInfo, POST {api}/user/register ← KcRegisterRequest\n */\n@Component({\n selector: 'kc-register',\n standalone: true,\n imports: [ReactiveFormsModule, RouterLink, SearchSelectComponent, KcPasswordToggleComponent],\n template: `\n <div class=\"kc-auth-screen\">\n <div class=\"kc-auth-card\">\n <h1 class=\"kc-auth-title\">ลงทะเบียนผู้ใช้ใหม่</h1>\n <p class=\"kc-auth-subtitle\">{{ config.appName }}</p>\n\n @if (loading()) {\n <p class=\"kc-auth-subtitle\">{{ ticket ? 'กำลังตรวจสอบการยืนยันตัวตน...' : 'กำลังโหลดข้อมูล...' }}</p>\n } @else if (done()) {\n <div class=\"kc-auth-alert success\" role=\"status\">{{ done() }}</div>\n <div class=\"kc-auth-actions center\">\n <a class=\"kc-auth-btn primary\" [routerLink]=\"paths.login\">กลับไปหน้าเข้าสู่ระบบ</a>\n </div>\n } @else if (!ready()) {\n <!-- ไม่มีตั๋ว / ตั๋วใช้ไม่ได้ / ไม่เปิดให้ลงทะเบียน — ไม่แสดงฟอร์ม เพราะกรอกไปก็ส่งไม่ผ่าน -->\n <div class=\"kc-auth-alert error\" role=\"alert\">{{ error() }}</div>\n <div class=\"kc-auth-actions center\">\n <a class=\"kc-auth-btn\" [routerLink]=\"paths.login\">กลับไปหน้าเข้าสู่ระบบ</a>\n </div>\n } @else {\n @if (notice()) {\n <div class=\"kc-auth-alert warn\" role=\"status\">{{ notice() }}</div>\n }\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"kc-auth-field\">\n <span>ชื่อ-สกุล @if (fromSso()) { (จาก {{ ssoLabel }}) } @else { <span class=\"req\">*</span> }</span>\n <input class=\"kc-auth-input\" type=\"text\" formControlName=\"fullName\" maxlength=\"200\" autocomplete=\"name\" />\n </label>\n\n @if (cid()) {\n <label class=\"kc-auth-field\">\n <span>เลขบัตรประชาชน</span>\n <input class=\"kc-auth-input\" type=\"text\" [value]=\"cid()\" disabled />\n </label>\n }\n\n @if (orgs().length) {\n @if (orgLocked()) {\n <label class=\"kc-auth-field\">\n <span>{{ config.orgLabel }}ที่สังกัด</span>\n <input class=\"kc-auth-input\" type=\"text\" [value]=\"orgName()\" disabled />\n </label>\n } @else {\n <div class=\"kc-auth-field\">\n <label class=\"kc-auth-label\" for=\"kc-reg-org\">{{ config.orgLabel }}ที่สังกัด <span class=\"req\">*</span></label>\n <app-search-select inputId=\"kc-reg-org\" [options]=\"orgs()\" formControlName=\"org\"\n [placeholder]=\"'-- เลือก' + config.orgLabel + 'ที่สังกัด --'\" />\n </div>\n }\n }\n\n <label class=\"kc-auth-field\">\n <span>ชื่อผู้ใช้ <span class=\"req\">*</span></span>\n <input class=\"kc-auth-input\" type=\"text\" formControlName=\"username\" maxlength=\"50\" autocomplete=\"username\" />\n </label>\n <label class=\"kc-auth-field\">\n <span>รหัสผ่าน <span class=\"req\">*</span></span>\n <kc-password><input class=\"kc-auth-input\" type=\"password\" formControlName=\"password\" maxlength=\"50\" autocomplete=\"new-password\" /></kc-password>\n </label>\n <label class=\"kc-auth-field\">\n <span>ยืนยันรหัสผ่าน <span class=\"req\">*</span></span>\n <kc-password><input class=\"kc-auth-input\" type=\"password\" formControlName=\"confirmPassword\" maxlength=\"50\" autocomplete=\"new-password\" /></kc-password>\n </label>\n\n <div class=\"kc-auth-actions center\">\n <button type=\"submit\" class=\"kc-auth-btn primary\" [disabled]=\"saving()\">\n {{ saving() ? 'กำลังบันทึก...' : 'ลงทะเบียน' }}\n </button>\n <a class=\"kc-auth-btn\" [routerLink]=\"paths.login\" [class.disabled]=\"saving()\">ยกเลิก</a>\n </div>\n\n @if (error()) {\n <div class=\"kc-auth-alert error\" role=\"alert\" style=\"margin: 14px 0 0\">{{ error() }}</div>\n }\n </form>\n }\n </div>\n </div>\n `,\n})\nexport class KcRegisterComponent implements OnInit {\n protected readonly config = inject(KC_AUTH_CONFIG);\n protected readonly paths = KC_AUTH_PATHS;\n protected readonly ssoLabel = this.config.sso?.label ?? 'SSO';\n private readonly auth = inject(KcAuthService);\n private readonly http = inject(HttpClient);\n private readonly router = inject(Router);\n\n protected readonly loading = signal(false);\n /** พร้อมให้กรอก (โหลดข้อมูลสำเร็จ และโหมดเปิดให้ลงทะเบียน) */\n protected readonly ready = signal(false);\n protected readonly saving = signal(false);\n protected readonly error = signal('');\n /** ข้อความหลังลงทะเบียนสำเร็จ — มีค่า = ซ่อนฟอร์มกันกดส่งซ้ำ */\n protected readonly done = signal('');\n protected readonly notice = signal('');\n protected readonly fromSso = signal(false);\n protected readonly cid = signal('');\n protected readonly orgs = signal<SearchOption[]>([]);\n protected readonly orgLocked = signal(false);\n private readonly lockedOrg = signal('');\n protected readonly orgName = computed(() => this.orgs().find((o) => o.value === this.lockedOrg())?.label ?? this.lockedOrg());\n\n protected readonly form = inject(FormBuilder).nonNullable.group({\n fullName: [''],\n org: [''],\n username: [''],\n password: [''],\n confirmPassword: [''],\n });\n\n protected ticket = '';\n\n constructor() {\n ensureAuthStyles();\n }\n\n ngOnInit(): void {\n const mode = this.config.register;\n if (mode === 'none') {\n this.error.set('ระบบนี้ไม่เปิดให้ลงทะเบียนเอง กรุณาติดต่อผู้ดูแลระบบ');\n return;\n }\n if (mode === 'sso') {\n this.ticket = this.auth.getTemp(KC_SSO_TICKET_KEY);\n if (!this.ticket) {\n this.error.set(`ยังไม่ได้ยืนยันตัวตน กรุณากลับไปกด \"เข้าสู่ระบบด้วย ${this.ssoLabel}\" ที่หน้าเข้าสู่ระบบก่อน`);\n return;\n }\n }\n\n this.loading.set(true);\n const params: Record<string, string> = this.ticket ? { ticket: this.ticket } : {};\n this.http.get<KcRegisterInfo>(`${this.config.apiBase}/user/register-info`, { params }).subscribe({\n next: (info) => {\n this.orgs.set(info.orgs ?? []);\n this.cid.set(info.cid ?? '');\n this.notice.set(info.notice ?? '');\n this.form.patchValue({ fullName: info.fullName ?? '', org: info.org ?? '' });\n if (info.fullName) {\n this.fromSso.set(true);\n this.form.controls.fullName.disable();\n }\n if (info.orgLocked && info.org) {\n this.orgLocked.set(true);\n this.lockedOrg.set(info.org);\n }\n this.loading.set(false);\n this.ready.set(true);\n },\n error: (err) => {\n this.loading.set(false);\n // ตั๋วหมดอายุ / มีบัญชีอยู่แล้ว — ล้างทิ้งไม่ให้ค้าง\n if (this.ticket) this.auth.takeTemp(KC_SSO_TICKET_KEY);\n this.error.set(kcErrorMessage(err, 'ไม่สามารถเริ่มการลงทะเบียนได้'));\n },\n });\n }\n\n protected submit(): void {\n if (this.saving()) return;\n const v = this.form.getRawValue();\n // ตรวจฝั่งหน้าจอก่อนเพื่อบอกผู้ใช้ได้ทันที ฝั่ง API ตรวจซ้ำทุกข้อ\n const problem =\n (!v.fullName.trim() ? 'กรุณากรอกชื่อ-สกุล' : '') ||\n kcUsernameProblem(v.username) ||\n kcPasswordProblem(v.password, v.confirmPassword, this.config.passwordMinLength, true) ||\n (this.orgs().length && !v.org ? `กรุณาเลือก${this.config.orgLabel}ที่สังกัด` : '');\n this.error.set(problem);\n if (problem) return;\n\n const body: KcRegisterRequest = {\n ...(this.ticket ? { ticket: this.ticket } : {}),\n fullName: v.fullName.trim(),\n username: v.username.trim(),\n password: v.password,\n org: v.org,\n };\n\n this.saving.set(true);\n this.http.post<KcRegisterResult>(`${this.config.apiBase}/user/register`, body).subscribe({\n next: (res) => {\n this.saving.set(false);\n if (this.ticket) this.auth.takeTemp(KC_SSO_TICKET_KEY);\n if (res?.token && res.user) {\n this.auth.start({ token: res.token, user: res.user });\n this.router.navigateByUrl(this.config.homeUrl);\n return;\n }\n this.done.set(res?.message || 'ลงทะเบียนเรียบร้อยแล้ว กรุณาเข้าสู่ระบบ');\n },\n error: (err) => {\n this.saving.set(false);\n this.error.set(kcErrorMessage(err, 'ลงทะเบียนไม่สำเร็จ'));\n },\n });\n }\n}\n","import { HttpClient } from '@angular/common/http';\nimport { Component, OnInit, inject, signal } from '@angular/core';\nimport { ActivatedRoute, Router, RouterLink } from '@angular/router';\nimport { ensureAuthStyles } from './auth-styles';\nimport { KC_AUTH_CONFIG, KC_AUTH_PATHS } from './auth.config';\nimport { KcSsoAuthorize, KcSsoCallbackResult, kcErrorMessage } from './auth.models';\nimport { KC_RETURN_URL_KEY, KC_SSO_STATE_KEY, KC_SSO_TICKET_KEY, KcAuthService } from './auth.service';\n\n/**\n * หน้ารับ redirect กลับจาก SSO (route /sso-callback) — path นี้ต้องตรงกับ redirect_uri ที่ API ส่งให้ SSO\n * และที่ลงทะเบียนไว้กับ SSO server\n * ?code=&state= → ตรวจ state → POST {api}/user/sso/callback { code }\n * signed-in → เริ่ม session แล้วกลับหน้าเดิม / homeUrl\n * register → เก็บตั๋วไว้ใน sessionStorage แล้วไป /register\n * ไม่มี code (เช่นเปิดจากเมนูของ SSO portal) → เริ่ม flow ใหม่เอง\n */\n@Component({\n selector: 'kc-sso-callback',\n standalone: true,\n imports: [RouterLink],\n template: `\n <div class=\"kc-auth-screen\">\n <div class=\"kc-auth-card center\">\n <h1 class=\"kc-auth-title\">{{ label }}</h1>\n @if (failed()) {\n <div class=\"kc-auth-alert error\" role=\"alert\" style=\"margin-top: 16px\">{{ message() }}</div>\n <a class=\"kc-auth-btn\" [routerLink]=\"paths.login\">กลับไปหน้าเข้าสู่ระบบ</a>\n } @else {\n <p class=\"kc-auth-subtitle\" style=\"margin-bottom: 0\">{{ message() }}</p>\n }\n </div>\n </div>\n `,\n})\nexport class KcSsoCallbackComponent implements OnInit {\n private readonly config = inject(KC_AUTH_CONFIG);\n private readonly auth = inject(KcAuthService);\n private readonly http = inject(HttpClient);\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n\n protected readonly paths = KC_AUTH_PATHS;\n protected readonly label = this.config.sso?.label ?? 'SSO';\n protected readonly message = signal(`กำลังเข้าสู่ระบบผ่าน ${this.label}...`);\n protected readonly failed = signal(false);\n\n constructor() {\n ensureAuthStyles();\n }\n\n ngOnInit(): void {\n const params = this.route.snapshot.queryParamMap;\n const expectedState = this.auth.takeTemp(KC_SSO_STATE_KEY);\n const code = params.get('code');\n\n if (params.get('error')) {\n this.fail(`${this.label} ปฏิเสธการเข้าสู่ระบบ กรุณาลองใหม่อีกครั้ง`);\n return;\n }\n\n if (!code) {\n // ผู้ใช้มี session ของ SSO อยู่แล้วจึงเด้งกลับมาพร้อม code ทันที\n this.message.set(`กำลังเชื่อมต่อกับ ${this.label}...`);\n this.http.get<KcSsoAuthorize>(`${this.config.apiBase}/user/sso/authorize-url`).subscribe({\n next: ({ url, state }) => {\n this.auth.setTemp(KC_SSO_STATE_KEY, state);\n this.auth.redirect(url);\n },\n error: () => this.fail(`ไม่สามารถเชื่อมต่อ ${this.label} ได้ กรุณาเริ่มเข้าสู่ระบบใหม่`),\n });\n return;\n }\n\n // state ไม่ตรง = คำขอนี้ไม่ได้เริ่มจากเบราว์เซอร์นี้ — ไม่เอา code ไปแลก\n if (!expectedState || expectedState !== params.get('state')) {\n this.fail('คำขอเข้าสู่ระบบไม่ถูกต้อง (state ไม่ตรงกัน) กรุณาเริ่มเข้าสู่ระบบใหม่');\n return;\n }\n\n this.http.post<KcSsoCallbackResult>(`${this.config.apiBase}/user/sso/callback`, { code }).subscribe({\n next: (res) => {\n if (res.mode === 'signed-in' && res.token && res.user) {\n this.auth.start({ token: res.token, user: res.user });\n this.router.navigateByUrl(this.auth.safeReturnUrl(this.auth.takeTemp(KC_RETURN_URL_KEY)), { replaceUrl: true });\n return;\n }\n if (res.mode === 'register' && res.ticket) {\n this.auth.setTemp(KC_SSO_TICKET_KEY, res.ticket);\n this.message.set('ยังไม่มีบัญชีในระบบ กำลังไปหน้าลงทะเบียน...');\n this.router.navigateByUrl(KC_AUTH_PATHS.register, { replaceUrl: true });\n return;\n }\n this.fail('ไม่สามารถเข้าสู่ระบบได้ กรุณาลองใหม่อีกครั้ง');\n },\n error: (err) => this.fail(kcErrorMessage(err, `ไม่สามารถเข้าสู่ระบบผ่าน ${this.label} ได้`)),\n });\n }\n\n private fail(message: string): void {\n this.message.set(message);\n this.failed.set(true);\n }\n}\n","import { Routes } from '@angular/router';\nimport { KcLoginComponent } from './login.component';\nimport { KcRegisterComponent } from './register.component';\nimport { KcSsoCallbackComponent } from './sso-callback.component';\n\n/**\n * route ของหน้าที่ไม่ต้องเข้าสู่ระบบ (อยู่นอก layout ของโปรเจกต์) — ใส่ไว้บนสุดของ routes\n * export const routes: Routes = [\n * ...kcAuthRoutes(),\n * { path: '', component: MainLayoutComponent, children: [\n * { path: 'user/detail', component: KcProfileComponent, canActivate: [kcAuthGuard] },\n * { path: 'config/users', component: KcConfigUsersComponent, canActivate: [kcAdminGuard] },\n * { path: 'config/kcllu', component: KcConfigKclluComponent, canActivate: [kcAdminGuard] },\n * ] },\n * ];\n * path ตรงกับ KC_AUTH_PATHS (login, register, sso-callback)\n */\nexport function kcAuthRoutes(): Routes {\n return [\n { path: 'login', component: KcLoginComponent, title: 'เข้าสู่ระบบ' },\n { path: 'register', component: KcRegisterComponent, title: 'ลงทะเบียน' },\n { path: 'sso-callback', component: KcSsoCallbackComponent, title: 'เข้าสู่ระบบ' },\n ];\n}\n","import { HttpClient } from '@angular/common/http';\nimport { Component, OnInit, inject, signal } from '@angular/core';\nimport { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { ensureAuthStyles } from './auth-styles';\nimport { KcPasswordToggleComponent } from './password-toggle.component';\nimport { kcContactProblem, kcPasswordProblem, kcUsernameProblem } from './auth-validation';\nimport { KC_AUTH_CONFIG } from './auth.config';\nimport { KcMessage, KcProfile, KcProfileUpdate, kcErrorMessage } from './auth.models';\nimport { KcAuthService } from './auth.service';\n\n/**\n * หน้า \"บัญชีของฉัน\" (user/detail) — รูปแบบตาม ictprocure: กล่องเดียวกลางหน้า ช่องเรียงแถวเดียว\n * วางใน layout ของโปรเจกต์พร้อม canActivate: [kcAuthGuard]\n * ชื่อ-สกุลและหน่วยงานแสดงอย่างเดียว (หน่วยงานกำหนดสิทธิ์การเห็นข้อมูล ย้ายได้โดยผู้ดูแลที่หน้า config)\n * แก้ได้: อีเมล มือถือ ชื่อผู้ใช้ รหัสผ่าน — เปลี่ยนรหัสผ่านไม่ต้องกรอกรหัสเดิม เว้นว่าง = ไม่เปลี่ยน\n * GET {api}/user/me → KcProfile, PUT {api}/user/me ← KcProfileUpdate\n */\n@Component({\n selector: 'kc-profile',\n standalone: true,\n imports: [ReactiveFormsModule, KcPasswordToggleComponent],\n template: `\n <div class=\"kc-auth-page single\">\n <div class=\"kc-auth-panel\">\n <h1 class=\"kc-auth-panel-title kc-auth-page-title\">บัญชีของฉัน</h1>\n\n @if (loading()) {\n <p class=\"kc-auth-hint\">กำลังโหลดข้อมูล...</p>\n } @else if (loadFailed()) {\n <div class=\"kc-auth-alert error\" role=\"alert\">{{ error() }}</div>\n } @else {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"kc-auth-field\">\n <span>ชื่อ-สกุล</span>\n <input class=\"kc-auth-input\" type=\"text\" [value]=\"fullName()\" disabled />\n </label>\n\n <label class=\"kc-auth-field\">\n <span>{{ config.orgLabel }}ที่สังกัด</span>\n <input class=\"kc-auth-input\" type=\"text\" [value]=\"orgName()\" disabled />\n <small class=\"kc-auth-hint\">หากต้องการย้าย{{ config.orgLabel }} กรุณาติดต่อผู้ดูแลระบบ</small>\n </label>\n\n <label class=\"kc-auth-field\">\n <span>อีเมล <span class=\"req\">*</span></span>\n <input class=\"kc-auth-input\" type=\"email\" formControlName=\"email\" maxlength=\"100\" autocomplete=\"email\"\n [class.invalid]=\"bad('email')\" />\n @if (bad('email')) {\n <small class=\"kc-auth-field-error\">* รูปแบบอีเมลไม่ถูกต้อง</small>\n }\n </label>\n\n <label class=\"kc-auth-field\">\n <span>เบอร์โทรศัพท์มือถือ <span class=\"req\">*</span></span>\n <input class=\"kc-auth-input\" type=\"tel\" formControlName=\"mobile\" maxlength=\"10\" inputmode=\"numeric\" autocomplete=\"tel\"\n [class.invalid]=\"bad('mobile')\" />\n @if (bad('mobile')) {\n <small class=\"kc-auth-field-error\">* เบอร์มือถือต้องเป็นตัวเลข 10 หลัก และขึ้นต้นด้วย 0</small>\n }\n </label>\n\n <label class=\"kc-auth-field\">\n <span>ชื่อผู้ใช้ <span class=\"req\">*</span></span>\n <input class=\"kc-auth-input\" type=\"text\" formControlName=\"username\" maxlength=\"50\" autocomplete=\"username\"\n [class.invalid]=\"bad('username')\" />\n @if (bad('username')) {\n <small class=\"kc-auth-field-error\">* กรุณากรอกชื่อผู้ใช้ (ห้ามมีช่องว่าง)</small>\n }\n </label>\n\n <label class=\"kc-auth-field\">\n <span>รหัสผ่านใหม่ <small class=\"kc-auth-hint\">(อย่างน้อย {{ config.passwordMinLength }} ตัวอักษร — เว้นว่างถ้าไม่เปลี่ยน)</small></span>\n <kc-password><input class=\"kc-auth-input\" type=\"password\" formControlName=\"password\" maxlength=\"50\" autocomplete=\"new-password\" /></kc-password>\n </label>\n\n <label class=\"kc-auth-field\">\n <span>ยืนยันรหัสผ่านใหม่</span>\n <kc-password><input class=\"kc-auth-input\" type=\"password\" formControlName=\"confirmPassword\" maxlength=\"50\" autocomplete=\"new-password\" /></kc-password>\n </label>\n\n @if (error()) {\n <div class=\"kc-auth-alert error\" role=\"alert\">{{ error() }}</div>\n }\n @if (success()) {\n <div class=\"kc-auth-alert success\" role=\"status\">{{ success() }}</div>\n }\n\n <button type=\"submit\" class=\"kc-auth-btn primary block\" [disabled]=\"saving()\">\n {{ saving() ? 'กำลังบันทึก...' : 'บันทึกข้อมูล' }}\n </button>\n </form>\n }\n </div>\n </div>\n `,\n})\nexport class KcProfileComponent implements OnInit {\n protected readonly config = inject(KC_AUTH_CONFIG);\n private readonly auth = inject(KcAuthService);\n private readonly http = inject(HttpClient);\n\n protected readonly loading = signal(true);\n /** โหลดข้อมูลไม่สำเร็จ — ไม่แสดงฟอร์ม (กันบันทึกค่าว่างทับของเดิม) */\n protected readonly loadFailed = signal(false);\n protected readonly saving = signal(false);\n protected readonly error = signal('');\n protected readonly success = signal('');\n protected readonly fullName = signal('');\n protected readonly orgName = signal('');\n\n protected readonly form = inject(FormBuilder).nonNullable.group({\n email: ['', [Validators.required, Validators.email]],\n mobile: ['', [Validators.required, Validators.pattern(/^0\\d{9}$/)]],\n username: ['', [Validators.required, Validators.pattern(/^\\S+$/)]],\n password: [''],\n confirmPassword: [''],\n });\n\n constructor() {\n ensureAuthStyles();\n }\n\n ngOnInit(): void {\n this.http.get<KcProfile>(`${this.config.apiBase}/user/me`).subscribe({\n next: (me) => {\n this.fullName.set(me.fullName);\n this.orgName.set(me.orgName || me.org);\n this.form.patchValue({ email: me.email ?? '', mobile: me.mobile ?? '', username: me.username ?? '' });\n this.loading.set(false);\n },\n error: (err) => {\n this.loading.set(false);\n this.loadFailed.set(true);\n this.error.set(kcErrorMessage(err, 'ไม่สามารถโหลดข้อมูลบัญชีได้'));\n },\n });\n }\n\n /** ช่องนี้ผิดและผู้ใช้แตะแล้ว — แสดงข้อความใต้ช่อง */\n protected bad(name: 'email' | 'mobile' | 'username'): boolean {\n const c = this.form.controls[name];\n return c.touched && c.invalid;\n }\n\n protected submit(): void {\n if (this.saving() || this.loading()) return;\n this.success.set('');\n this.form.markAllAsTouched();\n const v = this.form.getRawValue();\n const problem =\n kcContactProblem(v.email, v.mobile) ||\n kcUsernameProblem(v.username) ||\n kcPasswordProblem(v.password, v.confirmPassword, this.config.passwordMinLength, false);\n this.error.set(problem);\n if (problem) return;\n\n const body: KcProfileUpdate = { username: v.username.trim(), email: v.email.trim(), mobile: v.mobile.trim(), password: v.password };\n this.saving.set(true);\n this.http.put<KcMessage>(`${this.config.apiBase}/user/me`, body).subscribe({\n next: (res) => {\n this.saving.set(false);\n this.auth.updateUser({ username: body.username });\n this.form.patchValue({ password: '', confirmPassword: '' });\n this.success.set(res?.message || 'บันทึกข้อมูลเรียบร้อยแล้ว');\n },\n error: (err) => {\n this.saving.set(false);\n this.error.set(kcErrorMessage(err, 'บันทึกข้อมูลไม่สำเร็จ'));\n },\n });\n }\n}\n","import { HttpClient } from '@angular/common/http';\nimport { Component, OnInit, computed, inject, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { SearchOption, SearchSelectComponent } from '../search-select/search-select.component';\nimport { ensureAuthStyles } from './auth-styles';\nimport { KcPasswordToggleComponent } from './password-toggle.component';\nimport { kcPasswordProblem, kcUsernameProblem } from './auth-validation';\nimport { KC_AUTH_CONFIG } from './auth.config';\nimport { KcConfigUser, KcConfigUserUpdate, KcConfigUsersResponse, KcMessage, kcErrorMessage } from './auth.models';\nimport { KcAuthService } from './auth.service';\n\ninterface UserEdit extends KcConfigUserUpdate {\n id: string;\n fullName: string;\n confirmPassword: string;\n}\n\n/**\n * หน้า config › ผู้ใช้งานระบบ (เฉพาะผู้ดูแล) — วางใน layout ของโปรเจกต์พร้อม canActivate: [kcAdminGuard]\n * ค้นหา แล้วคลิกแถวเพื่อแก้ ชื่อผู้ใช้ / ระดับผู้ใช้ / สถานะ / หน่วยงาน / ตั้งรหัสผ่านใหม่\n * ไม่มีการลบ — ใช้ \"ระงับการใช้งาน\" แทน และแก้ระดับ/สถานะของตัวเองไม่ได้ (กันผู้ดูแลล็อกตัวเองออก)\n * GET {api}/config/users → { users, orgs, roles }, PUT {api}/config/users/{id} ← KcConfigUserUpdate\n */\n@Component({\n selector: 'kc-config-users',\n standalone: true,\n imports: [FormsModule, SearchSelectComponent, KcPasswordToggleComponent],\n host: { '(document:keydown.escape)': 'close()' },\n template: `\n <div class=\"kc-auth-page\">\n <div class=\"kc-auth-page-header\">\n <div>\n <h1 class=\"kc-auth-page-title\">ผู้ใช้งานระบบ</h1>\n <p class=\"kc-auth-page-subtitle\">คลิกแถวเพื่อแก้ไข — ทั้งหมด {{ users().length }} บัญชี</p>\n </div>\n </div>\n\n @if (error()) {\n <div class=\"kc-auth-alert error\" role=\"alert\">{{ error() }}</div>\n }\n @if (success()) {\n <div class=\"kc-auth-alert success\" role=\"status\">{{ success() }}</div>\n }\n\n <div class=\"kc-auth-panel\">\n <input class=\"kc-auth-input\" type=\"search\" [placeholder]=\"'ค้นหาชื่อผู้ใช้ ชื่อ-สกุล ' + config.orgLabel + ' ระดับผู้ใช้ หรืออีเมล'\"\n [ngModel]=\"search()\" (ngModelChange)=\"search.set($event)\" aria-label=\"ค้นหาผู้ใช้\" />\n </div>\n\n <div class=\"kc-auth-panel flush\">\n @if (loading()) {\n <div class=\"kc-auth-loading\">กำลังโหลดข้อมูล...</div>\n }\n <div class=\"kc-auth-table-wrap\">\n <table class=\"kc-auth-table stack\">\n <thead>\n <tr>\n <th>ชื่อผู้ใช้</th>\n <th>ชื่อ-สกุล</th>\n <th>{{ config.orgLabel }}</th>\n <th>ระดับผู้ใช้</th>\n <th class=\"c\">สถานะ</th>\n <th>เข้าระบบล่าสุด</th>\n </tr>\n </thead>\n <tbody>\n @for (u of filtered(); track u.id) {\n <tr class=\"clickable\" [class.inactive]=\"!u.active\" (click)=\"edit(u)\">\n <td><span class=\"kc-auth-link\">{{ u.username || '-' }}</span>\n @if (u.id === myId()) { <span class=\"kc-auth-hint\"> (ตัวคุณ)</span> }</td>\n <td data-label=\"ชื่อ-สกุล\">{{ u.fullName || '-' }}</td>\n <td [attr.data-label]=\"config.orgLabel\">{{ u.orgName || u.org || '-' }}</td>\n <td data-label=\"ระดับผู้ใช้\">{{ roleName(u.role) }}</td>\n <td class=\"c\" data-label=\"สถานะ\">\n <span class=\"kc-auth-badge\" [class.on]=\"u.active\" [class.off]=\"!u.active\">{{ u.active ? 'ใช้งาน' : 'ระงับ' }}</span>\n </td>\n <td data-label=\"เข้าระบบล่าสุด\">{{ u.lastLogin || '-' }}</td>\n </tr>\n } @empty {\n @if (!loading()) {\n <tr><td colspan=\"6\" class=\"empty\">ไม่พบผู้ใช้</td></tr>\n }\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n @if (editing(); as e) {\n <div class=\"kc-auth-modal-bg\" (click)=\"close()\">\n <div class=\"kc-auth-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"kc-cu-title\" (click)=\"$event.stopPropagation()\">\n <div class=\"kc-auth-modal-header\">\n <span id=\"kc-cu-title\">แก้ไขผู้ใช้ — {{ e.fullName || e.username }}</span>\n <button type=\"button\" class=\"kc-auth-close\" aria-label=\"ปิด\" (click)=\"close()\">×</button>\n </div>\n <div class=\"kc-auth-modal-body\">\n @if (editError()) {\n <div class=\"kc-auth-alert error\" role=\"alert\">{{ editError() }}</div>\n }\n <div class=\"kc-auth-grid\">\n <label class=\"kc-auth-field full\">\n <span>ชื่อผู้ใช้ <span class=\"req\">*</span></span>\n <input class=\"kc-auth-input\" type=\"text\" [(ngModel)]=\"e.username\" maxlength=\"50\" autocomplete=\"off\" />\n </label>\n <label class=\"kc-auth-field\">\n <span>ระดับผู้ใช้</span>\n <select class=\"kc-auth-input\" [(ngModel)]=\"e.role\" [disabled]=\"e.id === myId()\">\n @if (!hasRole(e.role)) {\n <option [value]=\"e.role\">{{ e.role || '-- ไม่ระบุ --' }}</option>\n }\n @for (r of roles(); track r.value) {\n <option [value]=\"r.value\">{{ r.label }}</option>\n }\n </select>\n </label>\n <label class=\"kc-auth-field\">\n <span>สถานะ</span>\n <select class=\"kc-auth-input\" [(ngModel)]=\"e.active\" [disabled]=\"e.id === myId()\">\n <option [ngValue]=\"true\">ใช้งาน</option>\n <option [ngValue]=\"false\">ระงับการใช้งาน</option>\n </select>\n </label>\n @if (orgs().length) {\n <div class=\"kc-auth-field full\">\n <label class=\"kc-auth-label\" for=\"kc-cu-org\">{{ config.orgLabel }}</label>\n <app-search-select inputId=\"kc-cu-org\" [options]=\"orgOptions()\" [(ngModel)]=\"e.org\" />\n </div>\n }\n <label class=\"kc-auth-field\">\n <span>รหัสผ่านใหม่</span>\n <kc-password><input class=\"kc-auth-input\" type=\"password\" [(ngModel)]=\"e.password\" autocomplete=\"new-password\" /></kc-password>\n </label>\n <label class=\"kc-auth-field\">\n <span>ยืนยันรหัสผ่านใหม่</span>\n <kc-password><input class=\"kc-auth-input\" type=\"password\" [(ngModel)]=\"e.confirmPassword\" autocomplete=\"new-password\" /></kc-password>\n </label>\n </div>\n <p class=\"kc-auth-hint\">\n เว้นว่างรหัสผ่านไว้ถ้าไม่ต้องการเปลี่ยน — ระดับผู้ใช้และ{{ config.orgLabel }}ที่เปลี่ยนมีผลเมื่อผู้ใช้เข้าสู่ระบบครั้งถัดไป\n @if (e.id === myId()) { — แก้ระดับหรือระงับบัญชีของตนเองไม่ได้ }\n </p>\n <div class=\"kc-auth-actions\">\n <button type=\"button\" class=\"kc-auth-btn\" (click)=\"close()\">ยกเลิก</button>\n <button type=\"button\" class=\"kc-auth-btn primary\" [disabled]=\"saving()\" (click)=\"save()\">\n {{ saving() ? 'กำลังบันทึก...' : 'บันทึก' }}\n </button>\n </div>\n </div>\n </div>\n </div>\n }\n `,\n})\nexport class KcConfigUsersComponent implements OnInit {\n protected readonly config = inject(KC_AUTH_CONFIG);\n private readonly auth = inject(KcAuthService);\n private readonly http = inject(HttpClient);\n\n protected readonly users = signal<KcConfigUser[]>([]);\n protected readonly orgs = signal<SearchOption[]>([]);\n protected readonly roles = signal<SearchOption[]>([]);\n protected readonly loading = signal(false);\n protected readonly saving = signal(false);\n protected readonly error = signal('');\n protected readonly success = signal('');\n protected readonly editError = signal('');\n protected readonly search = signal('');\n protected readonly editing = signal<UserEdit | null>(null);\n protected readonly myId = computed(() => this.auth.user()?.id ?? '');\n\n /** ตัวเลือกหน่วยงานในป็อปอัป — มี \"ไม่ระบุ\" ไว้ล้างค่า */\n protected readonly orgOptions = computed<SearchOption[]>(() => [{ value: '', label: '-- ไม่ระบุ --' }, ...this.orgs()]);\n\n protected readonly filtered = computed(() => {\n const words = this.search().trim().toLowerCase().split(/\\s+/).filter(Boolean);\n if (!words.length) return this.users();\n return this.users().filter((u) => {\n const text = [u.username, u.fullName, u.orgName, u.org, this.roleName(u.role), u.email, u.mobile].join(' ').toLowerCase();\n return words.every((w) => text.includes(w));\n });\n });\n\n constructor() {\n ensureAuthStyles();\n }\n\n ngOnInit(): void {\n this.load();\n }\n\n protected load(): void {\n this.loading.set(true);\n this.http.get<KcConfigUsersResponse>(`${this.config.apiBase}/config/users`).subscribe({\n next: (res) => {\n this.users.set(res.users ?? []);\n this.orgs.set(res.orgs ?? []);\n this.roles.set(res.roles ?? []);\n this.loading.set(false);\n },\n error: (err) => {\n this.loading.set(false);\n this.error.set(kcErrorMessage(err, 'ไม่สามารถโหลดรายชื่อผู้ใช้ได้'));\n },\n });\n }\n\n protected roleName(role: string): string {\n return this.roles().find((r) => r.value === role)?.label ?? (role || '-');\n }\n\n protected hasRole(role: string): boolean {\n return this.roles().some((r) => r.value === role);\n }\n\n protected edit(u: KcConfigUser): void {\n this.editError.set('');\n this.editing.set({\n id: u.id, fullName: u.fullName, username: u.username, role: u.role, active: u.active, org: u.org,\n password: '', confirmPassword: '',\n });\n }\n\n protected close(): void {\n if (!this.saving()) this.editing.set(null);\n }\n\n protected save(): void {\n const e = this.editing();\n if (!e || this.saving()) return;\n const problem = kcUsernameProblem(e.username) || kcPasswordProblem(e.password, e.confirmPassword, this.config.passwordMinLength, false);\n this.editError.set(problem);\n if (problem) return;\n\n const body: KcConfigUserUpdate = { username: e.username.trim(), password: e.password, role: e.role, active: e.active, org: e.org };\n this.saving.set(true);\n this.http.put<KcMessage>(`${this.config.apiBase}/config/users/${encodeURIComponent(e.id)}`, body).subscribe({\n next: (res) => {\n this.saving.set(false);\n this.editing.set(null);\n this.error.set('');\n this.success.set(res?.message || 'บันทึกข้อมูลเรียบร้อยแล้ว');\n this.load();\n },\n error: (err) => {\n this.saving.set(false);\n this.editError.set(kcErrorMessage(err, 'บันทึกข้อมูลไม่สำเร็จ'));\n },\n });\n }\n}\n","import { HttpClient } from '@angular/common/http';\nimport { Component, OnInit, computed, inject, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { SearchOption, SearchSelectComponent } from '../search-select/search-select.component';\nimport { ensureAuthStyles } from './auth-styles';\nimport { KC_AUTH_CONFIG } from './auth.config';\nimport { KcMessage, KclluRow, KclluTitle, kcErrorMessage } from './auth.models';\n\n/**\n * หน้า config › ข้อมูลอ้างอิง (ตาราง kcllu) เฉพาะผู้ดูแล — วางใน layout ของโปรเจกต์พร้อม canActivate: [kcAdminGuard]\n * เลือกกลุ่ม (k_title) แล้วคลิกแถวเพื่อแก้ / ปุ่มเพิ่มข้อมูล — ไม่มีการลบ ใช้ \"ปิดใช้งาน\" แทน\n * กลุ่มที่ API ตอบ readOnly (เช่น menu) ดูได้อย่างเดียว\n * คำอธิบายคอลัมน์ของแต่ละกลุ่มตั้งที่ provideKcAuth({ kclluHints })\n * GET {api}/config/kcllu/titles → KclluTitle[]\n * GET {api}/config/kcllu?title= → KclluRow[]\n * POST {api}/config/kcllu / PUT {api}/config/kcllu/{id} ← KclluRow + title\n */\n@Component({\n selector: 'kc-config-kcllu',\n standalone: true,\n imports: [FormsModule, SearchSelectComponent],\n host: { '(document:keydown.escape)': 'close()' },\n template: `\n <div class=\"kc-auth-page\">\n <div class=\"kc-auth-page-header\">\n <div>\n <h1 class=\"kc-auth-page-title\">ข้อมูลอ้างอิง (kcllu)</h1>\n <p class=\"kc-auth-page-subtitle\">เลือกกลุ่มข้อมูล แล้วคลิกแถวเพื่อแก้ไข — ไม่มีการลบ ใช้ \"ปิดใช้งาน\" แทน</p>\n </div>\n @if (title() && !readOnly()) {\n <button type=\"button\" class=\"kc-auth-btn primary\" (click)=\"add()\">+ เพิ่มข้อมูล</button>\n }\n </div>\n\n @if (error()) {\n <div class=\"kc-auth-alert error\" role=\"alert\">{{ error() }}</div>\n }\n @if (success()) {\n <div class=\"kc-auth-alert success\" role=\"status\">{{ success() }}</div>\n }\n\n <div class=\"kc-auth-panel\">\n <div class=\"kc-auth-grid\">\n <div class=\"kc-auth-field\" style=\"margin-bottom: 0\">\n <label class=\"kc-auth-label\" for=\"kc-kl-title\">กลุ่มข้อมูล (k_title)</label>\n <app-search-select inputId=\"kc-kl-title\" [options]=\"titleOptions()\" placeholder=\"-- เลือกกลุ่ม --\"\n [ngModel]=\"title()\" (ngModelChange)=\"select($event)\" />\n </div>\n <div class=\"kc-auth-field\" style=\"margin-bottom: 0; justify-content: flex-end\">\n <label class=\"kc-auth-check\">\n <input type=\"checkbox\" [ngModel]=\"showInactive()\" (ngModelChange)=\"showInactive.set($event)\" />\n แสดงรายการที่ปิดใช้งาน\n </label>\n </div>\n </div>\n @if (hint()) {\n <p class=\"kc-auth-hint\" style=\"margin-top: 10px\">{{ hint() }}</p>\n }\n @if (readOnly()) {\n <p class=\"kc-auth-hint\" style=\"margin-top: 6px\">กลุ่มนี้ดูได้อย่างเดียว</p>\n }\n </div>\n\n <div class=\"kc-auth-panel flush\">\n @if (loading()) {\n <div class=\"kc-auth-loading\">กำลังโหลดข้อมูล...</div>\n }\n <div class=\"kc-auth-table-wrap\">\n <table class=\"kc-auth-table stack\">\n <thead>\n <tr>\n <th>ลำดับ</th>\n <th>รหัส (k_code)</th>\n <th style=\"width: 30%\">ชื่อ (k_name)</th>\n <th>ค่า (k_value)</th>\n <th>กลุ่มย่อย (k_grp)</th>\n <th>k_mark</th>\n <th class=\"c\">สถานะ</th>\n </tr>\n </thead>\n <tbody>\n @for (r of visibleRows(); track r.id) {\n <tr [class.clickable]=\"!readOnly()\" [class.inactive]=\"!r.active\" (click)=\"edit(r)\">\n <td data-label=\"ลำดับ\">{{ r.no || '-' }}</td>\n <td data-label=\"รหัส\">{{ r.code || '-' }}</td>\n <td data-label=\"ชื่อ\"><span [class.kc-auth-link]=\"!readOnly()\">{{ r.name || '-' }}</span>\n @if (r.name1) { <div class=\"kc-auth-hint\">{{ r.name1 }}</div> }</td>\n <td data-label=\"ค่า\">{{ r.value || '-' }}</td>\n <td data-label=\"กลุ่มย่อย\">{{ r.grp || '-' }}</td>\n <td data-label=\"k_mark\">{{ r.mark || '-' }}</td>\n <td class=\"c\" data-label=\"สถานะ\">\n <span class=\"kc-auth-badge\" [class.on]=\"r.active\" [class.off]=\"!r.active\">{{ r.active ? 'ใช้งาน' : 'ปิดใช้งาน' }}</span>\n </td>\n </tr>\n } @empty {\n @if (!loading()) {\n <tr><td colspan=\"7\" class=\"empty\">{{ title() ? 'ไม่พบข้อมูล' : 'กรุณาเลือกกลุ่มข้อมูล' }}</td></tr>\n }\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n @if (editing(); as e) {\n <div class=\"kc-auth-modal-bg\" (click)=\"close()\">\n <div class=\"kc-auth-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"kc-kl-edit\" (click)=\"$event.stopPropagation()\">\n <div class=\"kc-auth-modal-header\">\n <span id=\"kc-kl-edit\">{{ e.id ? 'แก้ไขข้อมูล' : 'เพิ่มข้อมูล' }} — กลุ่ม {{ title() }}</span>\n <button type=\"button\" class=\"kc-auth-close\" aria-label=\"ปิด\" (click)=\"close()\">×</button>\n </div>\n <div class=\"kc-auth-modal-body\">\n @if (editError()) {\n <div class=\"kc-auth-alert error\" role=\"alert\">{{ editError() }}</div>\n }\n @if (hint()) {\n <p class=\"kc-auth-hint\" style=\"margin: 0 0 12px\">{{ hint() }}</p>\n }\n <div class=\"kc-auth-grid\">\n <label class=\"kc-auth-field\">\n <span>รหัส (k_code)</span>\n <input class=\"kc-auth-input\" type=\"text\" [(ngModel)]=\"e.code\" maxlength=\"255\" />\n </label>\n <label class=\"kc-auth-field\">\n <span>ลำดับ (k_no)</span>\n <input class=\"kc-auth-input\" type=\"text\" inputmode=\"numeric\" [(ngModel)]=\"e.no\" maxlength=\"9\" />\n </label>\n <label class=\"kc-auth-field full\">\n <span>ชื่อ (k_name) <span class=\"req\">*</span></span>\n <input class=\"kc-auth-input\" type=\"text\" [(ngModel)]=\"e.name\" maxlength=\"255\" />\n </label>\n <label class=\"kc-auth-field full\">\n <span>ชื่อที่ 2 (k_name1)</span>\n <input class=\"kc-auth-input\" type=\"text\" [(ngModel)]=\"e.name1\" maxlength=\"255\" />\n </label>\n <label class=\"kc-auth-field\">\n <span>ค่า (k_value)</span>\n <input class=\"kc-auth-input\" type=\"text\" [(ngModel)]=\"e.value\" maxlength=\"255\" />\n </label>\n <label class=\"kc-auth-field\">\n <span>กลุ่มย่อย (k_grp)</span>\n <input class=\"kc-auth-input\" type=\"text\" [(ngModel)]=\"e.grp\" maxlength=\"255\" />\n </label>\n <label class=\"kc-auth-field\">\n <span>k_mark</span>\n <input class=\"kc-auth-input\" type=\"text\" [(ngModel)]=\"e.mark\" maxlength=\"255\" />\n </label>\n <label class=\"kc-auth-field\">\n <span>สถานะ</span>\n <select class=\"kc-auth-input\" [(ngModel)]=\"e.active\">\n <option [ngValue]=\"true\">ใช้งาน</option>\n <option [ngValue]=\"false\">ปิดใช้งาน</option>\n </select>\n </label>\n </div>\n <div class=\"kc-auth-actions\">\n <button type=\"button\" class=\"kc-auth-btn\" (click)=\"close()\">ยกเลิก</button>\n <button type=\"button\" class=\"kc-auth-btn primary\" [disabled]=\"saving()\" (click)=\"save()\">\n {{ saving() ? 'กำลังบันทึก...' : 'บันทึก' }}\n </button>\n </div>\n </div>\n </div>\n </div>\n }\n `,\n})\nexport class KcConfigKclluComponent implements OnInit {\n private readonly config = inject(KC_AUTH_CONFIG);\n private readonly http = inject(HttpClient);\n private readonly api = `${this.config.apiBase}/config/kcllu`;\n\n protected readonly titles = signal<KclluTitle[]>([]);\n protected readonly title = signal('');\n protected readonly rows = signal<KclluRow[]>([]);\n protected readonly loading = signal(false);\n protected readonly saving = signal(false);\n protected readonly showInactive = signal(true);\n protected readonly error = signal('');\n protected readonly success = signal('');\n protected readonly editError = signal('');\n /** แถวที่กำลังแก้ — id 0 = เพิ่มใหม่ */\n protected readonly editing = signal<KclluRow | null>(null);\n\n protected readonly titleOptions = computed<SearchOption[]>(() =>\n this.titles().map((t) => ({ value: t.title, label: `${t.title} (${t.count})${t.readOnly ? ' — ดูอย่างเดียว' : ''}` })),\n );\n protected readonly readOnly = computed(() => this.titles().find((t) => t.title === this.title())?.readOnly ?? false);\n protected readonly hint = computed(() => this.config.kclluHints[this.title()] ?? '');\n protected readonly visibleRows = computed(() => (this.showInactive() ? this.rows() : this.rows().filter((r) => r.active)));\n\n constructor() {\n ensureAuthStyles();\n }\n\n ngOnInit(): void {\n this.loadTitles();\n }\n\n private loadTitles(): void {\n this.http.get<KclluTitle[]>(`${this.api}/titles`).subscribe({\n next: (res) => {\n this.titles.set(res ?? []);\n if (!this.title() && this.titles().length) this.select(this.titles()[0].title);\n },\n error: (err) => this.error.set(kcErrorMessage(err, 'ไม่สามารถโหลดกลุ่มข้อมูลได้')),\n });\n }\n\n protected select(title: string): void {\n if (!title || title === this.title()) return;\n this.title.set(title);\n this.success.set('');\n this.loadRows();\n }\n\n private loadRows(): void {\n const title = this.title();\n this.loading.set(true);\n this.http.get<KclluRow[]>(this.api, { params: { title } }).subscribe({\n next: (res) => {\n // เปลี่ยนกลุ่มระหว่างโหลด — ทิ้งผลของกลุ่มเก่า\n if (title !== this.title()) return;\n this.rows.set(res ?? []);\n this.loading.set(false);\n },\n error: (err) => {\n this.loading.set(false);\n this.error.set(kcErrorMessage(err, 'ไม่สามารถโหลดข้อมูลได้'));\n },\n });\n }\n\n protected add(): void {\n this.editError.set('');\n this.editing.set({ id: 0, code: '', name: '', name1: '', value: '', grp: '', no: '', mark: '', active: true });\n }\n\n protected edit(row: KclluRow): void {\n if (this.readOnly()) return;\n this.editError.set('');\n this.editing.set({ ...row });\n }\n\n protected close(): void {\n if (!this.saving()) this.editing.set(null);\n }\n\n protected save(): void {\n const e = this.editing();\n if (!e || this.saving()) return;\n const no = String(e.no ?? '').trim();\n const problem = !e.name.trim() ? 'กรุณากรอกชื่อ (k_name)' : !/^\\d*$/.test(no) ? 'ลำดับ (k_no) ต้องเป็นตัวเลข' : '';\n this.editError.set(problem);\n if (problem) return;\n\n const body = { ...e, no, name: e.name.trim(), title: this.title() };\n const req = e.id\n ? this.http.put<KcMessage>(`${this.api}/${e.id}`, body)\n : this.http.post<KcMessage>(this.api, body);\n\n this.saving.set(true);\n req.subscribe({\n next: (res) => {\n this.saving.set(false);\n this.editing.set(null);\n this.error.set('');\n this.success.set(res?.message || 'บันทึกข้อมูลเรียบร้อยแล้ว');\n this.loadRows();\n if (!e.id) this.loadTitles();\n },\n error: (err) => {\n this.saving.set(false);\n this.editError.set(kcErrorMessage(err, 'บันทึกข้อมูลไม่สำเร็จ'));\n },\n });\n }\n}\n"],"mappings":"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"}
|