diff --git a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/template/ui/my.js b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/template/ui/my.js index e0e04eecf06..95e8c8322b3 100644 --- a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/template/ui/my.js +++ b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/template/ui/my.js @@ -18,14 +18,31 @@ export const getSources = () => [ action: "generate", engine: "velocity", rename: "gen/{{genFolderName}}/js/components/pages/my/{{name}}MyListPage.js", - collection: "personalRootModels" + collection: "personalListModels" }, { location: "/template-application-ui-harmonia-java/ui/my/my-list-view.html.template", action: "generate", engine: "velocity", rename: "gen/{{genFolderName}}/views/my/{{name}}-list.html", - collection: "personalRootModels" + collection: "personalListModels" + }, + { + // Personal CALENDAR/RANGE layout: a view: calendar|range personal root renders its own + // records as calendar events (scoped to the MyController), replacing the list - exactly + // like the power surface. date-click / event-click route to the personal form. + location: "/template-application-ui-harmonia-java/ui/my/my-calendar-page.js.template", + action: "generate", + engine: "velocity", + rename: "gen/{{genFolderName}}/js/components/pages/my/{{name}}MyCalendarPage.js", + collection: "personalCalendarModels" + }, + { + location: "/template-application-ui-harmonia-java/ui/my/my-calendar-view.html.template", + action: "generate", + engine: "velocity", + rename: "gen/{{genFolderName}}/views/my/{{name}}-calendar.html", + collection: "personalCalendarModels" }, { location: "/template-application-ui-harmonia-java/ui/my/my-form-page.js.template", diff --git a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/my/my-calendar-page.js.template b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/my/my-calendar-page.js.template new file mode 100644 index 00000000000..fadbb6df3d3 --- /dev/null +++ b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/my/my-calendar-page.js.template @@ -0,0 +1,123 @@ +/* + * Generated by Eclipse Dirigible based on model and template. + * + * Do not modify the content as it may be re-generated again. + */ + +/* + * Personal (my) calendar page for ${name} - the logged-in user's OWN records rendered as events on + * a Harmonia x-h-calendar (the same view: calendar/range surface the power page has, scoped to the + * ${name}MyController - foreign rows and sensitive fields never arrive). date-click / event-click + * route to the personal form. + */ +document.addEventListener('alpine:init', () => { + Alpine.data('${name}MyCalendarPage', () => ({ + ...basePage(), + state: 'loading', + error: null, + items: [], + // Reactive config for x-h-calendar; repopulated after each load so the calendar re-renders. + calCfg: { view: '${calendarInitialView}', events: [] }, + + // Controller path for the PERSONAL surface, relative to App.config.restBase. + apiPath: '/${javaPerspectiveName}/${name}MyController', + + async init() { + await this.load(); + }, + + async load() { + this.state = 'loading'; + this.error = null; + try { + this.items = await App.services.api.get(this.apiPath) || []; + this.calCfg = { view: '${calendarInitialView}', events: this.buildEvents() }; + this.state = 'default'; + } catch (e) { + this.error = (e && e.message) || 'Could not load your ${menuLabel}.'; + this.state = 'error'; + } + this.refreshIcons(); + }, + + // Map each record to a calendar event. Rows with no start value are skipped. + buildEvents() { + return (this.items || []).map(row => { + const start = this.toISO(row.${calendarStartProperty}); + if (!start) return null; + const ev = { + id: String(row.${primaryKeysString}), + title: this.titleFor(row), + start: start, + allDay: #if($calendarRange)true#{else}this.isDateOnly(row.${calendarStartProperty})#end + }; +#if($calendarEndProperty) + const end = this.toISO(row.${calendarEndProperty}); + if (end) ev.end = end; +#end +#if($calendarColorProperty) + ev.color = this.colorFor(row.${calendarColorProperty}); +#end + return ev; + }).filter(Boolean); + }, + + titleFor(row) { +#if($calendarTitleProperty) + const t = row.${calendarTitleProperty}; + if (t !== undefined && t !== null && String(t) !== '') return String(t); +#end + return '${menuLabel} #' + row.${primaryKeysString}; + }, + + // Jackson serializes java.time as arrays (LocalDate [y,m,d]; LocalDateTime [y,m,d,h,mi,s,ns]) and + // Instant/Timestamp as a numeric epoch. Convert any of those (or a plain ISO string) to the ISO + // string x-h-calendar expects; '' when unset. + toISO(v) { + if (v === undefined || v === null || v === '') return ''; + if (Array.isArray(v)) { + const p = n => String(n).padStart(2, '0'); + const date = v[0] + '-' + p(v[1]) + '-' + p(v[2]); + if (v.length <= 3) return date; + return date + 'T' + p(v[3] || 0) + ':' + p(v[4] || 0) + ':' + p(v[5] || 0); + } + if (typeof v === 'number') { + const ms = v < 1e12 ? v * 1000 : v; + try { return new Date(ms).toISOString(); } catch (e) { return ''; } + } + return String(v); + }, + isDateOnly(v) { + return Array.isArray(v) ? v.length <= 3 : (typeof v === 'string' && v.length <= 10); + }, + + // Deterministic categorical colour from the Harmonia calendar palette. + colorFor(v) { + const palette = ['blue', 'green', 'purple', 'orange', 'teal', 'pink', 'indigo', 'yellow', 'red', 'gray']; + const key = (v === undefined || v === null) ? '' : String(v); + let h = 0; + for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) >>> 0; + return palette[h % palette.length]; + }, + + // Empty-cell click -> create your own record, prefilling the start field from the clicked day + // (the personal form presets any field whose name matches a query param on create). + onDateClick(e) { + const d = e && e.detail ? e.detail.date : null; + let qs = ''; + if (d instanceof Date && !isNaN(d.getTime())) { + const p = n => String(n).padStart(2, '0'); + let val = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()); + const time = e.detail.time; + if (time) val += 'T' + time; + qs = '?${calendarStartProperty}=' + encodeURIComponent(val); + } + window.PineconeRouter.navigate('/my/${name}/create' + qs); + }, + onEventClick(e) { + const id = e && e.detail && e.detail.event ? e.detail.event.id : null; + if (id) window.PineconeRouter.navigate('/my/${name}/' + encodeURIComponent(id) + '/edit'); + }, + newEntity() { window.PineconeRouter.navigate('/my/${name}/create'); } + })); +}, { once: true }); diff --git a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/my/my-calendar-view.html.template b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/my/my-calendar-view.html.template new file mode 100644 index 00000000000..2077e0e155a --- /dev/null +++ b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/my/my-calendar-view.html.template @@ -0,0 +1,26 @@ + +