@7365admin1/layer-common 4.126.0 → 4.127.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 4.127.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e57229d: TaskCalendarReadOnly: a view-only Task Calendar list for the contractor apps (open / overdue / done tabs, task details on click). The server limits it to the caller's own company's tasks.
8
+
3
9
  ## 4.126.0
4
10
 
5
11
  ### Minor Changes
@@ -3300,11 +3306,11 @@ Monthly report: a month that had patrols opens the list of that month's patrol r
3300
3306
  `utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
3301
3307
  own rule from two endpoints the console already calls, unprojected:
3302
3308
 
3303
- GET /api/members/user/:user/app/admin the Seven365 staff membership
3304
- GET /api/roles/id/:role that membership's role document
3309
+ GET /api/members/user/:user/app/admin the Seven365 staff membership
3310
+ GET /api/roles/id/:role that membership's role document
3305
3311
 
3306
- owner = member.type === "admin" && role.type === "admin" && role.default === true
3307
- staff = member.type === "admin" && role.type === "admin"
3312
+ owner = member.type === "admin" && role.type === "admin" && role.default === true
3313
+ staff = member.type === "admin" && role.type === "admin"
3308
3314
 
3309
3315
  `role.default` is the marker because it is the only property of a platform
3310
3316
  staff role no API caller can set - `role.controller.ts` validates create and
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-col cols="12">
4
+ <PageHeader title="Task Calendar"
5
+ subtitle="Tasks the managing agent assigned to your company at this site. View only - the managing agent marks them done." />
6
+ </v-col>
7
+
8
+ <TableMain :headers="headers" :items="items" :loading="pending" :page="page" :pages="pages"
9
+ :pageRange="pageRange" :items-per-page="LIMIT" @refresh="refresh" @update:page="(p: number) => (page = p)"
10
+ @row-click="(row: any) => (selected = row?.item ?? row)">
11
+ <template #tabs>
12
+ <button v-for="tab in TABS" :key="tab.status" type="button" role="tab" class="app-tab"
13
+ :class="{ 'app-tab--active': status === tab.status }" :aria-selected="status === tab.status"
14
+ @click="status = tab.status; page = 1">
15
+ {{ tab.name }}
16
+ </button>
17
+ </template>
18
+
19
+ <template v-if="error" #no-data>
20
+ <DashboardEmptyState error />
21
+ </template>
22
+
23
+ <template v-slot:item.dateTime="{ item }">{{ dueDate(item.dateTime) }}</template>
24
+ <template v-slot:item.category="{ item }">{{ item.category || "-" }}</template>
25
+ <template v-slot:item.repeat="{ item }">{{ repeatText(item) }}</template>
26
+ <template v-slot:item.status="{ item }">
27
+ <v-chip size="small" :color="taskState(item).color" variant="tonal">{{ taskState(item).label }}</v-chip>
28
+ </template>
29
+ </TableMain>
30
+
31
+ <v-dialog :model-value="!!selected" width="520" @update:model-value="selected = null">
32
+ <v-card v-if="selected" :title="selected.title">
33
+ <v-card-text>
34
+ <p><strong>Due:</strong> {{ dueDate(selected.dateTime) }}</p>
35
+ <p><strong>Status:</strong> {{ taskState(selected).label }}</p>
36
+ <p v-if="selected.category"><strong>Category:</strong> {{ selected.category }}</p>
37
+ <p><strong>Repeats:</strong> {{ repeatText(selected) }}</p>
38
+ <p v-if="selected.description" class="mt-2" style="white-space: pre-line">{{ selected.description }}</p>
39
+ </v-card-text>
40
+ <v-card-actions>
41
+ <v-spacer />
42
+ <v-btn variant="text" @click="selected = null">Close</v-btn>
43
+ </v-card-actions>
44
+ </v-card>
45
+ </v-dialog>
46
+ </v-row>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ // Task Calendar for contractor companies (1PAM, owner 2026-10-17): READ-ONLY.
51
+ // The server limits the list to tasks whose contractor is the caller's own
52
+ // company; this screen adds nothing to that rule and offers no write.
53
+ import { readListPage } from '../utils/list-page';
54
+
55
+ const props = defineProps({
56
+ siteId: { type: String, required: true },
57
+ });
58
+
59
+ const LIMIT = 20;
60
+ const TABS = [
61
+ { name: "Open", status: "planned" },
62
+ { name: "Overdue", status: "overdue" },
63
+ { name: "Done", status: "completed" },
64
+ ];
65
+
66
+ const headers = [
67
+ { title: "Due", value: "dateTime" },
68
+ { title: "Task", value: "title" },
69
+ { title: "Category", value: "category" },
70
+ { title: "Repeats", value: "repeat" },
71
+ { title: "Status", value: "status" },
72
+ ];
73
+
74
+ const page = ref(1);
75
+ const status = ref("planned");
76
+ const selected = ref<Record<string, any> | null>(null);
77
+ const items = ref<Array<Record<string, any>>>([]);
78
+ const pages = ref(0);
79
+ const pageRange = ref("-- - -- of --");
80
+
81
+ const { data, pending, error, refresh } = await useLazyAsyncData(
82
+ `contractor-task-calendar-${props.siteId}`,
83
+ () =>
84
+ useNuxtApp().$api<Record<string, any>>("/api/event-management", {
85
+ method: "GET",
86
+ query: { site: props.siteId, page: page.value, limit: LIMIT, status: status.value },
87
+ }),
88
+ { watch: [page, status] },
89
+ );
90
+
91
+ watch(data, (reply) => {
92
+ const listPage = readListPage<Record<string, any>>(reply as any);
93
+ items.value = listPage.items;
94
+ pages.value = listPage.pages;
95
+ pageRange.value = listPage.pageRange;
96
+ }, { immediate: true });
97
+
98
+ function dueDate(value: string) {
99
+ if (!value) return "-";
100
+ return new Date(value).toLocaleDateString("en-SG", { day: "numeric", month: "short", year: "numeric", timeZone: "Asia/Singapore" });
101
+ }
102
+
103
+ function repeatText(task: Record<string, any>) {
104
+ const every = Number(task?.repeatInterval) || 1;
105
+ if (task?.repeat === "monthly") return every === 1 ? "Every month" : `Every ${every} months`;
106
+ if (task?.repeat === "yearly") return every === 1 ? "Every year" : `Every ${every} years`;
107
+ return "Once";
108
+ }
109
+
110
+ function taskState(task: Record<string, any>) {
111
+ if (task?.status === "completed") return { label: "Done", color: "success" };
112
+ if (task?.dateTime && new Date(task.dateTime) < new Date()) return { label: "Overdue", color: "error" };
113
+ return { label: "Upcoming", color: "primary" };
114
+ }
115
+ </script>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "4.126.0",
5
+ "version": "4.127.0",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",