@mulmoclaude/collection-plugin 0.2.1 → 0.3.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.
- package/dist/calendarGrid-DZeLwBj9.cjs +778 -0
- package/dist/calendarGrid-DZeLwBj9.cjs.map +1 -0
- package/dist/calendarGrid-ggmFTZju.js +587 -0
- package/dist/calendarGrid-ggmFTZju.js.map +1 -0
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/core/actionVisible.d.ts +1 -0
- package/dist/core/actionVisible.d.ts.map +1 -0
- package/dist/core/calendarGrid.d.ts +1 -0
- package/dist/core/calendarGrid.d.ts.map +1 -0
- package/dist/core/deriveAll.d.ts +2 -1
- package/dist/core/deriveAll.d.ts.map +1 -0
- package/dist/core/derivedFormula.d.ts +1 -0
- package/dist/core/derivedFormula.d.ts.map +1 -0
- package/dist/core/draft.d.ts +3 -2
- package/dist/core/draft.d.ts.map +1 -0
- package/dist/core/enumColors.d.ts +2 -1
- package/dist/core/enumColors.d.ts.map +1 -0
- package/dist/core/itemLabel.d.ts +2 -1
- package/dist/core/itemLabel.d.ts.map +1 -0
- package/dist/core/presentCollection.d.ts +2 -1
- package/dist/core/presentCollection.d.ts.map +1 -0
- package/dist/core/schema.d.ts +1 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/sortItems.d.ts +2 -1
- package/dist/core/sortItems.d.ts.map +1 -0
- package/dist/core/uiTypes.d.ts +29 -1
- package/dist/core/uiTypes.d.ts.map +1 -0
- package/dist/{deriveAll-B5spcUIb.js → deriveAll-BHcs1erT.js} +2 -20
- package/dist/deriveAll-BHcs1erT.js.map +1 -0
- package/dist/{deriveAll-Do0v6Xjb.cjs → deriveAll-BJ0Lvm1Q.cjs} +1 -31
- package/dist/deriveAll-BJ0Lvm1Q.cjs.map +1 -0
- package/dist/index.cjs +37 -619
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +12 -11
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -585
- package/dist/index.js.map +1 -1
- package/dist/schema-DyfSfVzh.js +21 -0
- package/dist/schema-DyfSfVzh.js.map +1 -0
- package/dist/schema-LrmrgW12.cjs +32 -0
- package/dist/schema-LrmrgW12.cjs.map +1 -0
- package/dist/server/atomic.d.ts +1 -0
- package/dist/server/atomic.d.ts.map +1 -0
- package/dist/server/delete.d.ts +2 -1
- package/dist/server/delete.d.ts.map +1 -0
- package/dist/server/derive.d.ts +4 -3
- package/dist/server/derive.d.ts.map +1 -0
- package/dist/server/discoveredCollection.d.ts +2 -1
- package/dist/server/discoveredCollection.d.ts.map +1 -0
- package/dist/server/discovery.d.ts +4 -3
- package/dist/server/discovery.d.ts.map +1 -0
- package/dist/server/host.d.ts +4 -1
- package/dist/server/host.d.ts.map +1 -0
- package/dist/server/index.d.ts +12 -11
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/io.d.ts +3 -2
- package/dist/server/io.d.ts.map +1 -0
- package/dist/server/paths.d.ts +1 -0
- package/dist/server/paths.d.ts.map +1 -0
- package/dist/server/spawn.d.ts +3 -2
- package/dist/server/spawn.d.ts.map +1 -0
- package/dist/server/templatePath.d.ts +1 -0
- package/dist/server/templatePath.d.ts.map +1 -0
- package/dist/server/util.d.ts +1 -0
- package/dist/server/util.d.ts.map +1 -0
- package/dist/server/validate.d.ts +3 -2
- package/dist/server/validate.d.ts.map +1 -0
- package/dist/server/views.d.ts +3 -2
- package/dist/server/views.d.ts.map +1 -0
- package/dist/server.cjs +12 -29
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +8 -4
- package/dist/server.js.map +1 -1
- package/dist/style.css +2121 -0
- package/dist/vue/components/CollectionCalendarView.vue.d.ts +27 -0
- package/dist/vue/components/CollectionCalendarView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionDayView.vue.d.ts +40 -0
- package/dist/vue/components/CollectionDayView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionEmbedView.vue.d.ts +9 -0
- package/dist/vue/components/CollectionEmbedView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionKanbanView.vue.d.ts +23 -0
- package/dist/vue/components/CollectionKanbanView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionRecordModal.vue.d.ts +18 -0
- package/dist/vue/components/CollectionRecordModal.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionRecordPanel.vue.d.ts +46 -0
- package/dist/vue/components/CollectionRecordPanel.vue.d.ts.map +1 -0
- package/dist/vue/index.d.ts +10 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/uiContext.d.ts +30 -0
- package/dist/vue/uiContext.d.ts.map +1 -0
- package/dist/vue/useCollectionRendering.d.ts +30 -0
- package/dist/vue/useCollectionRendering.d.ts.map +1 -0
- package/dist/vue.cjs +1624 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.js +1613 -0
- package/dist/vue.js.map +1 -0
- package/package.json +21 -6
- package/dist/deriveAll-B5spcUIb.js.map +0 -1
- package/dist/deriveAll-Do0v6Xjb.cjs.map +0 -1
package/dist/vue.js
ADDED
|
@@ -0,0 +1,1613 @@
|
|
|
1
|
+
import { M as resolveEnumColor, T as fieldVisible, _ as labelFieldFor, g as itemLabelOf, h as itemIdOf, i as buildMonthGrid, m as ymdKey, n as assignLanes, r as bucketRecords, s as daySlice, t as MINUTES_PER_DAY, x as emptyRow } from "./calendarGrid-ggmFTZju.js";
|
|
2
|
+
import { t as deriveAll } from "./deriveAll-BHcs1erT.js";
|
|
3
|
+
import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, renderSlot, resolveComponent, resolveDynamicComponent, toDisplayString, unref, useModel, vModelCheckbox, vModelDynamic, vModelSelect, vModelText, withCtx, withDirectives, withKeys, withModifiers } from "vue";
|
|
4
|
+
import { useI18n } from "vue-i18n";
|
|
5
|
+
import draggable from "vuedraggable";
|
|
6
|
+
//#region src/vue/uiContext.ts
|
|
7
|
+
var current = null;
|
|
8
|
+
/** Wire the collection view layer to a host. Call once at app startup. */
|
|
9
|
+
function configureCollectionUi(capabilities) {
|
|
10
|
+
current = capabilities;
|
|
11
|
+
}
|
|
12
|
+
function collectionUi() {
|
|
13
|
+
if (current === null) throw new Error("@mulmoclaude/collection-plugin/vue: configureCollectionUi() was not called by the host");
|
|
14
|
+
return current;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/vue/useCollectionRendering.ts
|
|
18
|
+
function useCollectionRendering(collection, locale) {
|
|
19
|
+
const refCache = ref({});
|
|
20
|
+
const refRecordCache = ref({});
|
|
21
|
+
const embedCache = ref({});
|
|
22
|
+
function resetLinkedCaches() {
|
|
23
|
+
refCache.value = {};
|
|
24
|
+
refRecordCache.value = {};
|
|
25
|
+
embedCache.value = {};
|
|
26
|
+
}
|
|
27
|
+
function uniqueRefTargets(schema) {
|
|
28
|
+
const targets = /* @__PURE__ */ new Set();
|
|
29
|
+
const walk = (fields) => {
|
|
30
|
+
for (const field of Object.values(fields)) {
|
|
31
|
+
if (field.type === "ref" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
|
|
32
|
+
if (field.type === "table" && field.of) walk(field.of);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
walk(schema.fields);
|
|
36
|
+
return [...targets];
|
|
37
|
+
}
|
|
38
|
+
function uniqueEmbedTargets(schema) {
|
|
39
|
+
const targets = /* @__PURE__ */ new Set();
|
|
40
|
+
for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
|
|
41
|
+
return [...targets];
|
|
42
|
+
}
|
|
43
|
+
function buildRefDisplayMap(detail) {
|
|
44
|
+
const { fields, primaryKey } = detail.collection.schema;
|
|
45
|
+
const displayField = "name" in fields ? "name" : "title" in fields ? "title" : primaryKey;
|
|
46
|
+
const map = {};
|
|
47
|
+
for (const item of detail.items) {
|
|
48
|
+
const slugRaw = item[primaryKey];
|
|
49
|
+
if (typeof slugRaw !== "string" || slugRaw.length === 0) continue;
|
|
50
|
+
const displayRaw = item[displayField];
|
|
51
|
+
map[slugRaw] = typeof displayRaw === "string" && displayRaw.length > 0 ? displayRaw : slugRaw;
|
|
52
|
+
}
|
|
53
|
+
return map;
|
|
54
|
+
}
|
|
55
|
+
function buildRefRecordMap(detail) {
|
|
56
|
+
const { schema } = detail.collection;
|
|
57
|
+
const map = {};
|
|
58
|
+
for (const item of detail.items) {
|
|
59
|
+
const slugRaw = item[schema.primaryKey];
|
|
60
|
+
if (typeof slugRaw === "string" && slugRaw.length > 0) map[slugRaw] = deriveAll(schema, item, {});
|
|
61
|
+
}
|
|
62
|
+
return map;
|
|
63
|
+
}
|
|
64
|
+
async function loadLinkedCollections(schema, expectedSlug) {
|
|
65
|
+
const refTargets = new Set(uniqueRefTargets(schema));
|
|
66
|
+
const embedTargets = new Set(uniqueEmbedTargets(schema));
|
|
67
|
+
const allTargets = [...new Set([...refTargets, ...embedTargets])];
|
|
68
|
+
if (allTargets.length === 0) return;
|
|
69
|
+
const results = await Promise.all(allTargets.map((target) => collectionUi().fetchCollectionDetail(target).then((result) => ({
|
|
70
|
+
target,
|
|
71
|
+
result
|
|
72
|
+
}))));
|
|
73
|
+
if (collection.value?.slug !== expectedSlug) return;
|
|
74
|
+
const nextRef = {};
|
|
75
|
+
const nextRefRecords = {};
|
|
76
|
+
const nextEmbed = {};
|
|
77
|
+
for (const { target, result } of results) {
|
|
78
|
+
if (!result.ok) continue;
|
|
79
|
+
if (refTargets.has(target)) {
|
|
80
|
+
nextRef[target] = buildRefDisplayMap(result.data);
|
|
81
|
+
nextRefRecords[target] = buildRefRecordMap(result.data);
|
|
82
|
+
}
|
|
83
|
+
if (embedTargets.has(target)) nextEmbed[target] = {
|
|
84
|
+
schema: result.data.collection.schema,
|
|
85
|
+
items: result.data.items
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
refCache.value = nextRef;
|
|
89
|
+
refRecordCache.value = nextRefRecords;
|
|
90
|
+
embedCache.value = nextEmbed;
|
|
91
|
+
}
|
|
92
|
+
function refDisplay(targetSlug, itemSlug) {
|
|
93
|
+
const map = refCache.value[targetSlug];
|
|
94
|
+
return map && map[itemSlug] || itemSlug;
|
|
95
|
+
}
|
|
96
|
+
function refOptions(targetSlug) {
|
|
97
|
+
const map = refCache.value[targetSlug];
|
|
98
|
+
if (!map) return [];
|
|
99
|
+
return Object.entries(map).map(([slug, display]) => ({
|
|
100
|
+
slug,
|
|
101
|
+
display
|
|
102
|
+
})).sort((left, right) => left.display.localeCompare(right.display));
|
|
103
|
+
}
|
|
104
|
+
function resolveEmbed(field) {
|
|
105
|
+
if (field.type !== "embed" || !field.to || !field.id) return {
|
|
106
|
+
schema: null,
|
|
107
|
+
item: null
|
|
108
|
+
};
|
|
109
|
+
const data = embedCache.value[field.to];
|
|
110
|
+
if (!data) return {
|
|
111
|
+
schema: null,
|
|
112
|
+
item: null
|
|
113
|
+
};
|
|
114
|
+
const item = data.items.find((entry) => String(entry[data.schema.primaryKey] ?? "") === field.id) ?? null;
|
|
115
|
+
return {
|
|
116
|
+
schema: data.schema,
|
|
117
|
+
item
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function embedValue(field, value, record) {
|
|
121
|
+
if (field.type === "money") return formatMoney(value, resolveCurrency(field, record), locale.value);
|
|
122
|
+
return detailText(value);
|
|
123
|
+
}
|
|
124
|
+
const embedViews = computed(() => {
|
|
125
|
+
const out = {};
|
|
126
|
+
if (!collection.value) return out;
|
|
127
|
+
for (const [key, field] of Object.entries(collection.value.schema.fields)) {
|
|
128
|
+
if (field.type !== "embed") continue;
|
|
129
|
+
const { schema, item } = resolveEmbed(field);
|
|
130
|
+
const rows = [];
|
|
131
|
+
if (schema && item) for (const [subKey, subField] of Object.entries(schema.fields)) {
|
|
132
|
+
const value = item[subKey];
|
|
133
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
134
|
+
rows.push({
|
|
135
|
+
key: subKey,
|
|
136
|
+
label: subField.label,
|
|
137
|
+
type: subField.type,
|
|
138
|
+
value,
|
|
139
|
+
display: embedValue(subField, value, item)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
out[key] = {
|
|
143
|
+
found: Boolean(item),
|
|
144
|
+
rows,
|
|
145
|
+
targetSlug: field.to ?? "",
|
|
146
|
+
recordId: field.id ?? ""
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
});
|
|
151
|
+
function resolveCurrency(field, record) {
|
|
152
|
+
if (field.currencyField && record) {
|
|
153
|
+
const code = record[field.currencyField];
|
|
154
|
+
if (typeof code === "string" && code.trim().length > 0) return code;
|
|
155
|
+
}
|
|
156
|
+
return field.currency;
|
|
157
|
+
}
|
|
158
|
+
function currencySymbol(currency) {
|
|
159
|
+
const code = currency && currency.length > 0 ? currency : "USD";
|
|
160
|
+
try {
|
|
161
|
+
return new Intl.NumberFormat(locale.value, {
|
|
162
|
+
style: "currency",
|
|
163
|
+
currency: code
|
|
164
|
+
}).formatToParts(0).find((entry) => entry.type === "currency")?.value ?? code;
|
|
165
|
+
} catch {
|
|
166
|
+
return code;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function formatMoney(value, currency, displayLocale) {
|
|
170
|
+
if (value === void 0 || value === "") return "—";
|
|
171
|
+
const amount = typeof value === "number" ? value : Number(value);
|
|
172
|
+
if (!Number.isFinite(amount)) return String(value);
|
|
173
|
+
const currencyCode = currency && currency.length > 0 ? currency : "USD";
|
|
174
|
+
try {
|
|
175
|
+
return new Intl.NumberFormat(displayLocale, {
|
|
176
|
+
style: "currency",
|
|
177
|
+
currency: currencyCode
|
|
178
|
+
}).format(amount);
|
|
179
|
+
} catch {
|
|
180
|
+
return String(amount);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function formatCell(value, type) {
|
|
184
|
+
if (value === void 0 || value === null || value === "") return "—";
|
|
185
|
+
if (type === "markdown" && typeof value === "string") return value.length > 80 ? `${value.slice(0, 80)}…` : value;
|
|
186
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
187
|
+
return JSON.stringify(value);
|
|
188
|
+
}
|
|
189
|
+
function isExternalUrl(value) {
|
|
190
|
+
return typeof value === "string" && /^https?:\/\//i.test(value);
|
|
191
|
+
}
|
|
192
|
+
function artifactUrl(value) {
|
|
193
|
+
return collectionUi().fileAssetUrl(value);
|
|
194
|
+
}
|
|
195
|
+
function fileRoutePath(value) {
|
|
196
|
+
return collectionUi().fileRoutePath(value);
|
|
197
|
+
}
|
|
198
|
+
function detailText(value) {
|
|
199
|
+
if (value === void 0 || value === null || value === "") return "—";
|
|
200
|
+
return String(value);
|
|
201
|
+
}
|
|
202
|
+
function tableRows(value) {
|
|
203
|
+
if (!Array.isArray(value)) return [];
|
|
204
|
+
return value.filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row));
|
|
205
|
+
}
|
|
206
|
+
function hasTableRows(value) {
|
|
207
|
+
return tableRows(value).length > 0;
|
|
208
|
+
}
|
|
209
|
+
function formatSubCell(subField, value, record) {
|
|
210
|
+
if (subField.type === "money") return formatMoney(value, resolveCurrency(subField, record), locale.value);
|
|
211
|
+
if (subField.type === "ref" && subField.to && typeof value === "string" && value.length > 0) return refDisplay(subField.to, value);
|
|
212
|
+
return formatCell(value, subField.type);
|
|
213
|
+
}
|
|
214
|
+
function inputTypeFor(type) {
|
|
215
|
+
if (type === "email") return "email";
|
|
216
|
+
if (type === "number") return "number";
|
|
217
|
+
if (type === "money") return "number";
|
|
218
|
+
if (type === "date") return "date";
|
|
219
|
+
if (type === "datetime") return "datetime-local";
|
|
220
|
+
return "text";
|
|
221
|
+
}
|
|
222
|
+
function evaluateDerivedAgainstItem(field, fieldKey, item) {
|
|
223
|
+
if (!field.formula || !collection.value) return null;
|
|
224
|
+
const result = deriveAll(collection.value.schema, item, refRecordCache.value)[fieldKey];
|
|
225
|
+
return typeof result === "number" && Number.isFinite(result) ? result : null;
|
|
226
|
+
}
|
|
227
|
+
function derivedDisplay(field, computedValue, record) {
|
|
228
|
+
if (computedValue === null || computedValue === void 0) return "—";
|
|
229
|
+
if (field.display === "money") return formatMoney(computedValue, resolveCurrency(field, record), locale.value);
|
|
230
|
+
return formatCell(computedValue, field.display ?? "number");
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
refCache,
|
|
234
|
+
refRecordCache,
|
|
235
|
+
embedCache,
|
|
236
|
+
resetLinkedCaches,
|
|
237
|
+
loadLinkedCollections,
|
|
238
|
+
refDisplay,
|
|
239
|
+
refOptions,
|
|
240
|
+
embedViews,
|
|
241
|
+
resolveCurrency,
|
|
242
|
+
currencySymbol,
|
|
243
|
+
formatMoney,
|
|
244
|
+
formatCell,
|
|
245
|
+
detailText,
|
|
246
|
+
isExternalUrl,
|
|
247
|
+
artifactUrl,
|
|
248
|
+
fileRoutePath,
|
|
249
|
+
tableRows,
|
|
250
|
+
hasTableRows,
|
|
251
|
+
formatSubCell,
|
|
252
|
+
inputTypeFor,
|
|
253
|
+
deriveAll,
|
|
254
|
+
evaluateDerivedAgainstItem,
|
|
255
|
+
derivedDisplay
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/vue/components/CollectionRecordModal.vue
|
|
260
|
+
var CollectionRecordModal_default = /* @__PURE__ */ defineComponent({
|
|
261
|
+
__name: "CollectionRecordModal",
|
|
262
|
+
emits: ["close"],
|
|
263
|
+
setup(__props, { emit: __emit }) {
|
|
264
|
+
const emit = __emit;
|
|
265
|
+
const dialogEl = ref(null);
|
|
266
|
+
let previouslyFocused = null;
|
|
267
|
+
const FOCUSABLE_SELECTOR = [
|
|
268
|
+
"a[href]",
|
|
269
|
+
"button:not([disabled])",
|
|
270
|
+
"input:not([disabled])",
|
|
271
|
+
"select:not([disabled])",
|
|
272
|
+
"textarea:not([disabled])",
|
|
273
|
+
"[tabindex]:not([tabindex=\"-1\"])"
|
|
274
|
+
].join(",");
|
|
275
|
+
/** Visible, focusable controls inside the dialog, in DOM order. */
|
|
276
|
+
function focusableItems() {
|
|
277
|
+
if (!dialogEl.value) return [];
|
|
278
|
+
return Array.from(dialogEl.value.querySelectorAll(FOCUSABLE_SELECTOR)).filter((node) => node.offsetParent !== null);
|
|
279
|
+
}
|
|
280
|
+
/** Trap Tab / Shift+Tab inside the dialog so focus can't escape to the
|
|
281
|
+
* page behind the overlay. Wraps at both ends; the dialog container
|
|
282
|
+
* itself (tabindex -1) counts as "before the first item". */
|
|
283
|
+
function onTab(event) {
|
|
284
|
+
const items = focusableItems();
|
|
285
|
+
if (items.length === 0) {
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
dialogEl.value?.focus();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const [first] = items;
|
|
291
|
+
const last = items[items.length - 1];
|
|
292
|
+
const active = document.activeElement;
|
|
293
|
+
if (event.shiftKey) {
|
|
294
|
+
if (active === first || active === dialogEl.value) {
|
|
295
|
+
event.preventDefault();
|
|
296
|
+
last.focus();
|
|
297
|
+
}
|
|
298
|
+
} else if (active === last) {
|
|
299
|
+
event.preventDefault();
|
|
300
|
+
first.focus();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
onMounted(async () => {
|
|
304
|
+
previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
305
|
+
await nextTick();
|
|
306
|
+
dialogEl.value?.focus();
|
|
307
|
+
});
|
|
308
|
+
onBeforeUnmount(() => {
|
|
309
|
+
previouslyFocused?.focus?.();
|
|
310
|
+
});
|
|
311
|
+
return (_ctx, _cache) => {
|
|
312
|
+
return openBlock(), createBlock(Teleport, { to: "body" }, [createElementVNode("div", {
|
|
313
|
+
class: "fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4",
|
|
314
|
+
"data-testid": "collections-record-modal",
|
|
315
|
+
onClick: _cache[1] || (_cache[1] = withModifiers(($event) => emit("close"), ["self"]))
|
|
316
|
+
}, [createElementVNode("div", {
|
|
317
|
+
ref_key: "dialogEl",
|
|
318
|
+
ref: dialogEl,
|
|
319
|
+
class: "flex max-h-[85vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl bg-white shadow-xl focus:outline-none",
|
|
320
|
+
role: "dialog",
|
|
321
|
+
"aria-modal": "true",
|
|
322
|
+
tabindex: "-1",
|
|
323
|
+
onKeydown: [_cache[0] || (_cache[0] = withKeys(($event) => emit("close"), ["esc"])), withKeys(onTab, ["tab"])]
|
|
324
|
+
}, [renderSlot(_ctx.$slots, "default")], 544)])]);
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/vue/components/CollectionEmbedView.vue?vue&type=script&setup=true&lang.ts
|
|
330
|
+
var _hoisted_1$4 = { class: "flex items-center justify-between text-[10px] font-bold text-indigo-600/90 tracking-wider uppercase" };
|
|
331
|
+
var _hoisted_2$4 = { class: "flex items-center gap-1.5" };
|
|
332
|
+
var _hoisted_3$4 = { class: "bg-indigo-100/60 text-indigo-700 px-1.5 py-0.5 rounded font-mono font-medium lowercase" };
|
|
333
|
+
var _hoisted_4$4 = { class: "grid gap-x-4 gap-y-3 grid-cols-2" };
|
|
334
|
+
var _hoisted_5$4 = { class: "text-[10px] font-semibold text-slate-400 uppercase tracking-wide" };
|
|
335
|
+
var _hoisted_6$4 = ["data-testid"];
|
|
336
|
+
var _hoisted_7$4 = {
|
|
337
|
+
key: 0,
|
|
338
|
+
class: "material-icons text-emerald-600 text-sm align-middle"
|
|
339
|
+
};
|
|
340
|
+
var _hoisted_8$4 = {
|
|
341
|
+
key: 1,
|
|
342
|
+
class: "text-slate-300"
|
|
343
|
+
};
|
|
344
|
+
var _hoisted_9$4 = {
|
|
345
|
+
key: 1,
|
|
346
|
+
class: "whitespace-pre-wrap font-normal text-slate-600"
|
|
347
|
+
};
|
|
348
|
+
var _hoisted_10$4 = { key: 2 };
|
|
349
|
+
var _hoisted_11$4 = ["data-testid"];
|
|
350
|
+
var _hoisted_12$3 = { class: "flex items-start gap-3" };
|
|
351
|
+
var _hoisted_13$3 = { class: "flex-1 min-w-0" };
|
|
352
|
+
var _hoisted_14$2 = { class: "text-xs font-semibold text-red-800 uppercase tracking-wider mb-1" };
|
|
353
|
+
var _hoisted_15$2 = ["data-testid"];
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/vue/components/CollectionEmbedView.vue
|
|
356
|
+
var CollectionEmbedView_default = /* @__PURE__ */ defineComponent({
|
|
357
|
+
__name: "CollectionEmbedView",
|
|
358
|
+
props: {
|
|
359
|
+
view: {},
|
|
360
|
+
fieldKey: {}
|
|
361
|
+
},
|
|
362
|
+
setup(__props) {
|
|
363
|
+
const { t } = useI18n();
|
|
364
|
+
return (_ctx, _cache) => {
|
|
365
|
+
const _component_router_link = resolveComponent("router-link");
|
|
366
|
+
return __props.view.found ? (openBlock(), createBlock(_component_router_link, {
|
|
367
|
+
key: 0,
|
|
368
|
+
to: {
|
|
369
|
+
path: `/collections/${__props.view.targetSlug}`,
|
|
370
|
+
query: { selected: __props.view.recordId }
|
|
371
|
+
},
|
|
372
|
+
class: "group block relative rounded-xl border border-slate-200 bg-slate-50/50 p-4 pl-5 space-y-3 hover:bg-indigo-50/20 hover:border-indigo-200 transition-all duration-300 shadow-sm",
|
|
373
|
+
"data-testid": `collections-embed-${__props.fieldKey}`
|
|
374
|
+
}, {
|
|
375
|
+
default: withCtx(() => [
|
|
376
|
+
_cache[1] || (_cache[1] = createElementVNode("div", { class: "absolute left-0 top-0 bottom-0 w-1 bg-indigo-500 rounded-l-xl transition-all duration-300 group-hover:w-1.5 group-hover:bg-indigo-600" }, null, -1)),
|
|
377
|
+
createElementVNode("div", _hoisted_1$4, [createElementVNode("div", _hoisted_2$4, [_cache[0] || (_cache[0] = createElementVNode("span", { class: "material-icons text-sm" }, "link", -1)), createElementVNode("span", null, toDisplayString(__props.view.targetSlug), 1)]), createElementVNode("span", _hoisted_3$4, toDisplayString(__props.view.recordId), 1)]),
|
|
378
|
+
createElementVNode("div", _hoisted_4$4, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.view.rows, (row) => {
|
|
379
|
+
return openBlock(), createElementBlock("div", {
|
|
380
|
+
key: row.key,
|
|
381
|
+
class: "space-y-0.5"
|
|
382
|
+
}, [createElementVNode("div", _hoisted_5$4, toDisplayString(row.label), 1), createElementVNode("div", {
|
|
383
|
+
class: "text-xs text-slate-700 font-medium break-words",
|
|
384
|
+
"data-testid": `collections-embed-${__props.fieldKey}-${row.key}`
|
|
385
|
+
}, [row.type === "boolean" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [row.value === true ? (openBlock(), createElementBlock("span", _hoisted_7$4, "check_circle")) : (openBlock(), createElementBlock("span", _hoisted_8$4, "—"))], 64)) : row.type === "markdown" ? (openBlock(), createElementBlock("p", _hoisted_9$4, toDisplayString(row.display), 1)) : (openBlock(), createElementBlock("span", _hoisted_10$4, toDisplayString(row.display), 1))], 8, _hoisted_6$4)]);
|
|
386
|
+
}), 128))])
|
|
387
|
+
]),
|
|
388
|
+
_: 1
|
|
389
|
+
}, 8, ["to", "data-testid"])) : (openBlock(), createElementBlock("div", {
|
|
390
|
+
key: 1,
|
|
391
|
+
class: "relative rounded-xl border border-red-100 bg-red-50/30 p-4 pl-5 shadow-sm",
|
|
392
|
+
"data-testid": `collections-embed-${__props.fieldKey}`
|
|
393
|
+
}, [_cache[4] || (_cache[4] = createElementVNode("div", { class: "absolute left-0 top-0 bottom-0 w-1 bg-red-400 rounded-l-xl" }, null, -1)), createElementVNode("div", _hoisted_12$3, [_cache[3] || (_cache[3] = createElementVNode("span", { class: "material-icons text-red-500 text-lg mt-0.5" }, "error_outline", -1)), createElementVNode("div", _hoisted_13$3, [
|
|
394
|
+
createElementVNode("p", _hoisted_14$2, toDisplayString(unref(t)("collectionsView.embedMissingTitle")), 1),
|
|
395
|
+
createElementVNode("p", {
|
|
396
|
+
class: "text-xs text-red-600",
|
|
397
|
+
"data-testid": `collections-embed-missing-${__props.fieldKey}`
|
|
398
|
+
}, toDisplayString(unref(t)("collectionsView.embedMissing", {
|
|
399
|
+
collection: __props.view.targetSlug,
|
|
400
|
+
id: __props.view.recordId
|
|
401
|
+
})), 9, _hoisted_15$2),
|
|
402
|
+
__props.view.targetSlug ? (openBlock(), createBlock(_component_router_link, {
|
|
403
|
+
key: 0,
|
|
404
|
+
to: { path: `/collections/${__props.view.targetSlug}` },
|
|
405
|
+
class: "inline-flex items-center gap-0.5 text-xs text-indigo-600 hover:text-indigo-800 font-semibold mt-2 hover:underline"
|
|
406
|
+
}, {
|
|
407
|
+
default: withCtx(() => [createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.embedCreate")), 1), _cache[2] || (_cache[2] = createElementVNode("span", { class: "material-icons text-xs" }, "arrow_forward", -1))]),
|
|
408
|
+
_: 1
|
|
409
|
+
}, 8, ["to"])) : createCommentVNode("", true)
|
|
410
|
+
])])], 8, _hoisted_11$4));
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/vue/components/CollectionCalendarView.vue?vue&type=script&setup=true&lang.ts
|
|
416
|
+
var _hoisted_1$3 = {
|
|
417
|
+
class: "flex flex-col gap-3",
|
|
418
|
+
"data-testid": "collection-calendar"
|
|
419
|
+
};
|
|
420
|
+
var _hoisted_2$3 = { class: "flex items-center gap-2" };
|
|
421
|
+
var _hoisted_3$3 = ["aria-label"];
|
|
422
|
+
var _hoisted_4$3 = ["aria-label"];
|
|
423
|
+
var _hoisted_5$3 = {
|
|
424
|
+
class: "text-sm font-bold text-slate-800 flex-1",
|
|
425
|
+
"data-testid": "collection-calendar-month"
|
|
426
|
+
};
|
|
427
|
+
var _hoisted_6$3 = { class: "grid grid-cols-7 gap-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider select-none" };
|
|
428
|
+
var _hoisted_7$3 = { class: "grid grid-cols-7 gap-1" };
|
|
429
|
+
var _hoisted_8$3 = [
|
|
430
|
+
"aria-label",
|
|
431
|
+
"data-testid",
|
|
432
|
+
"onClick",
|
|
433
|
+
"onKeydown"
|
|
434
|
+
];
|
|
435
|
+
var _hoisted_9$3 = { class: "flex items-center justify-end" };
|
|
436
|
+
var _hoisted_10$3 = ["data-testid", "onClick"];
|
|
437
|
+
var _hoisted_11$3 = {
|
|
438
|
+
key: 0,
|
|
439
|
+
class: "flex flex-wrap items-center gap-1.5 pt-1",
|
|
440
|
+
"data-testid": "collection-calendar-no-date"
|
|
441
|
+
};
|
|
442
|
+
var _hoisted_12$2 = { class: "text-[10px] font-bold text-slate-400 uppercase tracking-wider mr-1" };
|
|
443
|
+
var _hoisted_13$2 = ["data-testid", "onClick"];
|
|
444
|
+
/** Sort key for ordering a day's chips by start time: earliest first, with
|
|
445
|
+
* clock-less all-day records sinking to the bottom (matching the day view). */
|
|
446
|
+
var DAY_CHIP_DEFAULT = "bg-indigo-50 text-indigo-700 border-indigo-100 hover:bg-indigo-100";
|
|
447
|
+
var UNDATED_CHIP_DEFAULT = "bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100";
|
|
448
|
+
//#endregion
|
|
449
|
+
//#region src/vue/components/CollectionCalendarView.vue
|
|
450
|
+
var CollectionCalendarView_default = /* @__PURE__ */ defineComponent({
|
|
451
|
+
__name: "CollectionCalendarView",
|
|
452
|
+
props: {
|
|
453
|
+
schema: {},
|
|
454
|
+
items: {},
|
|
455
|
+
anchorField: {},
|
|
456
|
+
endField: {},
|
|
457
|
+
timeField: {},
|
|
458
|
+
colorField: {},
|
|
459
|
+
selected: {}
|
|
460
|
+
},
|
|
461
|
+
emits: ["select", "openDay"],
|
|
462
|
+
setup(__props, { emit: __emit }) {
|
|
463
|
+
const props = __props;
|
|
464
|
+
const emit = __emit;
|
|
465
|
+
const { t, locale } = useI18n();
|
|
466
|
+
const now = /* @__PURE__ */ new Date();
|
|
467
|
+
const viewYear = ref(now.getFullYear());
|
|
468
|
+
const viewMonth = ref(now.getMonth() + 1);
|
|
469
|
+
const todayKey = ymdKey({
|
|
470
|
+
year: now.getFullYear(),
|
|
471
|
+
month: now.getMonth() + 1,
|
|
472
|
+
day: now.getDate()
|
|
473
|
+
});
|
|
474
|
+
const grid = computed(() => buildMonthGrid(viewYear.value, viewMonth.value));
|
|
475
|
+
const bucketed = computed(() => bucketRecords(props.items, props.anchorField, props.endField, props.timeField));
|
|
476
|
+
const labelField = computed(() => labelFieldFor(props.schema));
|
|
477
|
+
function colorOf(item) {
|
|
478
|
+
return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;
|
|
479
|
+
}
|
|
480
|
+
function sliceStartKey(slice) {
|
|
481
|
+
return slice.kind === "allDay" ? MINUTES_PER_DAY + 1 : slice.startMin;
|
|
482
|
+
}
|
|
483
|
+
/** Records whose span covers a given day, ordered by start time so the month
|
|
484
|
+
* grid stacks chips the same way the day (time-allocation) view does. */
|
|
485
|
+
function recordsOnDay(day) {
|
|
486
|
+
return bucketed.value.spans.map((span) => ({
|
|
487
|
+
span,
|
|
488
|
+
slice: daySlice(span, day)
|
|
489
|
+
})).filter((pair) => pair.slice !== null).sort((left, right) => sliceStartKey(left.slice) - sliceStartKey(right.slice)).map(({ span }) => ({
|
|
490
|
+
id: itemIdOf(span.item, props.schema),
|
|
491
|
+
label: itemLabelOf(span.item, props.schema, labelField.value),
|
|
492
|
+
color: colorOf(span.item)
|
|
493
|
+
}));
|
|
494
|
+
}
|
|
495
|
+
/** Grid cells paired with the records that land on them, computed once per
|
|
496
|
+
* render. Clicking any cell opens the day view (create happens there). */
|
|
497
|
+
const cells = computed(() => grid.value.map((cell) => ({
|
|
498
|
+
cell,
|
|
499
|
+
entries: recordsOnDay(cell.ymd)
|
|
500
|
+
})));
|
|
501
|
+
const undatedEntries = computed(() => bucketed.value.noDate.map((item) => ({
|
|
502
|
+
id: itemIdOf(item, props.schema),
|
|
503
|
+
label: itemLabelOf(item, props.schema, labelField.value),
|
|
504
|
+
color: colorOf(item)
|
|
505
|
+
})));
|
|
506
|
+
function chipClass(entry, uncolored) {
|
|
507
|
+
if (entry.id === props.selected) return "bg-indigo-600 text-white border-indigo-600";
|
|
508
|
+
if (!entry.color) return uncolored;
|
|
509
|
+
return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;
|
|
510
|
+
}
|
|
511
|
+
const monthLabel = computed(() => {
|
|
512
|
+
try {
|
|
513
|
+
return new Intl.DateTimeFormat(locale.value, {
|
|
514
|
+
month: "long",
|
|
515
|
+
year: "numeric",
|
|
516
|
+
timeZone: "UTC"
|
|
517
|
+
}).format(new Date(Date.UTC(viewYear.value, viewMonth.value - 1, 1)));
|
|
518
|
+
} catch {
|
|
519
|
+
return `${viewYear.value}-${String(viewMonth.value).padStart(2, "0")}`;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
/** Localized short weekday names, Sunday-first (matches the grid). */
|
|
523
|
+
const weekdayLabels = computed(() => {
|
|
524
|
+
try {
|
|
525
|
+
const formatter = new Intl.DateTimeFormat(locale.value, {
|
|
526
|
+
weekday: "short",
|
|
527
|
+
timeZone: "UTC"
|
|
528
|
+
});
|
|
529
|
+
return Array.from({ length: 7 }, (_, idx) => formatter.format(new Date(Date.UTC(2024, 0, 7 + idx))));
|
|
530
|
+
} catch {
|
|
531
|
+
return [
|
|
532
|
+
"Sun",
|
|
533
|
+
"Mon",
|
|
534
|
+
"Tue",
|
|
535
|
+
"Wed",
|
|
536
|
+
"Thu",
|
|
537
|
+
"Fri",
|
|
538
|
+
"Sat"
|
|
539
|
+
];
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
function stepMonth(delta) {
|
|
543
|
+
const next = viewMonth.value + delta;
|
|
544
|
+
if (next < 1) {
|
|
545
|
+
viewMonth.value = 12;
|
|
546
|
+
viewYear.value -= 1;
|
|
547
|
+
} else if (next > 12) {
|
|
548
|
+
viewMonth.value = 1;
|
|
549
|
+
viewYear.value += 1;
|
|
550
|
+
} else viewMonth.value = next;
|
|
551
|
+
}
|
|
552
|
+
function goToday() {
|
|
553
|
+
viewYear.value = now.getFullYear();
|
|
554
|
+
viewMonth.value = now.getMonth() + 1;
|
|
555
|
+
}
|
|
556
|
+
return (_ctx, _cache) => {
|
|
557
|
+
return openBlock(), createElementBlock("div", _hoisted_1$3, [
|
|
558
|
+
createElementVNode("div", _hoisted_2$3, [
|
|
559
|
+
createElementVNode("button", {
|
|
560
|
+
type: "button",
|
|
561
|
+
class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
|
|
562
|
+
"aria-label": unref(t)("collectionsView.calendarPrevMonth"),
|
|
563
|
+
"data-testid": "collection-calendar-prev",
|
|
564
|
+
onClick: _cache[0] || (_cache[0] = ($event) => stepMonth(-1))
|
|
565
|
+
}, [..._cache[2] || (_cache[2] = [createElementVNode("span", { class: "material-icons text-lg" }, "chevron_left", -1)])], 8, _hoisted_3$3),
|
|
566
|
+
createElementVNode("button", {
|
|
567
|
+
type: "button",
|
|
568
|
+
class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
|
|
569
|
+
"aria-label": unref(t)("collectionsView.calendarNextMonth"),
|
|
570
|
+
"data-testid": "collection-calendar-next",
|
|
571
|
+
onClick: _cache[1] || (_cache[1] = ($event) => stepMonth(1))
|
|
572
|
+
}, [..._cache[3] || (_cache[3] = [createElementVNode("span", { class: "material-icons text-lg" }, "chevron_right", -1)])], 8, _hoisted_4$3),
|
|
573
|
+
createElementVNode("h3", _hoisted_5$3, toDisplayString(monthLabel.value), 1),
|
|
574
|
+
createElementVNode("button", {
|
|
575
|
+
type: "button",
|
|
576
|
+
class: "h-8 px-2.5 flex items-center gap-1 rounded border border-slate-200 bg-white text-slate-600 hover:bg-slate-50 text-xs font-bold transition-colors",
|
|
577
|
+
"data-testid": "collection-calendar-today",
|
|
578
|
+
onClick: goToday
|
|
579
|
+
}, toDisplayString(unref(t)("collectionsView.calendarToday")), 1)
|
|
580
|
+
]),
|
|
581
|
+
createElementVNode("div", _hoisted_6$3, [(openBlock(true), createElementBlock(Fragment, null, renderList(weekdayLabels.value, (label, idx) => {
|
|
582
|
+
return openBlock(), createElementBlock("div", {
|
|
583
|
+
key: idx,
|
|
584
|
+
class: "px-1 py-1 text-center"
|
|
585
|
+
}, toDisplayString(label), 1);
|
|
586
|
+
}), 128))]),
|
|
587
|
+
createElementVNode("div", _hoisted_7$3, [(openBlock(true), createElementBlock(Fragment, null, renderList(cells.value, ({ cell, entries }) => {
|
|
588
|
+
return openBlock(), createElementBlock("div", {
|
|
589
|
+
key: cell.key,
|
|
590
|
+
class: normalizeClass(["min-h-[5.5rem] rounded-lg border p-1 flex flex-col gap-1 overflow-hidden transition-colors cursor-pointer hover:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/30", cell.inMonth ? "bg-white border-slate-200" : "bg-slate-50/50 border-slate-100"]),
|
|
591
|
+
role: "button",
|
|
592
|
+
tabindex: 0,
|
|
593
|
+
"aria-label": unref(t)("collectionsView.dayViewOpen", { date: cell.key }),
|
|
594
|
+
"data-testid": `collection-calendar-day-${cell.key}`,
|
|
595
|
+
onClick: ($event) => emit("openDay", cell.ymd),
|
|
596
|
+
onKeydown: [withKeys(withModifiers(($event) => emit("openDay", cell.ymd), ["self", "prevent"]), ["enter"]), withKeys(withModifiers(($event) => emit("openDay", cell.ymd), ["self", "prevent"]), ["space"])]
|
|
597
|
+
}, [createElementVNode("div", _hoisted_9$3, [createElementVNode("span", { class: normalizeClass(["text-[11px] font-bold h-5 min-w-5 px-1 inline-flex items-center justify-center rounded-full", cell.key === unref(todayKey) ? "bg-indigo-600 text-white" : cell.inMonth ? "text-slate-500" : "text-slate-300"]) }, toDisplayString(cell.ymd.day), 3)]), (openBlock(true), createElementBlock(Fragment, null, renderList(entries, (entry) => {
|
|
598
|
+
return openBlock(), createElementBlock("button", {
|
|
599
|
+
key: entry.id,
|
|
600
|
+
type: "button",
|
|
601
|
+
class: normalizeClass(["text-left text-[11px] leading-tight font-semibold truncate rounded px-1.5 py-0.5 border transition-colors", chipClass(entry, DAY_CHIP_DEFAULT)]),
|
|
602
|
+
"data-testid": `collection-calendar-chip-${entry.id}`,
|
|
603
|
+
onClick: withModifiers(($event) => emit("select", entry.id), ["stop"])
|
|
604
|
+
}, toDisplayString(entry.label), 11, _hoisted_10$3);
|
|
605
|
+
}), 128))], 42, _hoisted_8$3);
|
|
606
|
+
}), 128))]),
|
|
607
|
+
bucketed.value.noDate.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_11$3, [createElementVNode("span", _hoisted_12$2, toDisplayString(unref(t)("collectionsView.calendarNoDate")), 1), (openBlock(true), createElementBlock(Fragment, null, renderList(undatedEntries.value, (entry) => {
|
|
608
|
+
return openBlock(), createElementBlock("button", {
|
|
609
|
+
key: entry.id,
|
|
610
|
+
type: "button",
|
|
611
|
+
class: normalizeClass(["text-[11px] font-semibold truncate rounded px-1.5 py-0.5 border transition-colors", chipClass(entry, UNDATED_CHIP_DEFAULT)]),
|
|
612
|
+
"data-testid": `collection-calendar-undated-${entry.id}`,
|
|
613
|
+
onClick: ($event) => emit("select", entry.id)
|
|
614
|
+
}, toDisplayString(entry.label), 11, _hoisted_13$2);
|
|
615
|
+
}), 128))])) : createCommentVNode("", true)
|
|
616
|
+
]);
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
//#endregion
|
|
621
|
+
//#region src/vue/components/CollectionDayView.vue?vue&type=script&setup=true&lang.ts
|
|
622
|
+
var _hoisted_1$2 = { class: "flex items-center gap-2 border-b border-slate-200 px-4 py-3" };
|
|
623
|
+
var _hoisted_2$2 = {
|
|
624
|
+
class: "flex-1 text-sm font-bold text-slate-800",
|
|
625
|
+
"data-testid": "collection-day-view-title"
|
|
626
|
+
};
|
|
627
|
+
var _hoisted_3$2 = ["aria-label"];
|
|
628
|
+
var _hoisted_4$2 = ["aria-label"];
|
|
629
|
+
var _hoisted_5$2 = {
|
|
630
|
+
key: 0,
|
|
631
|
+
class: "px-4 py-10 text-center text-sm text-slate-400"
|
|
632
|
+
};
|
|
633
|
+
var _hoisted_6$2 = { class: "absolute -top-2 left-0 w-10 pr-1 text-right text-[10px] tabular-nums text-slate-400" };
|
|
634
|
+
var _hoisted_7$2 = {
|
|
635
|
+
class: "absolute inset-y-0 right-0",
|
|
636
|
+
style: { "left": "2.75rem" }
|
|
637
|
+
};
|
|
638
|
+
var _hoisted_8$2 = ["data-testid", "onClick"];
|
|
639
|
+
var _hoisted_9$2 = { class: "block truncate text-[11px] font-semibold leading-tight" };
|
|
640
|
+
var _hoisted_10$2 = {
|
|
641
|
+
key: 0,
|
|
642
|
+
"aria-hidden": "true"
|
|
643
|
+
};
|
|
644
|
+
var _hoisted_11$2 = {
|
|
645
|
+
key: 1,
|
|
646
|
+
"aria-hidden": "true"
|
|
647
|
+
};
|
|
648
|
+
var _hoisted_12$1 = {
|
|
649
|
+
key: 2,
|
|
650
|
+
class: "flex flex-wrap items-center gap-1.5 border-t border-slate-200 px-4 py-2",
|
|
651
|
+
"data-testid": "collection-day-view-all-day"
|
|
652
|
+
};
|
|
653
|
+
var _hoisted_13$1 = { class: "mr-1 text-[10px] font-bold uppercase tracking-wider text-slate-400" };
|
|
654
|
+
var _hoisted_14$1 = ["data-testid", "onClick"];
|
|
655
|
+
var _hoisted_15$1 = {
|
|
656
|
+
key: 0,
|
|
657
|
+
class: "min-w-0 flex-1 overflow-y-auto",
|
|
658
|
+
"data-testid": "collection-day-view-detail"
|
|
659
|
+
};
|
|
660
|
+
var HOUR_PX = 48;
|
|
661
|
+
var MIN_BLOCK_PX = 16;
|
|
662
|
+
var LINE_PX = 20;
|
|
663
|
+
var LANE_MIN_MINUTES = 30;
|
|
664
|
+
var MAX_CHIP_FIELDS = 3;
|
|
665
|
+
var TIMED_DEFAULT = "bg-indigo-50 text-indigo-700 border-indigo-200 hover:bg-indigo-100";
|
|
666
|
+
var ALL_DAY_DEFAULT = "bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100";
|
|
667
|
+
//#endregion
|
|
668
|
+
//#region src/vue/components/CollectionDayView.vue
|
|
669
|
+
var CollectionDayView_default = /* @__PURE__ */ defineComponent({
|
|
670
|
+
__name: "CollectionDayView",
|
|
671
|
+
props: {
|
|
672
|
+
schema: {},
|
|
673
|
+
items: {},
|
|
674
|
+
day: {},
|
|
675
|
+
anchorField: {},
|
|
676
|
+
endField: {},
|
|
677
|
+
timeField: {},
|
|
678
|
+
colorField: {},
|
|
679
|
+
selected: {},
|
|
680
|
+
canCreate: { type: Boolean },
|
|
681
|
+
showDetail: { type: Boolean }
|
|
682
|
+
},
|
|
683
|
+
emits: [
|
|
684
|
+
"select",
|
|
685
|
+
"createOn",
|
|
686
|
+
"close"
|
|
687
|
+
],
|
|
688
|
+
setup(__props, { emit: __emit }) {
|
|
689
|
+
const props = __props;
|
|
690
|
+
const emit = __emit;
|
|
691
|
+
const { t, locale } = useI18n();
|
|
692
|
+
const TOTAL_HEIGHT = HOUR_PX * 24;
|
|
693
|
+
const PX_PER_MIN = HOUR_PX / 60;
|
|
694
|
+
const scrollEl = ref(null);
|
|
695
|
+
const dialogEl = ref(null);
|
|
696
|
+
const dayKey = computed(() => ymdKey(props.day));
|
|
697
|
+
const dayLabel = computed(() => {
|
|
698
|
+
try {
|
|
699
|
+
return new Intl.DateTimeFormat(locale.value, {
|
|
700
|
+
weekday: "long",
|
|
701
|
+
year: "numeric",
|
|
702
|
+
month: "long",
|
|
703
|
+
day: "numeric",
|
|
704
|
+
timeZone: "UTC"
|
|
705
|
+
}).format(new Date(Date.UTC(props.day.year, props.day.month - 1, props.day.day)));
|
|
706
|
+
} catch {
|
|
707
|
+
return dayKey.value;
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
function hourLabel(hour) {
|
|
711
|
+
return `${String(hour).padStart(2, "0")}:00`;
|
|
712
|
+
}
|
|
713
|
+
const labelField = computed(() => labelFieldFor(props.schema));
|
|
714
|
+
const CHIP_SKIP_TYPES = new Set([
|
|
715
|
+
"date",
|
|
716
|
+
"datetime",
|
|
717
|
+
"table",
|
|
718
|
+
"embed",
|
|
719
|
+
"image",
|
|
720
|
+
"markdown"
|
|
721
|
+
]);
|
|
722
|
+
function secondaryFieldsOf(item) {
|
|
723
|
+
const out = [];
|
|
724
|
+
for (const [key, field] of Object.entries(props.schema.fields)) {
|
|
725
|
+
if (out.length >= MAX_CHIP_FIELDS) break;
|
|
726
|
+
if (key === props.schema.primaryKey || key === labelField.value) continue;
|
|
727
|
+
if (key === props.anchorField || key === props.endField || key === props.timeField) continue;
|
|
728
|
+
if (CHIP_SKIP_TYPES.has(field.type)) continue;
|
|
729
|
+
const value = item[key];
|
|
730
|
+
if (value === void 0 || value === null || typeof value === "object") continue;
|
|
731
|
+
const text = String(value);
|
|
732
|
+
if (text.length > 0) out.push(text);
|
|
733
|
+
}
|
|
734
|
+
return out;
|
|
735
|
+
}
|
|
736
|
+
function colorOf(item) {
|
|
737
|
+
return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;
|
|
738
|
+
}
|
|
739
|
+
const dayEntries = computed(() => {
|
|
740
|
+
const { spans } = bucketRecords(props.items, props.anchorField, props.endField, props.timeField);
|
|
741
|
+
const entries = [];
|
|
742
|
+
for (const span of spans) {
|
|
743
|
+
const slice = daySlice(span, props.day);
|
|
744
|
+
if (!slice) continue;
|
|
745
|
+
entries.push({
|
|
746
|
+
id: itemIdOf(span.item, props.schema),
|
|
747
|
+
label: itemLabelOf(span.item, props.schema, labelField.value),
|
|
748
|
+
secondary: secondaryFieldsOf(span.item),
|
|
749
|
+
color: colorOf(span.item),
|
|
750
|
+
slice
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
return entries;
|
|
754
|
+
});
|
|
755
|
+
const allDayEntries = computed(() => dayEntries.value.filter((entry) => entry.slice.kind === "allDay"));
|
|
756
|
+
const timedEntries = computed(() => {
|
|
757
|
+
const timed = dayEntries.value.filter((entry) => entry.slice.kind !== "allDay");
|
|
758
|
+
const lanes = assignLanes(timed.map((entry) => ({
|
|
759
|
+
startMin: entry.slice.startMin,
|
|
760
|
+
endMin: Math.max(entry.slice.endMin, entry.slice.startMin + LANE_MIN_MINUTES)
|
|
761
|
+
})));
|
|
762
|
+
return timed.map((entry, index) => {
|
|
763
|
+
const { lane, lanes: laneCount } = lanes[index];
|
|
764
|
+
const widthPct = 100 / laneCount;
|
|
765
|
+
const heightPx = entry.slice.kind === "line" ? LINE_PX : Math.max((entry.slice.endMin - entry.slice.startMin) * PX_PER_MIN, MIN_BLOCK_PX);
|
|
766
|
+
return {
|
|
767
|
+
...entry,
|
|
768
|
+
style: {
|
|
769
|
+
top: `${entry.slice.startMin * PX_PER_MIN}px`,
|
|
770
|
+
height: `${heightPx}px`,
|
|
771
|
+
left: `${lane * widthPct}%`,
|
|
772
|
+
width: `calc(${widthPct}% - 3px)`
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
function timedChipClass(entry) {
|
|
778
|
+
if (entry.id === props.selected) return "bg-indigo-600 text-white border-indigo-600 z-10";
|
|
779
|
+
if (!entry.color) return TIMED_DEFAULT;
|
|
780
|
+
return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;
|
|
781
|
+
}
|
|
782
|
+
function allDayChipClass(entry) {
|
|
783
|
+
if (entry.id === props.selected) return "bg-indigo-600 text-white border-indigo-600";
|
|
784
|
+
if (!entry.color) return ALL_DAY_DEFAULT;
|
|
785
|
+
return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;
|
|
786
|
+
}
|
|
787
|
+
function onSelect(itemId) {
|
|
788
|
+
emit("select", itemId);
|
|
789
|
+
}
|
|
790
|
+
function onCreate() {
|
|
791
|
+
emit("createOn", dayKey.value);
|
|
792
|
+
}
|
|
793
|
+
onMounted(async () => {
|
|
794
|
+
await nextTick();
|
|
795
|
+
dialogEl.value?.focus();
|
|
796
|
+
const earliest = timedEntries.value.reduce((min, entry) => Math.min(min, entry.slice.startMin), MINUTES_PER_DAY);
|
|
797
|
+
if (earliest >= 1440) return;
|
|
798
|
+
if (scrollEl.value) scrollEl.value.scrollTop = Math.max(0, (earliest - 60) * PX_PER_MIN);
|
|
799
|
+
});
|
|
800
|
+
return (_ctx, _cache) => {
|
|
801
|
+
return openBlock(), createElementBlock("div", {
|
|
802
|
+
class: "fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4",
|
|
803
|
+
"data-testid": "collection-day-view",
|
|
804
|
+
onClick: _cache[1] || (_cache[1] = withModifiers(($event) => emit("close"), ["self"])),
|
|
805
|
+
onKeydown: _cache[2] || (_cache[2] = withKeys(($event) => emit("close"), ["esc"]))
|
|
806
|
+
}, [createElementVNode("div", {
|
|
807
|
+
ref_key: "dialogEl",
|
|
808
|
+
ref: dialogEl,
|
|
809
|
+
tabindex: "-1",
|
|
810
|
+
class: normalizeClass(["flex max-h-[85vh] w-full flex-row rounded-2xl bg-white shadow-xl focus:outline-none", __props.showDetail ? "max-w-4xl" : "max-w-md"]),
|
|
811
|
+
role: "dialog",
|
|
812
|
+
"aria-modal": "true"
|
|
813
|
+
}, [createElementVNode("div", { class: normalizeClass(["flex min-h-0 flex-col", __props.showDetail ? "w-80 shrink-0 border-r border-slate-200" : "w-full"]) }, [
|
|
814
|
+
createElementVNode("div", _hoisted_1$2, [
|
|
815
|
+
createElementVNode("h3", _hoisted_2$2, toDisplayString(dayLabel.value), 1),
|
|
816
|
+
__props.canCreate ? (openBlock(), createElementBlock("button", {
|
|
817
|
+
key: 0,
|
|
818
|
+
type: "button",
|
|
819
|
+
class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
|
|
820
|
+
"aria-label": unref(t)("collectionsView.calendarCreateOn", { date: dayKey.value }),
|
|
821
|
+
"data-testid": "collection-day-view-create",
|
|
822
|
+
onClick: onCreate
|
|
823
|
+
}, [..._cache[3] || (_cache[3] = [createElementVNode("span", { class: "material-icons text-lg" }, "add", -1)])], 8, _hoisted_3$2)) : createCommentVNode("", true),
|
|
824
|
+
createElementVNode("button", {
|
|
825
|
+
type: "button",
|
|
826
|
+
class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
|
|
827
|
+
"aria-label": unref(t)("collectionsView.dayViewClose"),
|
|
828
|
+
"data-testid": "collection-day-view-close",
|
|
829
|
+
onClick: _cache[0] || (_cache[0] = ($event) => emit("close"))
|
|
830
|
+
}, [..._cache[4] || (_cache[4] = [createElementVNode("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_4$2)
|
|
831
|
+
]),
|
|
832
|
+
timedEntries.value.length === 0 && allDayEntries.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_5$2, toDisplayString(unref(t)("collectionsView.dayViewEmpty")), 1)) : (openBlock(), createElementBlock("div", {
|
|
833
|
+
key: 1,
|
|
834
|
+
ref_key: "scrollEl",
|
|
835
|
+
ref: scrollEl,
|
|
836
|
+
class: "flex-1 overflow-y-auto px-2 py-2"
|
|
837
|
+
}, [createElementVNode("div", {
|
|
838
|
+
class: "relative",
|
|
839
|
+
style: normalizeStyle({ height: `${TOTAL_HEIGHT}px` }),
|
|
840
|
+
"data-testid": "collection-day-view-timeline"
|
|
841
|
+
}, [(openBlock(), createElementBlock(Fragment, null, renderList(24, (hour) => {
|
|
842
|
+
return createElementVNode("div", {
|
|
843
|
+
key: hour,
|
|
844
|
+
class: "absolute left-0 right-0 border-t border-slate-100",
|
|
845
|
+
style: normalizeStyle({ top: `${(hour - 1) * HOUR_PX}px` })
|
|
846
|
+
}, [createElementVNode("span", _hoisted_6$2, toDisplayString(hourLabel(hour - 1)), 1)], 4);
|
|
847
|
+
}), 64)), createElementVNode("div", _hoisted_7$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(timedEntries.value, (entry) => {
|
|
848
|
+
return openBlock(), createElementBlock("button", {
|
|
849
|
+
key: entry.id,
|
|
850
|
+
type: "button",
|
|
851
|
+
class: normalizeClass(["absolute overflow-hidden rounded border px-1.5 py-0.5 text-left transition-colors", timedChipClass(entry)]),
|
|
852
|
+
style: normalizeStyle(entry.style),
|
|
853
|
+
"data-testid": `collection-day-view-chip-${entry.id}`,
|
|
854
|
+
onClick: ($event) => onSelect(entry.id)
|
|
855
|
+
}, [createElementVNode("span", _hoisted_9$2, [
|
|
856
|
+
entry.slice.bleedsBefore ? (openBlock(), createElementBlock("span", _hoisted_10$2, "▲ ")) : createCommentVNode("", true),
|
|
857
|
+
createTextVNode(toDisplayString(entry.label), 1),
|
|
858
|
+
entry.slice.bleedsAfter ? (openBlock(), createElementBlock("span", _hoisted_11$2, " ▼")) : createCommentVNode("", true)
|
|
859
|
+
]), (openBlock(true), createElementBlock(Fragment, null, renderList(entry.secondary, (text, i) => {
|
|
860
|
+
return openBlock(), createElementBlock("span", {
|
|
861
|
+
key: i,
|
|
862
|
+
class: "block truncate text-[10px] leading-tight opacity-70"
|
|
863
|
+
}, toDisplayString(text), 1);
|
|
864
|
+
}), 128))], 14, _hoisted_8$2);
|
|
865
|
+
}), 128))])], 4)], 512)),
|
|
866
|
+
allDayEntries.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_12$1, [createElementVNode("span", _hoisted_13$1, toDisplayString(unref(t)("collectionsView.dayViewAllDay")), 1), (openBlock(true), createElementBlock(Fragment, null, renderList(allDayEntries.value, (entry) => {
|
|
867
|
+
return openBlock(), createElementBlock("button", {
|
|
868
|
+
key: entry.id,
|
|
869
|
+
type: "button",
|
|
870
|
+
class: normalizeClass(["truncate rounded border px-1.5 py-0.5 text-[11px] font-semibold transition-colors", allDayChipClass(entry)]),
|
|
871
|
+
"data-testid": `collection-day-view-allday-${entry.id}`,
|
|
872
|
+
onClick: ($event) => onSelect(entry.id)
|
|
873
|
+
}, toDisplayString(entry.label), 11, _hoisted_14$1);
|
|
874
|
+
}), 128))])) : createCommentVNode("", true)
|
|
875
|
+
], 2), __props.showDetail ? (openBlock(), createElementBlock("div", _hoisted_15$1, [renderSlot(_ctx.$slots, "detail")])) : createCommentVNode("", true)], 2)], 32);
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
//#endregion
|
|
880
|
+
//#region src/vue/components/CollectionKanbanView.vue?vue&type=script&setup=true&lang.ts
|
|
881
|
+
var _hoisted_1$1 = {
|
|
882
|
+
class: "h-full overflow-x-auto overflow-y-hidden",
|
|
883
|
+
"data-testid": "collection-kanban"
|
|
884
|
+
};
|
|
885
|
+
var _hoisted_2$1 = { class: "flex gap-3 h-full p-1 min-w-max" };
|
|
886
|
+
var _hoisted_3$1 = ["data-testid"];
|
|
887
|
+
var _hoisted_4$1 = { class: "flex items-center justify-between px-3 py-2 border-b border-slate-200" };
|
|
888
|
+
var _hoisted_5$1 = { class: "flex items-center gap-2 min-w-0" };
|
|
889
|
+
var _hoisted_6$1 = ["title"];
|
|
890
|
+
var _hoisted_7$1 = { class: "text-[11px] text-slate-400 shrink-0" };
|
|
891
|
+
var _hoisted_8$1 = [
|
|
892
|
+
"data-testid",
|
|
893
|
+
"aria-label",
|
|
894
|
+
"onClick",
|
|
895
|
+
"onKeydown"
|
|
896
|
+
];
|
|
897
|
+
var _hoisted_9$1 = { class: "flex items-start gap-2" };
|
|
898
|
+
var _hoisted_10$1 = [
|
|
899
|
+
"checked",
|
|
900
|
+
"aria-label",
|
|
901
|
+
"data-testid",
|
|
902
|
+
"onChange"
|
|
903
|
+
];
|
|
904
|
+
var _hoisted_11$1 = { class: "text-sm font-medium text-slate-800 truncate" };
|
|
905
|
+
var UNCATEGORIZED = "";
|
|
906
|
+
//#endregion
|
|
907
|
+
//#region src/vue/components/CollectionKanbanView.vue
|
|
908
|
+
var CollectionKanbanView_default = /* @__PURE__ */ defineComponent({
|
|
909
|
+
__name: "CollectionKanbanView",
|
|
910
|
+
props: {
|
|
911
|
+
schema: {},
|
|
912
|
+
groupField: {},
|
|
913
|
+
items: {},
|
|
914
|
+
selected: {},
|
|
915
|
+
notified: {}
|
|
916
|
+
},
|
|
917
|
+
emits: ["select", "move"],
|
|
918
|
+
setup(__props, { emit: __emit }) {
|
|
919
|
+
const props = __props;
|
|
920
|
+
const emit = __emit;
|
|
921
|
+
const { t } = useI18n();
|
|
922
|
+
/** The Uncategorized column uses the empty string as its sentinel value. */
|
|
923
|
+
const groupSpec = computed(() => props.schema.fields[props.groupField]);
|
|
924
|
+
/** Declared enum values become columns in order, with a trailing
|
|
925
|
+
* Uncategorized column for empty/unknown values (also a drop target that
|
|
926
|
+
* clears the field). The Uncategorized column is omitted when the group
|
|
927
|
+
* field is `required` — there's no valid "no value" state to drop into,
|
|
928
|
+
* and clearing via it would only produce a rejected PUT. */
|
|
929
|
+
const columns = computed(() => {
|
|
930
|
+
const values = groupSpec.value?.values ?? [];
|
|
931
|
+
const declared = values.map((value) => ({
|
|
932
|
+
value,
|
|
933
|
+
label: value
|
|
934
|
+
}));
|
|
935
|
+
if (groupSpec.value?.required || values.includes(UNCATEGORIZED)) return declared;
|
|
936
|
+
return [...declared, {
|
|
937
|
+
value: UNCATEGORIZED,
|
|
938
|
+
label: t("collectionsView.kanbanUncategorized")
|
|
939
|
+
}];
|
|
940
|
+
});
|
|
941
|
+
function itemId(item) {
|
|
942
|
+
return String(item[props.schema.primaryKey] ?? "");
|
|
943
|
+
}
|
|
944
|
+
const NOTIFY_ACCENT = {
|
|
945
|
+
urgent: "border-l-4 border-l-red-500",
|
|
946
|
+
nudge: "border-l-4 border-l-amber-500",
|
|
947
|
+
info: "border-l-4 border-l-slate-400"
|
|
948
|
+
};
|
|
949
|
+
function notifyAccentClass(item) {
|
|
950
|
+
const severity = props.notified?.get(itemId(item));
|
|
951
|
+
return severity ? NOTIFY_ACCENT[severity] : "";
|
|
952
|
+
}
|
|
953
|
+
/** Card label: the schema's `displayField` value, else the primary key. */
|
|
954
|
+
function itemLabel(item) {
|
|
955
|
+
const field = props.schema.displayField;
|
|
956
|
+
if (field) {
|
|
957
|
+
const value = item[field];
|
|
958
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
959
|
+
}
|
|
960
|
+
return itemId(item);
|
|
961
|
+
}
|
|
962
|
+
/** Which column a record belongs to: its group value when that value is
|
|
963
|
+
* one of the declared enum values, else Uncategorized. */
|
|
964
|
+
function columnOf(item) {
|
|
965
|
+
const raw = item[props.groupField];
|
|
966
|
+
if (raw === void 0 || raw === null || raw === "") return UNCATEGORIZED;
|
|
967
|
+
const value = String(raw);
|
|
968
|
+
return (groupSpec.value?.values ?? []).includes(value) ? value : UNCATEGORIZED;
|
|
969
|
+
}
|
|
970
|
+
const visibleItems = computed(() => groupSpec.value ? props.items.filter((item) => fieldVisible(groupSpec.value, item)) : []);
|
|
971
|
+
const itemsByColumnMap = computed(() => {
|
|
972
|
+
const map = /* @__PURE__ */ new Map();
|
|
973
|
+
for (const column of columns.value) map.set(column.value, []);
|
|
974
|
+
for (const item of visibleItems.value) {
|
|
975
|
+
const value = columnOf(item);
|
|
976
|
+
(map.get(value) ?? map.get(UNCATEGORIZED))?.push(item);
|
|
977
|
+
}
|
|
978
|
+
return map;
|
|
979
|
+
});
|
|
980
|
+
function itemsByColumn(value) {
|
|
981
|
+
return itemsByColumnMap.value.get(value) ?? [];
|
|
982
|
+
}
|
|
983
|
+
function onDragChange(columnValue, event) {
|
|
984
|
+
if (event.added) emit("move", itemId(event.added.element), columnValue);
|
|
985
|
+
}
|
|
986
|
+
const cardToggle = computed(() => {
|
|
987
|
+
for (const spec of Object.values(props.schema.fields)) if (spec.type === "toggle" && spec.field === props.groupField) return spec;
|
|
988
|
+
return null;
|
|
989
|
+
});
|
|
990
|
+
function cardChecked(item) {
|
|
991
|
+
const toggle = cardToggle.value;
|
|
992
|
+
return toggle !== null && String(item[props.groupField] ?? "") === toggle.onValue;
|
|
993
|
+
}
|
|
994
|
+
function onCardToggle(item) {
|
|
995
|
+
const toggle = cardToggle.value;
|
|
996
|
+
if (!toggle) return;
|
|
997
|
+
const next = cardChecked(item) ? toggle.offValue : toggle.onValue;
|
|
998
|
+
if (next === void 0) return;
|
|
999
|
+
emit("move", itemId(item), next);
|
|
1000
|
+
}
|
|
1001
|
+
return (_ctx, _cache) => {
|
|
1002
|
+
return openBlock(), createElementBlock("div", _hoisted_1$1, [createElementVNode("div", _hoisted_2$1, [(openBlock(true), createElementBlock(Fragment, null, renderList(columns.value, (column) => {
|
|
1003
|
+
return openBlock(), createElementBlock("div", {
|
|
1004
|
+
key: column.value,
|
|
1005
|
+
"data-testid": `collection-kanban-column-${column.value || "uncategorized"}`,
|
|
1006
|
+
class: "w-72 shrink-0 flex flex-col bg-slate-100 rounded-lg"
|
|
1007
|
+
}, [createElementVNode("div", _hoisted_4$1, [createElementVNode("div", _hoisted_5$1, [createElementVNode("span", { class: normalizeClass(["w-2 h-2 rounded-full shrink-0", unref(resolveEnumColor)(__props.schema, __props.groupField, column.value).dot]) }, null, 2), createElementVNode("span", {
|
|
1008
|
+
class: "font-semibold text-xs text-slate-600 truncate",
|
|
1009
|
+
title: column.label
|
|
1010
|
+
}, toDisplayString(column.label), 9, _hoisted_6$1)]), createElementVNode("span", _hoisted_7$1, toDisplayString(itemsByColumn(column.value).length), 1)]), createVNode(unref(draggable), {
|
|
1011
|
+
"model-value": itemsByColumn(column.value),
|
|
1012
|
+
"item-key": __props.schema.primaryKey,
|
|
1013
|
+
group: "collection-kanban-cards",
|
|
1014
|
+
class: "flex-1 overflow-y-auto p-2 space-y-2 min-h-[2rem]",
|
|
1015
|
+
animation: 150,
|
|
1016
|
+
onChange: (e) => onDragChange(column.value, e)
|
|
1017
|
+
}, {
|
|
1018
|
+
item: withCtx(({ element }) => [createElementVNode("div", {
|
|
1019
|
+
"data-testid": `collection-kanban-card-${itemId(element)}`,
|
|
1020
|
+
tabindex: "0",
|
|
1021
|
+
role: "button",
|
|
1022
|
+
"aria-label": unref(t)("collectionsView.kanbanOpenCard", { label: itemLabel(element) }),
|
|
1023
|
+
class: normalizeClass(["bg-white border border-slate-200 rounded shadow-sm p-2 cursor-grab hover:shadow active:cursor-grabbing focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400", [itemId(element) === __props.selected ? "ring-2 ring-indigo-500 border-indigo-300" : "", notifyAccentClass(element)]]),
|
|
1024
|
+
onClick: ($event) => emit("select", itemId(element)),
|
|
1025
|
+
onKeydown: [withKeys(withModifiers((e) => !e.repeat && emit("select", itemId(element)), ["prevent", "self"]), ["enter"]), withKeys(withModifiers((e) => !e.repeat && emit("select", itemId(element)), ["prevent", "self"]), ["space"])]
|
|
1026
|
+
}, [createElementVNode("div", _hoisted_9$1, [cardToggle.value ? (openBlock(), createElementBlock("input", {
|
|
1027
|
+
key: 0,
|
|
1028
|
+
type: "checkbox",
|
|
1029
|
+
checked: cardChecked(element),
|
|
1030
|
+
class: "mt-0.5 h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer shrink-0",
|
|
1031
|
+
"aria-label": cardToggle.value.label,
|
|
1032
|
+
"data-testid": `collection-kanban-toggle-${itemId(element)}`,
|
|
1033
|
+
onClick: _cache[0] || (_cache[0] = withModifiers(() => {}, ["stop"])),
|
|
1034
|
+
onChange: ($event) => onCardToggle(element)
|
|
1035
|
+
}, null, 40, _hoisted_10$1)) : createCommentVNode("", true), createElementVNode("div", _hoisted_11$1, toDisplayString(itemLabel(element)), 1)])], 42, _hoisted_8$1)]),
|
|
1036
|
+
_: 1
|
|
1037
|
+
}, 8, [
|
|
1038
|
+
"model-value",
|
|
1039
|
+
"item-key",
|
|
1040
|
+
"onChange"
|
|
1041
|
+
])], 8, _hoisted_3$1);
|
|
1042
|
+
}), 128))])]);
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
//#endregion
|
|
1047
|
+
//#region src/vue/components/CollectionRecordPanel.vue?vue&type=script&setup=true&lang.ts
|
|
1048
|
+
var _hoisted_1 = { class: "flex items-center gap-2 mb-4" };
|
|
1049
|
+
var _hoisted_2 = { class: "flex-1 min-w-0" };
|
|
1050
|
+
var _hoisted_3 = { class: "block text-[9px] font-bold text-slate-400 uppercase tracking-wider" };
|
|
1051
|
+
var _hoisted_4 = ["data-testid"];
|
|
1052
|
+
var _hoisted_5 = ["disabled"];
|
|
1053
|
+
var _hoisted_6 = {
|
|
1054
|
+
key: 1,
|
|
1055
|
+
class: "flex items-center gap-2"
|
|
1056
|
+
};
|
|
1057
|
+
var _hoisted_7 = [
|
|
1058
|
+
"disabled",
|
|
1059
|
+
"data-testid",
|
|
1060
|
+
"onClick"
|
|
1061
|
+
];
|
|
1062
|
+
var _hoisted_8 = {
|
|
1063
|
+
key: 0,
|
|
1064
|
+
class: "material-icons text-sm"
|
|
1065
|
+
};
|
|
1066
|
+
var _hoisted_9 = ["aria-label"];
|
|
1067
|
+
var _hoisted_10 = {
|
|
1068
|
+
key: 0,
|
|
1069
|
+
class: "mb-3 text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl shadow-sm",
|
|
1070
|
+
"data-testid": "collections-detail-action-error"
|
|
1071
|
+
};
|
|
1072
|
+
var _hoisted_11 = { class: "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4 bg-white rounded-2xl border border-slate-200/60 p-6 shadow-sm" };
|
|
1073
|
+
var _hoisted_12 = ["for"];
|
|
1074
|
+
var _hoisted_13 = {
|
|
1075
|
+
key: 0,
|
|
1076
|
+
class: "text-rose-500 font-bold"
|
|
1077
|
+
};
|
|
1078
|
+
var _hoisted_14 = {
|
|
1079
|
+
key: 0,
|
|
1080
|
+
class: "inline-flex items-center gap-2.5 text-sm text-slate-700 cursor-pointer select-none"
|
|
1081
|
+
};
|
|
1082
|
+
var _hoisted_15 = [
|
|
1083
|
+
"id",
|
|
1084
|
+
"onUpdate:modelValue",
|
|
1085
|
+
"data-testid",
|
|
1086
|
+
"onChange"
|
|
1087
|
+
];
|
|
1088
|
+
var _hoisted_16 = [
|
|
1089
|
+
"id",
|
|
1090
|
+
"onUpdate:modelValue",
|
|
1091
|
+
"required",
|
|
1092
|
+
"data-testid"
|
|
1093
|
+
];
|
|
1094
|
+
var _hoisted_17 = { value: "" };
|
|
1095
|
+
var _hoisted_18 = ["value"];
|
|
1096
|
+
var _hoisted_19 = [
|
|
1097
|
+
"id",
|
|
1098
|
+
"onUpdate:modelValue",
|
|
1099
|
+
"required",
|
|
1100
|
+
"data-testid"
|
|
1101
|
+
];
|
|
1102
|
+
var _hoisted_20 = { value: "" };
|
|
1103
|
+
var _hoisted_21 = ["value"];
|
|
1104
|
+
var _hoisted_22 = ["data-testid"];
|
|
1105
|
+
var _hoisted_23 = {
|
|
1106
|
+
key: 0,
|
|
1107
|
+
class: "overflow-hidden border border-slate-200 rounded-lg shadow-sm"
|
|
1108
|
+
};
|
|
1109
|
+
var _hoisted_24 = { class: "w-full text-xs text-slate-600 bg-white" };
|
|
1110
|
+
var _hoisted_25 = { class: "bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider" };
|
|
1111
|
+
var _hoisted_26 = { class: "divide-y divide-slate-100" };
|
|
1112
|
+
var _hoisted_27 = ["onUpdate:modelValue", "onChange"];
|
|
1113
|
+
var _hoisted_28 = ["onUpdate:modelValue", "required"];
|
|
1114
|
+
var _hoisted_29 = { value: "" };
|
|
1115
|
+
var _hoisted_30 = ["value"];
|
|
1116
|
+
var _hoisted_31 = ["onUpdate:modelValue", "required"];
|
|
1117
|
+
var _hoisted_32 = { value: "" };
|
|
1118
|
+
var _hoisted_33 = ["value"];
|
|
1119
|
+
var _hoisted_34 = {
|
|
1120
|
+
key: 3,
|
|
1121
|
+
class: "relative flex items-center"
|
|
1122
|
+
};
|
|
1123
|
+
var _hoisted_35 = { class: "absolute left-1.5 text-[10px] text-slate-400 font-bold pr-1 border-r border-slate-200" };
|
|
1124
|
+
var _hoisted_36 = ["onUpdate:modelValue", "required"];
|
|
1125
|
+
var _hoisted_37 = [
|
|
1126
|
+
"onUpdate:modelValue",
|
|
1127
|
+
"type",
|
|
1128
|
+
"required"
|
|
1129
|
+
];
|
|
1130
|
+
var _hoisted_38 = { class: "text-center px-1" };
|
|
1131
|
+
var _hoisted_39 = [
|
|
1132
|
+
"aria-label",
|
|
1133
|
+
"data-testid",
|
|
1134
|
+
"onClick"
|
|
1135
|
+
];
|
|
1136
|
+
var _hoisted_40 = {
|
|
1137
|
+
key: 1,
|
|
1138
|
+
class: "text-xs text-slate-400 italic"
|
|
1139
|
+
};
|
|
1140
|
+
var _hoisted_41 = ["data-testid", "onClick"];
|
|
1141
|
+
var _hoisted_42 = {
|
|
1142
|
+
key: 4,
|
|
1143
|
+
class: "relative flex items-center"
|
|
1144
|
+
};
|
|
1145
|
+
var _hoisted_43 = { class: "absolute left-3 text-slate-400 font-bold text-xs select-none pr-1.5 border-r border-slate-200" };
|
|
1146
|
+
var _hoisted_44 = [
|
|
1147
|
+
"id",
|
|
1148
|
+
"onUpdate:modelValue",
|
|
1149
|
+
"required",
|
|
1150
|
+
"data-testid"
|
|
1151
|
+
];
|
|
1152
|
+
var _hoisted_45 = [
|
|
1153
|
+
"id",
|
|
1154
|
+
"onUpdate:modelValue",
|
|
1155
|
+
"type",
|
|
1156
|
+
"required",
|
|
1157
|
+
"disabled",
|
|
1158
|
+
"data-testid"
|
|
1159
|
+
];
|
|
1160
|
+
var _hoisted_46 = [
|
|
1161
|
+
"id",
|
|
1162
|
+
"onUpdate:modelValue",
|
|
1163
|
+
"rows",
|
|
1164
|
+
"required",
|
|
1165
|
+
"data-testid"
|
|
1166
|
+
];
|
|
1167
|
+
var _hoisted_47 = ["data-testid"];
|
|
1168
|
+
var _hoisted_48 = {
|
|
1169
|
+
key: 0,
|
|
1170
|
+
class: "inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40"
|
|
1171
|
+
};
|
|
1172
|
+
var _hoisted_49 = {
|
|
1173
|
+
key: 1,
|
|
1174
|
+
class: "inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20"
|
|
1175
|
+
};
|
|
1176
|
+
var _hoisted_50 = {
|
|
1177
|
+
key: 0,
|
|
1178
|
+
class: "inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40"
|
|
1179
|
+
};
|
|
1180
|
+
var _hoisted_51 = {
|
|
1181
|
+
key: 1,
|
|
1182
|
+
class: "inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20"
|
|
1183
|
+
};
|
|
1184
|
+
var _hoisted_52 = {
|
|
1185
|
+
key: 2,
|
|
1186
|
+
class: "text-slate-300"
|
|
1187
|
+
};
|
|
1188
|
+
var _hoisted_53 = {
|
|
1189
|
+
key: 3,
|
|
1190
|
+
class: "font-semibold text-slate-900 tabular-nums text-sm"
|
|
1191
|
+
};
|
|
1192
|
+
var _hoisted_54 = {
|
|
1193
|
+
key: 4,
|
|
1194
|
+
class: "inline-block truncate tabular-nums font-bold text-indigo-900 bg-indigo-50/50 px-2 py-0.5 rounded border border-indigo-100/50"
|
|
1195
|
+
};
|
|
1196
|
+
var _hoisted_55 = {
|
|
1197
|
+
key: 5,
|
|
1198
|
+
class: "border border-slate-200/80 rounded-xl overflow-hidden shadow-sm mt-1"
|
|
1199
|
+
};
|
|
1200
|
+
var _hoisted_56 = { class: "w-full text-[11px] text-slate-600 bg-white" };
|
|
1201
|
+
var _hoisted_57 = { class: "bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider" };
|
|
1202
|
+
var _hoisted_58 = { class: "divide-y divide-slate-100" };
|
|
1203
|
+
var _hoisted_59 = {
|
|
1204
|
+
key: 0,
|
|
1205
|
+
class: "material-icons text-emerald-600 text-base"
|
|
1206
|
+
};
|
|
1207
|
+
var _hoisted_60 = {
|
|
1208
|
+
key: 1,
|
|
1209
|
+
class: "text-slate-300"
|
|
1210
|
+
};
|
|
1211
|
+
var _hoisted_61 = {
|
|
1212
|
+
key: 6,
|
|
1213
|
+
class: "text-slate-400 italic"
|
|
1214
|
+
};
|
|
1215
|
+
var _hoisted_62 = {
|
|
1216
|
+
key: 7,
|
|
1217
|
+
class: "bg-slate-50 rounded-xl p-4 border border-slate-200/60 text-slate-600 text-xs whitespace-pre-wrap leading-relaxed max-h-[30vh] overflow-y-auto"
|
|
1218
|
+
};
|
|
1219
|
+
var _hoisted_63 = [
|
|
1220
|
+
"src",
|
|
1221
|
+
"alt",
|
|
1222
|
+
"data-testid"
|
|
1223
|
+
];
|
|
1224
|
+
var _hoisted_64 = ["href", "data-testid"];
|
|
1225
|
+
var _hoisted_65 = ["href", "data-testid"];
|
|
1226
|
+
var _hoisted_66 = {
|
|
1227
|
+
key: 13,
|
|
1228
|
+
class: "text-slate-800 font-semibold"
|
|
1229
|
+
};
|
|
1230
|
+
var _hoisted_67 = {
|
|
1231
|
+
key: 0,
|
|
1232
|
+
class: "col-span-full text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl"
|
|
1233
|
+
};
|
|
1234
|
+
//#endregion
|
|
1235
|
+
//#region src/vue/components/CollectionRecordPanel.vue
|
|
1236
|
+
var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
|
|
1237
|
+
__name: "CollectionRecordPanel",
|
|
1238
|
+
props: /* @__PURE__ */ mergeModels({
|
|
1239
|
+
collection: {},
|
|
1240
|
+
viewing: {},
|
|
1241
|
+
saving: { type: Boolean },
|
|
1242
|
+
saveError: {},
|
|
1243
|
+
actionError: {},
|
|
1244
|
+
actionPending: { type: Boolean },
|
|
1245
|
+
visibleActions: {},
|
|
1246
|
+
liveRecord: {},
|
|
1247
|
+
liveDerived: {},
|
|
1248
|
+
viewTitle: {},
|
|
1249
|
+
isSingleton: { type: Boolean },
|
|
1250
|
+
render: {},
|
|
1251
|
+
locale: {}
|
|
1252
|
+
}, {
|
|
1253
|
+
"editing": { required: true },
|
|
1254
|
+
"editingModifiers": {}
|
|
1255
|
+
}),
|
|
1256
|
+
emits: /* @__PURE__ */ mergeModels([
|
|
1257
|
+
"submit",
|
|
1258
|
+
"cancel",
|
|
1259
|
+
"edit",
|
|
1260
|
+
"close",
|
|
1261
|
+
"delete",
|
|
1262
|
+
"runAction"
|
|
1263
|
+
], ["update:editing"]),
|
|
1264
|
+
setup(__props, { emit: __emit }) {
|
|
1265
|
+
const resolveImageSrc = collectionUi().imageSrc;
|
|
1266
|
+
const props = __props;
|
|
1267
|
+
const editing = useModel(__props, "editing");
|
|
1268
|
+
const emit = __emit;
|
|
1269
|
+
const { t } = useI18n();
|
|
1270
|
+
const embedViews = computed(() => props.render.embedViews.value);
|
|
1271
|
+
/** The record the read-only displays render from: the live draft while
|
|
1272
|
+
* editing (so non-editable cells like derived/embed preview the in-flight
|
|
1273
|
+
* values), else the open record. Always an object so `[key]` lookups are
|
|
1274
|
+
* safe in the template. */
|
|
1275
|
+
const detailRecord = computed(() => editing.value ? props.liveDerived ?? props.liveRecord ?? {} : props.viewing ?? {});
|
|
1276
|
+
/** Title for the header: the create label, the edited record's id, or the
|
|
1277
|
+
* open record's title — same h2 slot in every mode. */
|
|
1278
|
+
const headerTitle = computed(() => {
|
|
1279
|
+
if (editing.value) return editing.value.mode === "create" ? t("collectionsView.createTitle") : editing.value.originalId ?? "";
|
|
1280
|
+
return props.viewTitle;
|
|
1281
|
+
});
|
|
1282
|
+
const rootTestid = computed(() => {
|
|
1283
|
+
if (!editing.value) return "collections-detail";
|
|
1284
|
+
return editing.value.mode === "create" ? "collections-create" : "collections-edit";
|
|
1285
|
+
});
|
|
1286
|
+
/** Whether a field gets an editable control in edit mode. Toggle (a
|
|
1287
|
+
* projection of an enum that has its own input), derived (computed), and
|
|
1288
|
+
* embed (a foreign record) stay read-only in both modes, so the cell
|
|
1289
|
+
* geometry never changes on the view↔edit toggle. */
|
|
1290
|
+
function isEditableType(type) {
|
|
1291
|
+
return type !== "toggle" && type !== "derived" && type !== "embed";
|
|
1292
|
+
}
|
|
1293
|
+
/** Wide field types span the full grid width in BOTH modes — keeping
|
|
1294
|
+
* `image` full-width here (not just when viewing) is what stops a field
|
|
1295
|
+
* from jumping columns when editing starts. */
|
|
1296
|
+
function colSpanClass(field) {
|
|
1297
|
+
return [
|
|
1298
|
+
"table",
|
|
1299
|
+
"markdown",
|
|
1300
|
+
"embed",
|
|
1301
|
+
"image"
|
|
1302
|
+
].includes(field.type) ? "col-span-full" : "col-span-1";
|
|
1303
|
+
}
|
|
1304
|
+
/** Whether to render a field's cell. Identical rule in both modes so no
|
|
1305
|
+
* cell appears or disappears on toggle: respect the `when` predicate
|
|
1306
|
+
* (against the active record) and hide the primary key except while
|
|
1307
|
+
* creating. */
|
|
1308
|
+
function cellVisible(field) {
|
|
1309
|
+
if (field.primary && editing.value?.mode !== "create") return false;
|
|
1310
|
+
return fieldVisible(field, detailRecord.value);
|
|
1311
|
+
}
|
|
1312
|
+
/** Mirror of the create-mode primary-key carve-out: drop the HTML5
|
|
1313
|
+
* `required` flag on the primary field while creating so the browser
|
|
1314
|
+
* doesn't block an intentionally-blank primary (server generates the id). */
|
|
1315
|
+
function isFieldRequiredInUi(field) {
|
|
1316
|
+
if (!field.required) return false;
|
|
1317
|
+
if (editing.value?.mode === "create" && field.primary === true) return false;
|
|
1318
|
+
return true;
|
|
1319
|
+
}
|
|
1320
|
+
/** Tailwind fill/text/border classes tinting an enum `<select>` by its current
|
|
1321
|
+
* value's colour (palette, or notification red/amber/grey when the field is
|
|
1322
|
+
* the schema's notifyWhen target). */
|
|
1323
|
+
function enumControlClass(fieldKey, value) {
|
|
1324
|
+
const cls = resolveEnumColor(props.collection.schema, fieldKey, value);
|
|
1325
|
+
return `${cls.badge} ${cls.border}`;
|
|
1326
|
+
}
|
|
1327
|
+
function markBoolTouched(key) {
|
|
1328
|
+
if (editing.value) editing.value.boolTouched[key] = true;
|
|
1329
|
+
}
|
|
1330
|
+
function markRowBoolTouched(row, subKey) {
|
|
1331
|
+
row.boolTouched[subKey] = true;
|
|
1332
|
+
}
|
|
1333
|
+
function addTableRow(key, subFields) {
|
|
1334
|
+
if (!editing.value) return;
|
|
1335
|
+
const rows = editing.value.table[key] ?? [];
|
|
1336
|
+
rows.push(emptyRow(subFields));
|
|
1337
|
+
editing.value.table[key] = rows;
|
|
1338
|
+
}
|
|
1339
|
+
function removeTableRow(key, index) {
|
|
1340
|
+
if (!editing.value) return;
|
|
1341
|
+
const rows = editing.value.table[key];
|
|
1342
|
+
if (rows) rows.splice(index, 1);
|
|
1343
|
+
}
|
|
1344
|
+
return (_ctx, _cache) => {
|
|
1345
|
+
const _component_router_link = resolveComponent("router-link");
|
|
1346
|
+
return openBlock(), createBlock(resolveDynamicComponent(editing.value ? "form" : "div"), {
|
|
1347
|
+
class: "px-6 py-5 max-h-[60vh] overflow-y-auto",
|
|
1348
|
+
"data-testid": rootTestid.value,
|
|
1349
|
+
onSubmit: _cache[4] || (_cache[4] = withModifiers(($event) => emit("submit"), ["prevent"]))
|
|
1350
|
+
}, {
|
|
1351
|
+
default: withCtx(() => [
|
|
1352
|
+
createElementVNode("div", _hoisted_1, [createElementVNode("div", _hoisted_2, [createElementVNode("span", _hoisted_3, toDisplayString(__props.collection.title), 1), createElementVNode("h2", {
|
|
1353
|
+
class: "text-base font-bold text-slate-800 truncate",
|
|
1354
|
+
"data-testid": editing.value ? "collections-edit-title" : "collections-detail-title"
|
|
1355
|
+
}, toDisplayString(headerTitle.value), 9, _hoisted_4)]), editing.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("button", {
|
|
1356
|
+
type: "button",
|
|
1357
|
+
class: "h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors",
|
|
1358
|
+
"data-testid": "collections-editor-cancel",
|
|
1359
|
+
onClick: _cache[0] || (_cache[0] = ($event) => emit("cancel"))
|
|
1360
|
+
}, toDisplayString(unref(t)("common.cancel")), 1), createElementVNode("button", {
|
|
1361
|
+
type: "submit",
|
|
1362
|
+
class: "h-8 px-2.5 rounded bg-indigo-600 text-white font-bold text-xs hover:bg-indigo-700 disabled:opacity-50 transition-all shadow-sm shadow-indigo-600/10",
|
|
1363
|
+
disabled: __props.saving,
|
|
1364
|
+
"data-testid": "collections-editor-save"
|
|
1365
|
+
}, toDisplayString(__props.saving ? unref(t)("common.saving") : unref(t)("common.save")), 9, _hoisted_5)], 64)) : (openBlock(), createElementBlock("div", _hoisted_6, [
|
|
1366
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.visibleActions, (action) => {
|
|
1367
|
+
return openBlock(), createElementBlock("button", {
|
|
1368
|
+
key: action.id,
|
|
1369
|
+
type: "button",
|
|
1370
|
+
class: "h-8 px-2.5 rounded border border-indigo-200 bg-indigo-50/50 text-indigo-600 hover:bg-indigo-600 hover:text-white hover:border-indigo-600 font-bold text-xs transition-all flex items-center gap-1 disabled:opacity-50",
|
|
1371
|
+
disabled: __props.actionPending,
|
|
1372
|
+
"data-testid": `collections-detail-action-${action.id}`,
|
|
1373
|
+
onClick: ($event) => emit("runAction", action)
|
|
1374
|
+
}, [action.icon ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(action.icon), 1)) : createCommentVNode("", true), createElementVNode("span", null, toDisplayString(action.label), 1)], 8, _hoisted_7);
|
|
1375
|
+
}), 128)),
|
|
1376
|
+
createElementVNode("button", {
|
|
1377
|
+
type: "button",
|
|
1378
|
+
class: "h-8 px-2.5 rounded border border-slate-200 bg-white text-slate-700 hover:bg-slate-50 font-bold text-xs transition-all flex items-center gap-1",
|
|
1379
|
+
"data-testid": "collections-detail-edit",
|
|
1380
|
+
onClick: _cache[1] || (_cache[1] = ($event) => emit("edit"))
|
|
1381
|
+
}, [_cache[5] || (_cache[5] = createElementVNode("span", { class: "material-icons text-sm" }, "edit", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.editItem")), 1)]),
|
|
1382
|
+
createElementVNode("button", {
|
|
1383
|
+
type: "button",
|
|
1384
|
+
class: "h-8 px-2.5 rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 font-bold text-xs transition-all flex items-center gap-1",
|
|
1385
|
+
"data-testid": "collections-detail-remove",
|
|
1386
|
+
onClick: _cache[2] || (_cache[2] = ($event) => emit("delete"))
|
|
1387
|
+
}, [_cache[6] || (_cache[6] = createElementVNode("span", { class: "material-icons text-sm" }, "delete", -1)), createElementVNode("span", null, toDisplayString(unref(t)("common.remove")), 1)]),
|
|
1388
|
+
createElementVNode("button", {
|
|
1389
|
+
type: "button",
|
|
1390
|
+
class: "h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-colors",
|
|
1391
|
+
"aria-label": unref(t)("common.close"),
|
|
1392
|
+
"data-testid": "collections-detail-close",
|
|
1393
|
+
onClick: _cache[3] || (_cache[3] = ($event) => emit("close"))
|
|
1394
|
+
}, [..._cache[7] || (_cache[7] = [createElementVNode("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_9)
|
|
1395
|
+
]))]),
|
|
1396
|
+
!editing.value && __props.actionError ? (openBlock(), createElementBlock("p", _hoisted_10, toDisplayString(__props.actionError), 1)) : createCommentVNode("", true),
|
|
1397
|
+
createElementVNode("div", _hoisted_11, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.collection.schema.fields, (field, key) => {
|
|
1398
|
+
return openBlock(), createElementBlock(Fragment, { key }, [cellVisible(field) ? (openBlock(), createElementBlock("div", {
|
|
1399
|
+
key: 0,
|
|
1400
|
+
class: normalizeClass(["flex flex-col gap-1.5", colSpanClass(field)])
|
|
1401
|
+
}, [createElementVNode("label", {
|
|
1402
|
+
class: "text-[10px] font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1",
|
|
1403
|
+
for: `collections-field-${key}`
|
|
1404
|
+
}, [createTextVNode(toDisplayString(field.label) + " ", 1), editing.value && field.required ? (openBlock(), createElementBlock("span", _hoisted_13, "*")) : createCommentVNode("", true)], 8, _hoisted_12), editing.value && isEditableType(field.type) ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [field.type === "boolean" ? (openBlock(), createElementBlock("label", _hoisted_14, [withDirectives(createElementVNode("input", {
|
|
1405
|
+
id: `collections-field-${key}`,
|
|
1406
|
+
"onUpdate:modelValue": ($event) => editing.value.bool[key] = $event,
|
|
1407
|
+
type: "checkbox",
|
|
1408
|
+
class: "h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer",
|
|
1409
|
+
"data-testid": `collections-input-${key}`,
|
|
1410
|
+
onChange: ($event) => markBoolTouched(String(key))
|
|
1411
|
+
}, null, 40, _hoisted_15), [[vModelCheckbox, editing.value.bool[key]]]), createElementVNode("span", { class: normalizeClass(["text-xs font-semibold", editing.value.bool[key] ? "text-indigo-600" : "text-slate-500"]) }, toDisplayString(editing.value.bool[key] ? unref(t)("common.yes") : unref(t)("common.no")), 3)])) : field.type === "ref" && field.to && __props.render.refOptions(field.to).length > 0 ? withDirectives((openBlock(), createElementBlock("select", {
|
|
1412
|
+
key: 1,
|
|
1413
|
+
id: `collections-field-${key}`,
|
|
1414
|
+
"onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
|
|
1415
|
+
required: isFieldRequiredInUi(field),
|
|
1416
|
+
class: "w-full rounded-xl border border-slate-200 px-3 py-2 text-xs bg-slate-50 hover:bg-slate-50/50 focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium text-slate-700",
|
|
1417
|
+
"data-testid": `collections-input-${key}`
|
|
1418
|
+
}, [createElementVNode("option", _hoisted_17, toDisplayString(unref(t)("collectionsView.selectPlaceholder")), 1), (openBlock(true), createElementBlock(Fragment, null, renderList(__props.render.refOptions(field.to), (opt) => {
|
|
1419
|
+
return openBlock(), createElementBlock("option", {
|
|
1420
|
+
key: opt.slug,
|
|
1421
|
+
value: opt.slug
|
|
1422
|
+
}, toDisplayString(opt.display), 9, _hoisted_18);
|
|
1423
|
+
}), 128))], 8, _hoisted_16)), [[vModelSelect, editing.value.text[key]]]) : field.type === "enum" && Array.isArray(field.values) && field.values.length > 0 ? withDirectives((openBlock(), createElementBlock("select", {
|
|
1424
|
+
key: 2,
|
|
1425
|
+
id: `collections-field-${key}`,
|
|
1426
|
+
"onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
|
|
1427
|
+
required: isFieldRequiredInUi(field),
|
|
1428
|
+
class: normalizeClass(["w-full rounded-xl border px-3 py-2 text-xs focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium", enumControlClass(String(key), editing.value.text[key])]),
|
|
1429
|
+
"data-testid": `collections-input-${key}`
|
|
1430
|
+
}, [createElementVNode("option", _hoisted_20, toDisplayString(unref(t)("collectionsView.selectPlaceholder")), 1), (openBlock(true), createElementBlock(Fragment, null, renderList(field.values, (value) => {
|
|
1431
|
+
return openBlock(), createElementBlock("option", {
|
|
1432
|
+
key: value,
|
|
1433
|
+
value
|
|
1434
|
+
}, toDisplayString(value), 9, _hoisted_21);
|
|
1435
|
+
}), 128))], 10, _hoisted_19)), [[vModelSelect, editing.value.text[key]]]) : field.type === "table" && field.of ? (openBlock(), createElementBlock("div", {
|
|
1436
|
+
key: 3,
|
|
1437
|
+
class: "border border-slate-200 bg-slate-50/30 rounded-xl p-4 space-y-3",
|
|
1438
|
+
"data-testid": `collections-table-${key}`
|
|
1439
|
+
}, [editing.value.table[key] && editing.value.table[key].length > 0 ? (openBlock(), createElementBlock("div", _hoisted_23, [createElementVNode("table", _hoisted_24, [createElementVNode("thead", _hoisted_25, [createElementVNode("tr", null, [(openBlock(true), createElementBlock(Fragment, null, renderList(field.of, (subField, subKey) => {
|
|
1440
|
+
return openBlock(), createElementBlock("th", {
|
|
1441
|
+
key: subKey,
|
|
1442
|
+
class: "text-left px-3 py-2 font-bold"
|
|
1443
|
+
}, toDisplayString(subField.label), 1);
|
|
1444
|
+
}), 128)), _cache[8] || (_cache[8] = createElementVNode("th", { class: "w-9" }, null, -1))])]), createElementVNode("tbody", _hoisted_26, [(openBlock(true), createElementBlock(Fragment, null, renderList(editing.value.table[key], (row, rowIdx) => {
|
|
1445
|
+
return openBlock(), createElementBlock("tr", {
|
|
1446
|
+
key: rowIdx,
|
|
1447
|
+
class: "hover:bg-slate-50/50"
|
|
1448
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(field.of, (subField, subKey) => {
|
|
1449
|
+
return openBlock(), createElementBlock("td", {
|
|
1450
|
+
key: subKey,
|
|
1451
|
+
class: "px-2 py-1.5 align-middle"
|
|
1452
|
+
}, [subField.type === "boolean" ? withDirectives((openBlock(), createElementBlock("input", {
|
|
1453
|
+
key: 0,
|
|
1454
|
+
"onUpdate:modelValue": ($event) => row.bool[subKey] = $event,
|
|
1455
|
+
type: "checkbox",
|
|
1456
|
+
class: "h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer",
|
|
1457
|
+
onChange: ($event) => markRowBoolTouched(row, String(subKey))
|
|
1458
|
+
}, null, 40, _hoisted_27)), [[vModelCheckbox, row.bool[subKey]]]) : subField.type === "enum" && Array.isArray(subField.values) && subField.values.length > 0 ? withDirectives((openBlock(), createElementBlock("select", {
|
|
1459
|
+
key: 1,
|
|
1460
|
+
"onUpdate:modelValue": ($event) => row.text[subKey] = $event,
|
|
1461
|
+
required: subField.required,
|
|
1462
|
+
class: "w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium"
|
|
1463
|
+
}, [createElementVNode("option", _hoisted_29, toDisplayString(unref(t)("collectionsView.selectPlaceholder")), 1), (openBlock(true), createElementBlock(Fragment, null, renderList(subField.values, (value) => {
|
|
1464
|
+
return openBlock(), createElementBlock("option", {
|
|
1465
|
+
key: value,
|
|
1466
|
+
value
|
|
1467
|
+
}, toDisplayString(value), 9, _hoisted_30);
|
|
1468
|
+
}), 128))], 8, _hoisted_28)), [[vModelSelect, row.text[subKey]]]) : subField.type === "ref" && subField.to && __props.render.refOptions(subField.to).length > 0 ? withDirectives((openBlock(), createElementBlock("select", {
|
|
1469
|
+
key: 2,
|
|
1470
|
+
"onUpdate:modelValue": ($event) => row.text[subKey] = $event,
|
|
1471
|
+
required: subField.required,
|
|
1472
|
+
class: "w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium"
|
|
1473
|
+
}, [createElementVNode("option", _hoisted_32, toDisplayString(unref(t)("collectionsView.selectPlaceholder")), 1), (openBlock(true), createElementBlock(Fragment, null, renderList(__props.render.refOptions(subField.to), (opt) => {
|
|
1474
|
+
return openBlock(), createElementBlock("option", {
|
|
1475
|
+
key: opt.slug,
|
|
1476
|
+
value: opt.slug
|
|
1477
|
+
}, toDisplayString(opt.display), 9, _hoisted_33);
|
|
1478
|
+
}), 128))], 8, _hoisted_31)), [[vModelSelect, row.text[subKey]]]) : subField.type === "money" ? (openBlock(), createElementBlock("div", _hoisted_34, [createElementVNode("span", _hoisted_35, toDisplayString(__props.render.currencySymbol(__props.render.resolveCurrency(subField, __props.liveRecord))), 1), withDirectives(createElementVNode("input", {
|
|
1479
|
+
"onUpdate:modelValue": ($event) => row.text[subKey] = $event,
|
|
1480
|
+
type: "number",
|
|
1481
|
+
step: "0.01",
|
|
1482
|
+
required: subField.required,
|
|
1483
|
+
class: "w-full rounded-lg border border-slate-200 pl-6 pr-1.5 py-1 text-xs focus:border-indigo-500 focus:outline-none font-semibold text-slate-800"
|
|
1484
|
+
}, null, 8, _hoisted_36), [[vModelText, row.text[subKey]]])])) : withDirectives((openBlock(), createElementBlock("input", {
|
|
1485
|
+
key: 4,
|
|
1486
|
+
"onUpdate:modelValue": ($event) => row.text[subKey] = $event,
|
|
1487
|
+
type: __props.render.inputTypeFor(subField.type),
|
|
1488
|
+
required: subField.required,
|
|
1489
|
+
class: "w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none font-medium text-slate-700"
|
|
1490
|
+
}, null, 8, _hoisted_37)), [[vModelDynamic, row.text[subKey]]])]);
|
|
1491
|
+
}), 128)), createElementVNode("td", _hoisted_38, [createElementVNode("button", {
|
|
1492
|
+
type: "button",
|
|
1493
|
+
class: "h-7 w-7 flex items-center justify-center rounded-lg text-slate-400 hover:text-rose-600 hover:bg-rose-50 transition-colors",
|
|
1494
|
+
"aria-label": unref(t)("collectionsView.removeRow"),
|
|
1495
|
+
"data-testid": `collections-table-${key}-remove-${rowIdx}`,
|
|
1496
|
+
onClick: ($event) => removeTableRow(String(key), rowIdx)
|
|
1497
|
+
}, [..._cache[9] || (_cache[9] = [createElementVNode("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_39)])]);
|
|
1498
|
+
}), 128))])])])) : (openBlock(), createElementBlock("p", _hoisted_40, toDisplayString(unref(t)("collectionsView.noRows")), 1)), createElementVNode("button", {
|
|
1499
|
+
type: "button",
|
|
1500
|
+
class: "inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-800 font-bold hover:underline",
|
|
1501
|
+
"data-testid": `collections-table-${key}-add`,
|
|
1502
|
+
onClick: ($event) => addTableRow(String(key), field.of)
|
|
1503
|
+
}, [_cache[10] || (_cache[10] = createElementVNode("span", { class: "material-icons text-xs" }, "add", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.addRow")), 1)], 8, _hoisted_41)], 8, _hoisted_22)) : field.type === "money" ? (openBlock(), createElementBlock("div", _hoisted_42, [createElementVNode("div", _hoisted_43, toDisplayString(__props.render.currencySymbol(__props.render.resolveCurrency(field, __props.liveRecord))), 1), withDirectives(createElementVNode("input", {
|
|
1504
|
+
id: `collections-field-${key}`,
|
|
1505
|
+
"onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
|
|
1506
|
+
type: "number",
|
|
1507
|
+
step: "0.01",
|
|
1508
|
+
required: isFieldRequiredInUi(field),
|
|
1509
|
+
class: "w-full rounded-xl border border-slate-200 pl-11 pr-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-semibold text-slate-800 transition-all",
|
|
1510
|
+
"data-testid": `collections-input-${key}`
|
|
1511
|
+
}, null, 8, _hoisted_44), [[vModelText, editing.value.text[key]]])])) : [
|
|
1512
|
+
"string",
|
|
1513
|
+
"email",
|
|
1514
|
+
"number",
|
|
1515
|
+
"date",
|
|
1516
|
+
"datetime",
|
|
1517
|
+
"ref",
|
|
1518
|
+
"image",
|
|
1519
|
+
"file"
|
|
1520
|
+
].includes(field.type) ? withDirectives((openBlock(), createElementBlock("input", {
|
|
1521
|
+
key: 5,
|
|
1522
|
+
id: `collections-field-${key}`,
|
|
1523
|
+
"onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
|
|
1524
|
+
type: __props.render.inputTypeFor(field.type),
|
|
1525
|
+
required: isFieldRequiredInUi(field),
|
|
1526
|
+
disabled: field.primary === true && (editing.value.mode === "edit" || __props.isSingleton),
|
|
1527
|
+
class: "w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none disabled:bg-slate-100 disabled:text-slate-400 font-medium text-slate-700 transition-all",
|
|
1528
|
+
"data-testid": `collections-input-${key}`
|
|
1529
|
+
}, null, 8, _hoisted_45)), [[vModelDynamic, editing.value.text[key]]]) : withDirectives((openBlock(), createElementBlock("textarea", {
|
|
1530
|
+
key: 6,
|
|
1531
|
+
id: `collections-field-${key}`,
|
|
1532
|
+
"onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
|
|
1533
|
+
rows: field.type === "markdown" ? 5 : 3,
|
|
1534
|
+
required: isFieldRequiredInUi(field),
|
|
1535
|
+
class: "w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-medium text-slate-700 transition-all",
|
|
1536
|
+
"data-testid": `collections-input-${key}`
|
|
1537
|
+
}, null, 8, _hoisted_46)), [[vModelText, editing.value.text[key]]])], 64)) : (openBlock(), createElementBlock("div", {
|
|
1538
|
+
key: 1,
|
|
1539
|
+
class: "text-xs font-medium text-slate-700 break-words",
|
|
1540
|
+
"data-testid": `collections-detail-value-${key}`
|
|
1541
|
+
}, [field.type === "toggle" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [field.field !== void 0 && String(detailRecord.value[field.field] ?? "") === field.onValue ? (openBlock(), createElementBlock("span", _hoisted_48, [_cache[11] || (_cache[11] = createElementVNode("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), createTextVNode(" " + toDisplayString(unref(t)("common.yes")), 1)])) : (openBlock(), createElementBlock("span", _hoisted_49, toDisplayString(unref(t)("common.no")), 1))], 64)) : field.type === "boolean" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [detailRecord.value[key] === true ? (openBlock(), createElementBlock("span", _hoisted_50, [_cache[12] || (_cache[12] = createElementVNode("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), createTextVNode(" " + toDisplayString(unref(t)("common.yes")), 1)])) : detailRecord.value[key] === false ? (openBlock(), createElementBlock("span", _hoisted_51, toDisplayString(unref(t)("common.no")), 1)) : (openBlock(), createElementBlock("span", _hoisted_52, "—"))], 64)) : field.type === "ref" && field.to && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? (openBlock(), createBlock(_component_router_link, {
|
|
1542
|
+
key: 2,
|
|
1543
|
+
to: {
|
|
1544
|
+
path: `/collections/${field.to}`,
|
|
1545
|
+
query: { selected: String(detailRecord.value[key]) }
|
|
1546
|
+
},
|
|
1547
|
+
class: "text-indigo-600 hover:text-indigo-800 font-bold hover:underline",
|
|
1548
|
+
"data-testid": `collections-detail-ref-${key}`
|
|
1549
|
+
}, {
|
|
1550
|
+
default: withCtx(() => [createTextVNode(toDisplayString(__props.render.refDisplay(field.to, String(detailRecord.value[key]))), 1)]),
|
|
1551
|
+
_: 2
|
|
1552
|
+
}, 1032, ["to", "data-testid"])) : field.type === "money" ? (openBlock(), createElementBlock("span", _hoisted_53, toDisplayString(__props.render.formatMoney(detailRecord.value[key], __props.render.resolveCurrency(field, detailRecord.value), __props.locale)), 1)) : field.type === "derived" ? (openBlock(), createElementBlock("span", _hoisted_54, toDisplayString(__props.render.derivedDisplay(field, __props.render.evaluateDerivedAgainstItem(field, String(key), detailRecord.value), detailRecord.value)), 1)) : field.type === "table" && field.of && __props.render.hasTableRows(detailRecord.value[key]) ? (openBlock(), createElementBlock("div", _hoisted_55, [createElementVNode("table", _hoisted_56, [createElementVNode("thead", _hoisted_57, [createElementVNode("tr", null, [(openBlock(true), createElementBlock(Fragment, null, renderList(field.of, (subField, subKey) => {
|
|
1553
|
+
return openBlock(), createElementBlock("th", {
|
|
1554
|
+
key: subKey,
|
|
1555
|
+
class: "text-left px-4 py-2 font-bold"
|
|
1556
|
+
}, toDisplayString(subField.label), 1);
|
|
1557
|
+
}), 128))])]), createElementVNode("tbody", _hoisted_58, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.render.tableRows(detailRecord.value[key]), (row, rowIdx) => {
|
|
1558
|
+
return openBlock(), createElementBlock("tr", {
|
|
1559
|
+
key: rowIdx,
|
|
1560
|
+
class: "hover:bg-slate-50/50"
|
|
1561
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(field.of, (subField, subKey) => {
|
|
1562
|
+
return openBlock(), createElementBlock("td", {
|
|
1563
|
+
key: subKey,
|
|
1564
|
+
class: "px-4 py-2 align-middle font-medium"
|
|
1565
|
+
}, [subField.type === "boolean" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [row[subKey] === true ? (openBlock(), createElementBlock("span", _hoisted_59, "check_circle")) : (openBlock(), createElementBlock("span", _hoisted_60, "—"))], 64)) : (openBlock(), createElementBlock("span", {
|
|
1566
|
+
key: 1,
|
|
1567
|
+
class: normalizeClass([subField.type === "money" ? "font-bold text-slate-800 tabular-nums" : ""])
|
|
1568
|
+
}, toDisplayString(__props.render.formatSubCell(subField, row[subKey], detailRecord.value)), 3))]);
|
|
1569
|
+
}), 128))]);
|
|
1570
|
+
}), 128))])])])) : field.type === "table" ? (openBlock(), createElementBlock("span", _hoisted_61, toDisplayString(unref(t)("collectionsView.noRows")), 1)) : field.type === "markdown" ? (openBlock(), createElementBlock("div", _hoisted_62, toDisplayString(__props.render.detailText(detailRecord.value[key])), 1)) : field.type === "embed" && embedViews.value[key] ? (openBlock(), createBlock(CollectionEmbedView_default, {
|
|
1571
|
+
key: 8,
|
|
1572
|
+
view: embedViews.value[key],
|
|
1573
|
+
"field-key": String(key)
|
|
1574
|
+
}, null, 8, ["view", "field-key"])) : field.type === "image" && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? (openBlock(), createElementBlock("img", {
|
|
1575
|
+
key: 9,
|
|
1576
|
+
src: unref(resolveImageSrc)(String(detailRecord.value[key])),
|
|
1577
|
+
alt: field.label,
|
|
1578
|
+
class: "max-h-64 max-w-full object-contain rounded-lg border border-slate-200 bg-slate-50",
|
|
1579
|
+
"data-testid": `collections-detail-image-${key}`
|
|
1580
|
+
}, null, 8, _hoisted_63)) : field.type !== "file" && __props.render.isExternalUrl(detailRecord.value[key]) ? (openBlock(), createElementBlock("a", {
|
|
1581
|
+
key: 10,
|
|
1582
|
+
href: String(detailRecord.value[key]),
|
|
1583
|
+
target: "_blank",
|
|
1584
|
+
rel: "noopener noreferrer",
|
|
1585
|
+
class: "text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all",
|
|
1586
|
+
"data-testid": `collections-detail-url-${key}`
|
|
1587
|
+
}, toDisplayString(String(detailRecord.value[key])), 9, _hoisted_64)) : field.type === "file" && __props.render.artifactUrl(detailRecord.value[key]) ? (openBlock(), createElementBlock("a", {
|
|
1588
|
+
key: 11,
|
|
1589
|
+
href: __props.render.artifactUrl(detailRecord.value[key]) ?? void 0,
|
|
1590
|
+
target: "_blank",
|
|
1591
|
+
rel: "noopener noreferrer",
|
|
1592
|
+
class: "text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all",
|
|
1593
|
+
"data-testid": `collections-detail-file-${key}`
|
|
1594
|
+
}, toDisplayString(String(detailRecord.value[key])), 9, _hoisted_65)) : field.type === "file" && __props.render.fileRoutePath(detailRecord.value[key]) ? (openBlock(), createBlock(_component_router_link, {
|
|
1595
|
+
key: 12,
|
|
1596
|
+
to: __props.render.fileRoutePath(detailRecord.value[key]) ?? "",
|
|
1597
|
+
class: "text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all",
|
|
1598
|
+
"data-testid": `collections-detail-file-${key}`
|
|
1599
|
+
}, {
|
|
1600
|
+
default: withCtx(() => [createTextVNode(toDisplayString(String(detailRecord.value[key])), 1)]),
|
|
1601
|
+
_: 2
|
|
1602
|
+
}, 1032, ["to", "data-testid"])) : (openBlock(), createElementBlock("span", _hoisted_66, toDisplayString(__props.render.formatCell(detailRecord.value[key], field.type)), 1))], 8, _hoisted_47))], 2)) : createCommentVNode("", true)], 64);
|
|
1603
|
+
}), 128)), editing.value && __props.saveError ? (openBlock(), createElementBlock("p", _hoisted_67, toDisplayString(__props.saveError), 1)) : createCommentVNode("", true)])
|
|
1604
|
+
]),
|
|
1605
|
+
_: 1
|
|
1606
|
+
}, 40, ["data-testid"]);
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
//#endregion
|
|
1611
|
+
export { CollectionCalendarView_default as CollectionCalendarView, CollectionDayView_default as CollectionDayView, CollectionEmbedView_default as CollectionEmbedView, CollectionKanbanView_default as CollectionKanbanView, CollectionRecordModal_default as CollectionRecordModal, CollectionRecordPanel_default as CollectionRecordPanel, collectionUi, configureCollectionUi, useCollectionRendering };
|
|
1612
|
+
|
|
1613
|
+
//# sourceMappingURL=vue.js.map
|