@7365admin1/layer-common 4.118.10 → 4.120.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/CHANGELOG.md +25 -4
- package/components/AccessCardDetailsDialog.vue +3 -2
- package/components/AddPassKeyToVisitor.vue +5 -0
- package/components/BuildingUnitFormEdit.vue +3 -1
- package/components/DocumentManagement.vue +56 -3
- package/components/FeedbackHandlingSettings.vue +385 -0
- package/components/HandoverChecklistRecords.vue +223 -0
- package/components/HandoverChecklistSetup.vue +224 -0
- package/components/IncidentReportEmailsSettings.vue +88 -0
- package/components/KeyPressFilter.vue +43 -0
- package/components/KeyPressName.vue +14 -0
- package/components/KeyPressSelect.vue +31 -0
- package/components/KeySettingsPanel.vue +156 -0
- package/components/ListCsvExport.vue +71 -0
- package/components/MaStaffRegistrationSettings.vue +61 -0
- package/components/MemberInformation.vue +2 -2
- package/components/PassInformation.vue +5 -0
- package/components/RolePermissionFormCreate.vue +33 -0
- package/components/SearchVehicleNumberUser.vue +3 -0
- package/components/SiteDataRetention.vue +286 -0
- package/components/SiteSettings.vue +48 -0
- package/components/SiteSettingsVisitorRegistration.vue +118 -0
- package/components/VehicleForm.vue +2 -2
- package/components/VisitorForm.vue +41 -6
- package/components/VisitorManagement.vue +3 -1
- package/components/VisitorSearchResultSummary.vue +2 -1
- package/composables/useCommonPermission.ts +10 -0
- package/composables/useDataRetention.ts +43 -0
- package/composables/useDocument.ts +13 -0
- package/composables/useFeedback.ts +12 -0
- package/composables/useHandoverChecklist.ts +111 -0
- package/composables/useKeyPresses.ts +57 -0
- package/composables/usePassKey.ts +4 -0
- package/composables/useSecurityPermission.ts +38 -0
- package/composables/useSettingsPermission.ts +16 -1
- package/composables/useSiteFeatureSettings.ts +37 -0
- package/composables/useVisitor.ts +22 -0
- package/constants/permissions.ts +3 -0
- package/package.json +2 -2
- package/types/key-log.d.ts +2 -0
- package/types/passKey.d.ts +2 -0
- package/utils/feedback-handling-settings.ts +139 -0
- package/utils/inventory-presets.ts +64 -0
- package/utils/key-presses.ts +55 -0
- package/utils/list-export.ts +39 -0
|
@@ -143,3 +143,67 @@ export function inventoryControls(a?: Partial<TInventoryAccess> | null) {
|
|
|
143
143
|
unitPrice: c.canManage || c.canApprove || c.canHandle || c.canReports,
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Whole-role starting points (TLR A7), offered in the create-role dialog when
|
|
149
|
+
* the catalogue has every module they use. Keys are the security catalogue's
|
|
150
|
+
* own (`composables/useSecurityPermission.ts`). Applying one REPLACES the
|
|
151
|
+
* ticks in the dialog and saves nothing; the admin adjusts and saves.
|
|
152
|
+
*/
|
|
153
|
+
export type TRolePreset = {
|
|
154
|
+
key: string;
|
|
155
|
+
label: string;
|
|
156
|
+
description: string;
|
|
157
|
+
permissions: string[];
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The Guardhouse officer's grants, by module, in the same nested form as the
|
|
162
|
+
* security catalogue that offers them (`useSecurityPermission.ts`). A grant
|
|
163
|
+
* list, not a check: `constants/permissions.ts` (what this package CHECKS)
|
|
164
|
+
* deliberately has no Passes & Keys entry, so the role editors there gain no
|
|
165
|
+
* extra checkbox.
|
|
166
|
+
*/
|
|
167
|
+
const GUARDHOUSE_OFFICER: Record<string, string[]> = {
|
|
168
|
+
// check-out is a visitor update on the server
|
|
169
|
+
"visitor-mgmt": ["add-visitor", "see-all-visitor", "see-visitor-details", "update-visitor"],
|
|
170
|
+
"daily-occurrence-books": ["see-all-daily-occurrence", "see-daily-occurrence-details"],
|
|
171
|
+
"daily-occurrence-entries": ["add-daily-occurrence-entries", "see-daily-occurrence-entries-details"],
|
|
172
|
+
"incident-reports": ["add-incident-report", "see-incident-reports", "see-incident-report-details"],
|
|
173
|
+
feedbacks: ["add-feedback", "see-all-feedback", "see-feedback-details"],
|
|
174
|
+
// issuing and taking back a key is a pass/key update on the server
|
|
175
|
+
"passes-and-keys-mgmt": ["see-all-passes-and-keys", "update-passes-and-keys"],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const grants = (byModule: Record<string, string[]>) =>
|
|
179
|
+
Object.entries(byModule).flatMap(([resource, actions]) => actions.map((action) => [resource, action].join(":")));
|
|
180
|
+
|
|
181
|
+
export const ROLE_PRESETS: TRolePreset[] = [
|
|
182
|
+
{
|
|
183
|
+
key: "guardhouse-officer",
|
|
184
|
+
label: "Guardhouse officer",
|
|
185
|
+
description:
|
|
186
|
+
"Register, see and check out visitors; fill in the daily occurrence book; raise and view incident reports and feedback; issue and take back passes and keys. No delete rights.",
|
|
187
|
+
permissions: grants(GUARDHOUSE_OFFICER),
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The dialog state for `preset` against the offered catalogue, or null when
|
|
193
|
+
* the catalogue lacks any of it. Never returns an empty list: an empty
|
|
194
|
+
* permission list means "everything" on the server.
|
|
195
|
+
*/
|
|
196
|
+
export function applyRolePreset(
|
|
197
|
+
catalogue: Record<string, Record<string, unknown> | undefined>,
|
|
198
|
+
preset: TRolePreset,
|
|
199
|
+
): { selected: string[]; enabled: string[] } | null {
|
|
200
|
+
const offered = preset.permissions.every((p) => {
|
|
201
|
+
const [resource, action] = p.split(":");
|
|
202
|
+
return Boolean(catalogue?.[resource]?.[action]);
|
|
203
|
+
});
|
|
204
|
+
if (!offered || !preset.permissions.length) return null;
|
|
205
|
+
return {
|
|
206
|
+
selected: [...preset.permissions],
|
|
207
|
+
enabled: [...new Set(preset.permissions.map((p) => p.split(":")[0]))],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key presses (cabinets) and the Passes & keys site switches (TLR B8 / D2).
|
|
3
|
+
* Stored on the site as `metadata.keySettings`; read with the same defaults
|
|
4
|
+
* as core's `getKeySettings`, so a site that never saved anything reads as
|
|
5
|
+
* today: no presses, no daily list, check-out never blocked by the server.
|
|
6
|
+
*/
|
|
7
|
+
export type TKeyPress = { id: string; name: string };
|
|
8
|
+
|
|
9
|
+
export type TKeySettings = {
|
|
10
|
+
cabinets: TKeyPress[];
|
|
11
|
+
dailyUnreturnedAlert: boolean;
|
|
12
|
+
alertTime: string;
|
|
13
|
+
blockCheckoutWithKeys: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function readKeySettings(site: any): TKeySettings {
|
|
17
|
+
const k = site?.metadata?.keySettings ?? {};
|
|
18
|
+
return {
|
|
19
|
+
cabinets: Array.isArray(k.cabinets)
|
|
20
|
+
? k.cabinets.filter((c: any) => c && typeof c.id === "string" && typeof c.name === "string")
|
|
21
|
+
: [],
|
|
22
|
+
dailyUnreturnedAlert: k.dailyUnreturnedAlert === true,
|
|
23
|
+
alertTime: typeof k.alertTime === "string" && /^([01]\d|2[0-3]):[0-5]\d$/.test(k.alertTime) ? k.alertTime : "18:00",
|
|
24
|
+
blockCheckoutWithKeys: k.blockCheckoutWithKeys === true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The press's name, or "" when the key has none (or one since removed). */
|
|
29
|
+
export function keyPressName(cabinets: TKeyPress[], id?: string | null): string {
|
|
30
|
+
if (!id) return "";
|
|
31
|
+
return cabinets.find((c) => c.id === id)?.name ?? "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The list as saved: names trimmed, empty rows dropped, and an id for each new
|
|
36
|
+
* row made from its name (lowercase letters, digits and dashes, unique). An
|
|
37
|
+
* existing row keeps its id, so renaming a press keeps every key in it.
|
|
38
|
+
*/
|
|
39
|
+
export function cleanKeyPresses(rows: Array<{ id?: string; name?: string }>): TKeyPress[] {
|
|
40
|
+
const out: TKeyPress[] = [];
|
|
41
|
+
const taken = new Set(rows.map((r) => r.id).filter(Boolean) as string[]);
|
|
42
|
+
for (const row of rows) {
|
|
43
|
+
const name = String(row.name ?? "").trim().slice(0, 60);
|
|
44
|
+
if (!name) continue;
|
|
45
|
+
let id = row.id;
|
|
46
|
+
if (!id) {
|
|
47
|
+
const base = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 34) || "press";
|
|
48
|
+
id = base;
|
|
49
|
+
for (let n = 2; taken.has(id); n++) id = `${base}-${n}`;
|
|
50
|
+
taken.add(id);
|
|
51
|
+
}
|
|
52
|
+
out.push({ id, name });
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSV download of a list screen (TLR B6): the rows the list itself can read,
|
|
3
|
+
* through the same list API and filters, paged through to the end. Browser-safe.
|
|
4
|
+
*/
|
|
5
|
+
import { toCsv } from "./attendance-proxy.ts";
|
|
6
|
+
|
|
7
|
+
export type TExportColumn = { title: string; value: (row: any) => unknown };
|
|
8
|
+
|
|
9
|
+
/** Largest page the list APIs accept (core feedback / work-order: max 100). */
|
|
10
|
+
export const EXPORT_PAGE_SIZE = 100;
|
|
11
|
+
/** ponytail: hard cap, so one click never pulls an estate's whole history; raise or add a server export if hit. */
|
|
12
|
+
export const EXPORT_MAX_ROWS = 5000;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Every row from `fetchPage(1..n)`, stopping at the last page, an empty page,
|
|
16
|
+
* or `maxRows`. `fetchPage` returns the list API's `{ items, pages }`.
|
|
17
|
+
*/
|
|
18
|
+
export async function collectAllPages(
|
|
19
|
+
fetchPage: (page: number, limit: number) => Promise<{ items?: any[]; pages?: number } | null | undefined>,
|
|
20
|
+
maxRows = EXPORT_MAX_ROWS,
|
|
21
|
+
limit = EXPORT_PAGE_SIZE,
|
|
22
|
+
): Promise<{ rows: any[]; truncated: boolean }> {
|
|
23
|
+
const rows: any[] = [];
|
|
24
|
+
for (let page = 1; rows.length < maxRows; page++) {
|
|
25
|
+
const res = await fetchPage(page, limit);
|
|
26
|
+
const items = Array.isArray(res?.items) ? res!.items : [];
|
|
27
|
+
rows.push(...items);
|
|
28
|
+
const last = Number(res?.pages) || 0;
|
|
29
|
+
if (!items.length || page >= last) return { rows: rows.slice(0, maxRows), truncated: rows.length > maxRows };
|
|
30
|
+
}
|
|
31
|
+
return { rows: rows.slice(0, maxRows), truncated: true };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function listCsv(columns: TExportColumn[], rows: any[]): string {
|
|
35
|
+
return toCsv(
|
|
36
|
+
columns.map((c) => c.title),
|
|
37
|
+
rows.map((row) => columns.map((c) => c.value(row))),
|
|
38
|
+
);
|
|
39
|
+
}
|