@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 +8 -2
- package/src/Employees.jsx +106 -0
- package/src/employees.page.jsx +53 -0
- package/src/moduleStatus.js +95 -0
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
|
|
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
|
-
"
|
|
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
|
+
}
|