@ossy/employees 1.0.2 → 1.2.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ossy/employees",
3
3
  "description": "Analytics module — workspace analytics for website traffic",
4
- "version": "1.0.2",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "module": "./src/index.js",
@@ -21,5 +21,11 @@
21
21
  "/src",
22
22
  "README.md"
23
23
  ],
24
- "gitHead": "54361a7e3ab2e5b6bed04b7b9caa1fd60ff99b03"
24
+ "peerDependencies": {
25
+ "@ossy/calendar": "*",
26
+ "@ossy/design-system": "*",
27
+ "@ossy/router-react": "*",
28
+ "react": "*"
29
+ },
30
+ "gitHead": "08b323d32d21600ba9519ad3f73fd5738556a68f"
25
31
  }
@@ -0,0 +1,106 @@
1
+ import React from 'react'
2
+ import { Title, View, Text, Separator } from '@ossy/design-system'
3
+ import { useEmployees } from '@ossy/calendar'
4
+
5
+ export const Employees = () => {
6
+ const employees = useEmployees()
7
+
8
+ return (
9
+ <div
10
+ style={{
11
+ width: '100%',
12
+ minHeight: 0,
13
+ display: 'grid',
14
+ gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, 380px), 1fr))',
15
+ gap: 'var(--space-l)',
16
+ }}
17
+ >
18
+
19
+ {employees.map(employee => (
20
+ <View
21
+ key={employee.id}
22
+ surface="primary"
23
+ roundness="m"
24
+ gap="m"
25
+ layout="column"
26
+ style={{
27
+ padding: 'var(--space-xl)',
28
+ boxSizing: 'border-box',
29
+ }}
30
+ >
31
+ <Title variant="tertiary">{employee.name}</Title>
32
+ <Separator />
33
+ <View gap="s" style={{ flexGrow: 1 }}>
34
+
35
+ <View layout="row" gap="s" style={{ justifyContent: 'space-between' }}>
36
+ <Text variant="m" style={{ marginBottom: 0 }}>
37
+ Id:
38
+ </Text>
39
+ <Text variant="m" style={{ marginBottom: 0 }}>
40
+ {employee.id}
41
+ </Text>
42
+ </View>
43
+
44
+ <View layout="row" gap="s" style={{ justifyContent: 'space-between' }}>
45
+ <Text variant="m" style={{ marginBottom: 0 }}>
46
+ Start date:
47
+ </Text>
48
+ <Text variant="m" style={{ marginBottom: 0 }}>
49
+ {employee.startDate}
50
+ </Text>
51
+ </View>
52
+
53
+ <View layout="row" gap="s" style={{ justifyContent: 'space-between' }}>
54
+ <Text variant="m" style={{ marginBottom: 0 }}>
55
+ End date:
56
+ </Text>
57
+ <Text variant="m" style={{ marginBottom: 0 }}>
58
+ {employee.endDate || 'ongoing'}
59
+ </Text>
60
+ </View>
61
+
62
+ <View layout="row" gap="s" style={{ justifyContent: 'space-between' }}>
63
+ <Text variant="m" style={{ marginBottom: 0 }}>
64
+ ContractId:
65
+ </Text>
66
+ <Text variant="m" style={{ marginBottom: 0 }}>
67
+ {employee.contractId}
68
+ </Text>
69
+ </View>
70
+
71
+ <View layout="row" gap="s" style={{ justifyContent: 'space-between' }}>
72
+ <Text variant="m" style={{ marginBottom: 0 }}>
73
+ Compensation:
74
+ </Text>
75
+ <Text variant="m" style={{ marginBottom: 0 }}>
76
+ {new Intl.NumberFormat('sv-SE', { style: 'currency', currency: 'SEK' }).format(employee.monthlySalary)}/month
77
+ </Text>
78
+ </View>
79
+
80
+ <View layout="row" gap="s" style={{ justifyContent: 'space-between' }}>
81
+ <Text variant="m" style={{ marginBottom: 0 }}>
82
+ Work hours per day:
83
+ </Text>
84
+ <Text variant="m" style={{ marginBottom: 0 }}>
85
+ {new Intl.NumberFormat('sv-SE', { style: 'unit', unit: 'hour' }).format(employee.workHoursPerDay)}
86
+ </Text>
87
+ </View>
88
+
89
+ <Text style={{ marginBottom: 0 }}>
90
+ vacations
91
+ </Text>
92
+
93
+ {employee?.vacations?.map(vacation => (
94
+ <Text key={`${vacation.start}-${vacation.end}`} style={{ marginBottom: 0 }}>
95
+ {vacation.start} - {vacation.end}
96
+ </Text>
97
+ ))}
98
+
99
+ </View>
100
+
101
+ </View>
102
+ ))}
103
+
104
+ </div>
105
+ )
106
+ }
@@ -0,0 +1,53 @@
1
+ import React from 'react'
2
+ import { useRouter } from '@ossy/router-react'
3
+ import { Title, Text, View, Tags, Button } from '@ossy/design-system'
4
+ import { Employees } from './Employees.jsx'
5
+ import { DataLoader } from '@ossy/calendar'
6
+ import { Definition } from '@ossy/employees'
7
+ import { moduleStatusTags } from './moduleStatus.js'
8
+
9
+ export const metadata = {
10
+ id: 'employees/home',
11
+ path: {
12
+ sv: '/anstallda',
13
+ en: '/employees',
14
+ },
15
+ }
16
+
17
+ export const EmployeesPage = () => {
18
+ const router = useRouter()
19
+ return (
20
+ <View gap="m" surface="primary" style={{ padding: 'var(--space-m) var(--space-l)', height: '100%', overflowY: 'auto' }}>
21
+
22
+ <View inset="s" gap="s">
23
+
24
+ <View layout="row" justifyContent="space-between" alignItems="center">
25
+ <View layout='row' gap="s" alignItems="center">
26
+ <Title>{Definition.title}</Title>
27
+ {moduleStatusTags(Definition).length > 0 && (
28
+ <Tags tags={moduleStatusTags(Definition)} size="s" />
29
+ )}
30
+ </View>
31
+ <Button variant="cta" disabled={true} prefix="add">
32
+ Add
33
+ </Button>
34
+ </View>
35
+
36
+ <Text style={{ maxWidth: '400px' }}>
37
+ Manage Employees
38
+ </Text>
39
+
40
+ </View>
41
+
42
+ <View gap="m" inset="s">
43
+ <Title variant="secondary">Overview</Title>
44
+
45
+ <DataLoader content={<Employees />} />
46
+
47
+ </View>
48
+
49
+ </View>
50
+ )
51
+ }
52
+
53
+ export default EmployeesPage
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Module lifecycle & visibility helpers.
3
+ *
4
+ * - `statuses` is a string[] on each Definition (e.g. `['beta']`, `['beta', 'internal']`).
5
+ * - Legacy single `status` is still read for compatibility.
6
+ * - Optional workspace gating via `workspaceServiceKey` or `workspaceServiceKeys` + `workspaceServiceRequirement`.
7
+ */
8
+
9
+ export const MODULE_STATUS = {
10
+ beta: 'beta',
11
+ comingSoon: 'coming-soon',
12
+ stable: 'stable',
13
+ /** When present, the module is omitted from the primary sidebar. */
14
+ hidden: 'hidden',
15
+ }
16
+
17
+ const STATUS_TO_TAG_LABEL = {
18
+ [MODULE_STATUS.beta]: 'Beta',
19
+ [MODULE_STATUS.comingSoon]: 'Coming Soon',
20
+ [MODULE_STATUS.stable]: null,
21
+ [MODULE_STATUS.hidden]: null,
22
+ }
23
+
24
+ /** Status values that exclude a module from the sidebar (in addition to workspace gating). */
25
+ const SIDEBAR_HIDE_STATUSES = new Set([MODULE_STATUS.hidden])
26
+
27
+ /**
28
+ * Normalize legacy `definition.status` and `definition.statuses` into one array.
29
+ * @param {{ status?: string, statuses?: string[] }} definition
30
+ * @returns {string[]}
31
+ */
32
+ export function moduleStatuses(definition) {
33
+ const fromArray = Array.isArray(definition?.statuses) ? definition.statuses : []
34
+ const fromLegacy = definition?.status && typeof definition.status === 'string' ? [definition.status] : []
35
+ if (fromArray.length) return [...new Set(fromArray)]
36
+ if (fromLegacy.length) return [...new Set(fromLegacy)]
37
+ return []
38
+ }
39
+
40
+ /**
41
+ * @param {{ status?: string, statuses?: string[] }} definition
42
+ * @returns {string[]} labels for `<Tags tags={...} />` (stable/hidden emit none)
43
+ */
44
+ export function moduleStatusTags(definition) {
45
+ const labels = []
46
+ for (const s of moduleStatuses(definition)) {
47
+ const label = STATUS_TO_TAG_LABEL[s]
48
+ if (label) labels.push(label)
49
+ }
50
+ return [...new Set(labels)]
51
+ }
52
+
53
+ /**
54
+ * @param {{ status?: string, statuses?: string[] }} definition
55
+ * @returns {boolean}
56
+ */
57
+ export function isHiddenFromSidebar(definition) {
58
+ if (!definition) return true
59
+ return moduleStatuses(definition).some(s => SIDEBAR_HIDE_STATUSES.has(s))
60
+ }
61
+
62
+ /**
63
+ * @param {Record<string, boolean> | undefined} services workspace.services
64
+ * @param {{ workspaceServiceKey?: string, workspaceServiceKeys?: string[], workspaceServiceRequirement?: 'all' | 'any' }} definition
65
+ * @returns {boolean}
66
+ */
67
+ export function workspaceAllowsModule(services, definition) {
68
+ if (!definition) return false
69
+ const keys = definition.workspaceServiceKeys?.length
70
+ ? definition.workspaceServiceKeys
71
+ : definition.workspaceServiceKey
72
+ ? [definition.workspaceServiceKey]
73
+ : null
74
+ if (!keys?.length) return true
75
+ const mode = definition.workspaceServiceRequirement || 'all'
76
+ const map = services || {}
77
+ /** Treat missing keys as allowed (legacy workspaces); only explicit `false` from disable turns a service off. */
78
+ const flags = keys.map(k => map[k] !== false)
79
+ return mode === 'any' ? flags.some(Boolean) : flags.every(Boolean)
80
+ }
81
+
82
+ /**
83
+ * Router ref for a module’s default page: `@<moduleId>/home`
84
+ * @param {string} moduleId — matches `Definition.module.id` / folder name
85
+ */
86
+ export function moduleHomeRef(moduleId) {
87
+ return `@${moduleId}/home`
88
+ }
89
+
90
+ /**
91
+ * @param {{ requiresModules?: Array<{ id: string, label: string, href: string }> }} definition
92
+ */
93
+ export function moduleRequiresModules(definition) {
94
+ return definition?.requiresModules ?? []
95
+ }