@ossy/timesheets 3.0.6 → 3.0.8
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 +20 -9
- package/src/Definition.js +2 -1
- package/src/HeroCover.jsx +113 -0
- package/src/SalesSection.jsx +76 -0
- package/src/Timesheet.jsx +181 -66
- package/src/TimesheetCard.jsx +36 -0
- package/src/TimesheetExportCard.jsx +212 -0
- package/src/TimesheetsFreeTool.jsx +319 -0
- package/src/TimesheetsProductHome.jsx +229 -0
- package/src/TimesheetsSalesPage.jsx +50 -0
- package/src/build-prefilled-lines.js +60 -0
- package/src/download-timesheet-csv.js +35 -0
- package/src/download-timesheet-image.js +15 -0
- package/src/download-timesheet-pdf.js +49 -0
- package/src/en.translations.json +69 -1
- package/src/export-capture.js +106 -0
- package/src/generate.action.js +5 -0
- package/src/generate.task.js +103 -0
- package/src/get.action.js +4 -0
- package/src/get.task.js +56 -0
- package/src/holiday-locale.js +64 -0
- package/src/index.js +22 -0
- package/src/invoke-error-message.js +10 -0
- package/src/list.action.js +4 -0
- package/src/list.task.js +37 -0
- package/src/locations.js +11 -0
- package/src/open-new.action.js +5 -0
- package/src/sample-showcase-timesheet.js +34 -0
- package/src/save.action.js +5 -0
- package/src/save.task.js +62 -0
- package/src/schema-ids.js +4 -0
- package/src/sv.translations.json +69 -1
- package/src/timesheet-detail.page.jsx +209 -0
- package/src/timesheet-resources.js +28 -0
- package/src/timesheet.schema.js +29 -0
- package/src/timesheets-auth-return.js +23 -0
- package/src/timesheets-home-content.js +54 -0
- package/src/timesheets.page.jsx +86 -27
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
+
import { Text, View, Button, Page, useLocale, Alert, Dropdown, ContextMenu } from '@ossy/design-system'
|
|
3
|
+
import { useRouter } from '@ossy/router-react'
|
|
4
|
+
import { useSdk } from '@ossy/sdk-react'
|
|
5
|
+
import { Timesheet } from './Timesheet.jsx'
|
|
6
|
+
import { TimesheetExportCard } from './TimesheetExportCard.jsx'
|
|
7
|
+
import { downloadTimesheetCsv } from './download-timesheet-csv.js'
|
|
8
|
+
import { downloadTimesheetImage } from './download-timesheet-image.js'
|
|
9
|
+
import { downloadTimesheetPdf } from './download-timesheet-pdf.js'
|
|
10
|
+
import { invokeErrorMessage } from './invoke-error-message.js'
|
|
11
|
+
import { metadata as GetTimesheet } from './get.action.js'
|
|
12
|
+
import { metadata as SaveTimesheet } from './save.action.js'
|
|
13
|
+
|
|
14
|
+
export const metadata = {
|
|
15
|
+
id: 'timesheets/detail',
|
|
16
|
+
path: {
|
|
17
|
+
sv: '/tidrapporter/:timesheetId',
|
|
18
|
+
en: '/timesheets/:timesheetId',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function TimesheetDetailPage () {
|
|
23
|
+
const { t } = useLocale()
|
|
24
|
+
const sdk = useSdk()
|
|
25
|
+
const router = useRouter()
|
|
26
|
+
const timesheetId = router.params?.timesheetId
|
|
27
|
+
const captureRef = useRef(null)
|
|
28
|
+
|
|
29
|
+
const [timesheet, setTimesheet] = useState(null)
|
|
30
|
+
const [lines, setLines] = useState([])
|
|
31
|
+
const [loading, setLoading] = useState(true)
|
|
32
|
+
const [saving, setSaving] = useState(false)
|
|
33
|
+
const [exporting, setExporting] = useState(false)
|
|
34
|
+
const [error, setError] = useState(null)
|
|
35
|
+
const [message, setMessage] = useState(null)
|
|
36
|
+
|
|
37
|
+
const load = useCallback(async () => {
|
|
38
|
+
if (!timesheetId) return
|
|
39
|
+
setLoading(true)
|
|
40
|
+
setError(null)
|
|
41
|
+
try {
|
|
42
|
+
const data = await sdk.invoke(GetTimesheet, { timesheetId })
|
|
43
|
+
if (!data) {
|
|
44
|
+
setError(t('timesheets.errorNotFound'))
|
|
45
|
+
setTimesheet(null)
|
|
46
|
+
setLines([])
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
setTimesheet(data)
|
|
50
|
+
setLines(data.lines ?? [])
|
|
51
|
+
} catch (err) {
|
|
52
|
+
setError(await invokeErrorMessage(err, t('timesheets.errorLoad')))
|
|
53
|
+
setTimesheet(null)
|
|
54
|
+
setLines([])
|
|
55
|
+
} finally {
|
|
56
|
+
setLoading(false)
|
|
57
|
+
}
|
|
58
|
+
}, [sdk, t, timesheetId])
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
load()
|
|
62
|
+
}, [load])
|
|
63
|
+
|
|
64
|
+
const handleSave = async () => {
|
|
65
|
+
if (!timesheet?.id) return
|
|
66
|
+
setSaving(true)
|
|
67
|
+
setError(null)
|
|
68
|
+
setMessage(null)
|
|
69
|
+
try {
|
|
70
|
+
const data = await sdk.invoke(SaveTimesheet, {
|
|
71
|
+
timesheetId: timesheet.id,
|
|
72
|
+
lines,
|
|
73
|
+
})
|
|
74
|
+
setTimesheet(data)
|
|
75
|
+
setLines(data?.lines ?? lines)
|
|
76
|
+
setMessage(t('timesheets.saved'))
|
|
77
|
+
} catch (err) {
|
|
78
|
+
setError(await invokeErrorMessage(err, t('timesheets.errorSave')))
|
|
79
|
+
} finally {
|
|
80
|
+
setSaving(false)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const exportPayload = {
|
|
85
|
+
...timesheet,
|
|
86
|
+
lines,
|
|
87
|
+
status: timesheet?.status,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const runExport = async (kind) => {
|
|
91
|
+
if (!lines.length) return
|
|
92
|
+
setExporting(true)
|
|
93
|
+
setError(null)
|
|
94
|
+
setMessage(null)
|
|
95
|
+
try {
|
|
96
|
+
if (kind === 'csv') {
|
|
97
|
+
downloadTimesheetCsv(exportPayload)
|
|
98
|
+
} else if (kind === 'png') {
|
|
99
|
+
await downloadTimesheetImage(captureRef.current, exportPayload)
|
|
100
|
+
} else if (kind === 'pdf') {
|
|
101
|
+
await downloadTimesheetPdf(captureRef.current, exportPayload)
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
setError(await invokeErrorMessage(err, t('timesheets.errorExport')))
|
|
105
|
+
} finally {
|
|
106
|
+
setExporting(false)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const homeHref = router.getHref({ id: 'timesheets/home' })
|
|
111
|
+
const exportDisabled = !lines.length || loading || exporting
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Page
|
|
115
|
+
title={(
|
|
116
|
+
<View layout="row" gap="s" alignItems="center">
|
|
117
|
+
<Button
|
|
118
|
+
prefix="chevron-left"
|
|
119
|
+
variant="command"
|
|
120
|
+
href={homeHref || undefined}
|
|
121
|
+
aria-label="timesheets.backToList"
|
|
122
|
+
onClick={homeHref ? undefined : () => router.back()}
|
|
123
|
+
/>
|
|
124
|
+
<Text variant="heading-default" as="h1" text="timesheets.detail.title" />
|
|
125
|
+
</View>
|
|
126
|
+
)}
|
|
127
|
+
description="timesheets.detail.description"
|
|
128
|
+
far={(
|
|
129
|
+
<View layout="row" gap="s" alignItems="center">
|
|
130
|
+
<Dropdown
|
|
131
|
+
trigger={(
|
|
132
|
+
<Button
|
|
133
|
+
variant="secondary"
|
|
134
|
+
prefix="software-download"
|
|
135
|
+
suffix="chevron-down"
|
|
136
|
+
disabled={exportDisabled}
|
|
137
|
+
>
|
|
138
|
+
{exporting ? t('timesheets.exporting') : t('timesheets.export')}
|
|
139
|
+
</Button>
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
<ContextMenu roundness="s" surface="primary">
|
|
143
|
+
<ContextMenu.Item
|
|
144
|
+
prefix="list"
|
|
145
|
+
label="timesheets.exportCsv"
|
|
146
|
+
onClick={() => runExport('csv')}
|
|
147
|
+
/>
|
|
148
|
+
<ContextMenu.Item
|
|
149
|
+
prefix="image"
|
|
150
|
+
label="timesheets.exportPng"
|
|
151
|
+
onClick={() => runExport('png')}
|
|
152
|
+
/>
|
|
153
|
+
<ContextMenu.Item
|
|
154
|
+
prefix="file-document"
|
|
155
|
+
label="timesheets.exportPdf"
|
|
156
|
+
onClick={() => runExport('pdf')}
|
|
157
|
+
/>
|
|
158
|
+
</ContextMenu>
|
|
159
|
+
</Dropdown>
|
|
160
|
+
<Button
|
|
161
|
+
id={SaveTimesheet.id}
|
|
162
|
+
variant="cta"
|
|
163
|
+
prefix="check"
|
|
164
|
+
disabled={!timesheet?.id || saving || loading}
|
|
165
|
+
onClick={handleSave}
|
|
166
|
+
>
|
|
167
|
+
{saving ? t('timesheets.saving') : t('timesheets.save')}
|
|
168
|
+
</Button>
|
|
169
|
+
</View>
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
<View gap="m">
|
|
173
|
+
{error && <Alert variant="danger">{error}</Alert>}
|
|
174
|
+
{message && !error && <Alert variant="success">{message}</Alert>}
|
|
175
|
+
|
|
176
|
+
{loading ? (
|
|
177
|
+
<Text color="secondary" text="timesheets.loading" />
|
|
178
|
+
) : timesheet ? (
|
|
179
|
+
<>
|
|
180
|
+
<Timesheet
|
|
181
|
+
lines={lines}
|
|
182
|
+
onChange={setLines}
|
|
183
|
+
disabled={loading || saving || exporting}
|
|
184
|
+
periodStart={timesheet.periodStart}
|
|
185
|
+
/>
|
|
186
|
+
{/* Off-screen light-theme card for PNG/PDF capture (no inputs). */}
|
|
187
|
+
<div
|
|
188
|
+
aria-hidden
|
|
189
|
+
style={{
|
|
190
|
+
position: 'fixed',
|
|
191
|
+
left: 0,
|
|
192
|
+
top: 0,
|
|
193
|
+
transform: 'translateX(-200vw)',
|
|
194
|
+
pointerEvents: 'none',
|
|
195
|
+
zIndex: -1,
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
<TimesheetExportCard
|
|
199
|
+
lines={lines}
|
|
200
|
+
periodStart={timesheet.periodStart}
|
|
201
|
+
captureRef={captureRef}
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
</>
|
|
205
|
+
) : null}
|
|
206
|
+
</View>
|
|
207
|
+
</Page>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ResourcesQueries } from '@ossy/resources/server'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* List timesheet resources by schema id (and optional workspace).
|
|
5
|
+
*/
|
|
6
|
+
export function getTimesheetResources ({ schemaId, belongsTo } = {}) {
|
|
7
|
+
const query = { type: schemaId }
|
|
8
|
+
if (belongsTo) query.belongsTo = belongsTo
|
|
9
|
+
return ResourcesQueries.GetResources(query)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function findTimesheetForPeriod (resources, periodStart) {
|
|
13
|
+
return (resources || []).find((r) => r.content?.periodStart === periodStart) ?? null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function toTimesheetDto (resource) {
|
|
17
|
+
if (!resource) return null
|
|
18
|
+
return {
|
|
19
|
+
id: resource.id,
|
|
20
|
+
periodStart: resource.content?.periodStart,
|
|
21
|
+
periodEnd: resource.content?.periodEnd,
|
|
22
|
+
status: resource.content?.status ?? 'draft',
|
|
23
|
+
holidayLocale: resource.content?.holidayLocale ?? null,
|
|
24
|
+
lines: resource.content?.lines ?? [],
|
|
25
|
+
createdAt: resource.createdAt,
|
|
26
|
+
updatedAt: resource.updatedAt,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
name: 'Timesheet',
|
|
3
|
+
id: '@ossy/timesheets/schema/timesheet',
|
|
4
|
+
categoryName: 'Timesheets',
|
|
5
|
+
icon: 'calendar',
|
|
6
|
+
fields: [
|
|
7
|
+
{
|
|
8
|
+
name: 'periodStart',
|
|
9
|
+
type: 'timestamp',
|
|
10
|
+
description: 'UTC midnight of the first day of the period (inclusive)',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'periodEnd',
|
|
14
|
+
type: 'timestamp',
|
|
15
|
+
description: 'UTC midnight of the last day of the period (inclusive)',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
// draft | saved
|
|
19
|
+
name: 'status',
|
|
20
|
+
type: 'text',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'holidayLocale',
|
|
24
|
+
type: 'text',
|
|
25
|
+
description: 'ISO 3166-1 alpha-2 country used for workday/public-holiday prefill',
|
|
26
|
+
},
|
|
27
|
+
// `lines` is stored in content (array of day rows) — same nested-content pattern as booking availability.
|
|
28
|
+
],
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { withRedirectLink } from '@ossy/authentication'
|
|
2
|
+
|
|
3
|
+
export const TIMESHEETS_SERVICE = '@ossy/timesheets'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Path to land on after verify, with enable intent for the sales page.
|
|
7
|
+
* Keep the query value literal here — `withRedirectLink` encodes once.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} timesheetsHomePath locale-aware `/timesheets` or `/tidrapporter`
|
|
10
|
+
*/
|
|
11
|
+
export function timesheetsEnableReturnPath (timesheetsHomePath = '/timesheets') {
|
|
12
|
+
const path = (timesheetsHomePath.startsWith('/') ? timesheetsHomePath : `/${timesheetsHomePath}`)
|
|
13
|
+
.split('?')[0]
|
|
14
|
+
return `${path}?enable=${TIMESHEETS_SERVICE}`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} authPath `/sign-up` or `/sign-in`
|
|
19
|
+
* @param {string} timesheetsHomePath
|
|
20
|
+
*/
|
|
21
|
+
export function timesheetsAuthHref (authPath, timesheetsHomePath) {
|
|
22
|
+
return withRedirectLink(authPath, timesheetsEnableReturnPath(timesheetsHomePath))
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useLocale } from '@ossy/design-system'
|
|
2
|
+
import { useRouter } from '@ossy/router-react'
|
|
3
|
+
import { OpenSignUp } from '@ossy/authentication'
|
|
4
|
+
import { timesheetsAuthHref } from './timesheets-auth-return.js'
|
|
5
|
+
|
|
6
|
+
export function useTimesheetsHomeContent () {
|
|
7
|
+
const { t } = useLocale()
|
|
8
|
+
const router = useRouter()
|
|
9
|
+
|
|
10
|
+
const timesheetsHome = router.getHref('timesheets/home') || '/timesheets'
|
|
11
|
+
const signUpHref = timesheetsAuthHref(
|
|
12
|
+
router.getHref('sign-up') || router.getHref('@sign-up') || '/sign-up',
|
|
13
|
+
timesheetsHome,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
const cover = {
|
|
17
|
+
title: t('timesheets.home.cover.title'),
|
|
18
|
+
text: t('timesheets.home.cover.text'),
|
|
19
|
+
// CTAs live in the free-tool cover (export + save / enable).
|
|
20
|
+
actions: [
|
|
21
|
+
{
|
|
22
|
+
...OpenSignUp,
|
|
23
|
+
variant: 'cta',
|
|
24
|
+
href: signUpHref,
|
|
25
|
+
label: 'timesheets.home.cover.ctaPrimary',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const features = [
|
|
31
|
+
{
|
|
32
|
+
title: t('timesheets.home.features.prefill.title'),
|
|
33
|
+
icon: 'calendar',
|
|
34
|
+
text: t('timesheets.home.features.prefill.text'),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
title: t('timesheets.home.features.review.title'),
|
|
38
|
+
icon: 'pen',
|
|
39
|
+
text: t('timesheets.home.features.review.text'),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: t('timesheets.home.features.export.title'),
|
|
43
|
+
icon: 'software-download',
|
|
44
|
+
text: t('timesheets.home.features.export.text'),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: t('timesheets.home.features.calendar.title'),
|
|
48
|
+
icon: 'globe',
|
|
49
|
+
text: t('timesheets.home.features.calendar.text'),
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
return { cover, features }
|
|
54
|
+
}
|
package/src/timesheets.page.jsx
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
2
|
+
import { useApp, useShellWorkspace } from '@ossy/app/shell'
|
|
3
|
+
import { GetWorkspace, EnableService } from '@ossy/workspaces'
|
|
4
|
+
import { isServiceEntitled } from '@ossy/workspaces/entitlements'
|
|
5
|
+
import { AsyncStatus, useSdk, cacheKey } from '@ossy/sdk-react'
|
|
6
|
+
import { useRouter } from '@ossy/router-react'
|
|
7
|
+
import TimesheetsSalesPage from './TimesheetsSalesPage.jsx'
|
|
8
|
+
import TimesheetsProductHome from './TimesheetsProductHome.jsx'
|
|
9
|
+
import { TIMESHEETS_SERVICE } from './timesheets-auth-return.js'
|
|
6
10
|
|
|
7
11
|
export const metadata = {
|
|
8
12
|
id: 'timesheets/home',
|
|
@@ -12,29 +16,84 @@ export const metadata = {
|
|
|
12
16
|
},
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
export
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
export default function TimesheetsHomePage () {
|
|
20
|
+
const app = useApp()
|
|
21
|
+
const router = useRouter()
|
|
22
|
+
const { workspace: shellWorkspace } = useShellWorkspace()
|
|
23
|
+
const { read, invoke, invalidate } = useSdk()
|
|
24
|
+
const isAuthenticated = !!app?.isAuthenticated
|
|
25
|
+
const {
|
|
26
|
+
data: workspaceFromSdk,
|
|
27
|
+
status: workspaceStatus,
|
|
28
|
+
} = read(GetWorkspace, undefined, { enabled: isAuthenticated })
|
|
29
|
+
const autoEnableStarted = useRef(false)
|
|
30
|
+
|
|
31
|
+
// Once GetWorkspace succeeds, trust only the live workspace — cookie/bootstrap
|
|
32
|
+
// can be stale and would skip the sales enable CTA.
|
|
33
|
+
const services = useMemo(() => {
|
|
34
|
+
if (workspaceStatus === AsyncStatus.Success) {
|
|
35
|
+
return workspaceFromSdk?.services ?? {}
|
|
36
|
+
}
|
|
37
|
+
return workspaceFromSdk?.services
|
|
38
|
+
?? app?.workspaceServices
|
|
39
|
+
?? shellWorkspace?.services
|
|
40
|
+
}, [
|
|
41
|
+
workspaceStatus,
|
|
42
|
+
workspaceFromSdk?.services,
|
|
43
|
+
app?.workspaceServices,
|
|
44
|
+
shellWorkspace?.services,
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
const entitled = isServiceEntitled(services, TIMESHEETS_SERVICE)
|
|
48
|
+
const enableRequested = router.searchParams.enable === TIMESHEETS_SERVICE
|
|
49
|
+
|
|
50
|
+
const entitlementsPending = isAuthenticated
|
|
51
|
+
&& workspaceStatus !== AsyncStatus.Success
|
|
52
|
+
&& workspaceStatus !== AsyncStatus.Error
|
|
53
|
+
&& app?.workspaceServices == null
|
|
54
|
+
|
|
55
|
+
const enableService = useCallback(() => {
|
|
56
|
+
return invoke(EnableService, { service: TIMESHEETS_SERVICE })
|
|
57
|
+
.then(() => invalidate(cacheKey(GetWorkspace)))
|
|
58
|
+
}, [invoke, invalidate])
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!isAuthenticated || !enableRequested || autoEnableStarted.current) return
|
|
62
|
+
if (entitlementsPending) return
|
|
63
|
+
if (entitled) {
|
|
64
|
+
const home = router.getHref('timesheets/home') || '/timesheets'
|
|
65
|
+
window.history.replaceState({}, '', home)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
autoEnableStarted.current = true
|
|
70
|
+
const home = router.getHref('timesheets/home') || '/timesheets'
|
|
71
|
+
window.history.replaceState({}, '', home)
|
|
72
|
+
|
|
73
|
+
enableService().catch(() => {
|
|
74
|
+
// Sales page surfaces a manual enable CTA + error if this fails.
|
|
75
|
+
})
|
|
76
|
+
}, [
|
|
77
|
+
isAuthenticated,
|
|
78
|
+
enableRequested,
|
|
79
|
+
entitlementsPending,
|
|
80
|
+
entitled,
|
|
81
|
+
enableService,
|
|
82
|
+
router,
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
if (entitlementsPending) {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (isAuthenticated && entitled) {
|
|
90
|
+
return <TimesheetsProductHome />
|
|
91
|
+
}
|
|
20
92
|
|
|
21
93
|
return (
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
far={(
|
|
27
|
-
<Button variant="cta" disabled prefix="add">
|
|
28
|
-
{t('design-system.add')}
|
|
29
|
-
</Button>
|
|
30
|
-
)}
|
|
31
|
-
>
|
|
32
|
-
<View gap="m">
|
|
33
|
-
<Text variant="heading-secondary" as="h2" text="design-system.overview" />
|
|
34
|
-
<DataLoader content={<Timesheet />} />
|
|
35
|
-
</View>
|
|
36
|
-
</Page>
|
|
94
|
+
<TimesheetsSalesPage
|
|
95
|
+
isAuthenticated={isAuthenticated}
|
|
96
|
+
onEnable={enableService}
|
|
97
|
+
/>
|
|
37
98
|
)
|
|
38
99
|
}
|
|
39
|
-
|
|
40
|
-
export default TimesheetsPage
|