@open-mercato/core 0.4.2-canary-f6b7824b47 → 0.4.2-canary-70c8402224
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/dist/generated/entities/notification/index.js +57 -0
- package/dist/generated/entities/notification/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +5 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js +3 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
- package/dist/modules/auth/api/admin/nav.js +4 -3
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +155 -0
- package/dist/modules/auth/api/profile/route.js.map +7 -0
- package/dist/modules/auth/api/reset/confirm.js +25 -2
- package/dist/modules/auth/api/reset/confirm.js.map +2 -2
- package/dist/modules/auth/api/reset.js +23 -0
- package/dist/modules/auth/api/reset.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +14 -9
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/backend/auth/profile/page.js +99 -0
- package/dist/modules/auth/backend/auth/profile/page.js.map +7 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js +12 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js.map +7 -0
- package/dist/modules/auth/commands/users.js +55 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +1 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/notifications.js +112 -0
- package/dist/modules/auth/notifications.js.map +7 -0
- package/dist/modules/auth/services/authService.js +3 -3
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/business_rules/notifications.js +28 -0
- package/dist/modules/business_rules/notifications.js.map +7 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +37 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +7 -0
- package/dist/modules/catalog/notifications.js +28 -0
- package/dist/modules/catalog/notifications.js.map +7 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js +38 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js.map +7 -0
- package/dist/modules/configs/cli.js +6 -0
- package/dist/modules/configs/cli.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +31 -0
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/notifications.js +48 -0
- package/dist/modules/customers/notifications.js.map +7 -0
- package/dist/modules/notifications/acl.js +11 -0
- package/dist/modules/notifications/acl.js.map +7 -0
- package/dist/modules/notifications/api/[id]/action/route.js +69 -0
- package/dist/modules/notifications/api/[id]/action/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js +15 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/read/route.js +15 -0
- package/dist/modules/notifications/api/[id]/read/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/restore/route.js +53 -0
- package/dist/modules/notifications/api/[id]/restore/route.js.map +7 -0
- package/dist/modules/notifications/api/batch/route.js +17 -0
- package/dist/modules/notifications/api/batch/route.js.map +7 -0
- package/dist/modules/notifications/api/feature/route.js +17 -0
- package/dist/modules/notifications/api/feature/route.js.map +7 -0
- package/dist/modules/notifications/api/mark-all-read/route.js +35 -0
- package/dist/modules/notifications/api/mark-all-read/route.js.map +7 -0
- package/dist/modules/notifications/api/openapi.js +57 -0
- package/dist/modules/notifications/api/openapi.js.map +7 -0
- package/dist/modules/notifications/api/role/route.js +17 -0
- package/dist/modules/notifications/api/role/route.js.map +7 -0
- package/dist/modules/notifications/api/route.js +85 -0
- package/dist/modules/notifications/api/route.js.map +7 -0
- package/dist/modules/notifications/api/settings/route.js +96 -0
- package/dist/modules/notifications/api/settings/route.js.map +7 -0
- package/dist/modules/notifications/api/unread-count/route.js +38 -0
- package/dist/modules/notifications/api/unread-count/route.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.js +10 -0
- package/dist/modules/notifications/backend/config/notifications/page.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js +24 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +7 -0
- package/dist/modules/notifications/cli.js +16 -0
- package/dist/modules/notifications/cli.js.map +7 -0
- package/dist/modules/notifications/data/entities.js +112 -0
- package/dist/modules/notifications/data/entities.js.map +7 -0
- package/dist/modules/notifications/data/validators.js +94 -0
- package/dist/modules/notifications/data/validators.js.map +7 -0
- package/dist/modules/notifications/di.js +13 -0
- package/dist/modules/notifications/di.js.map +7 -0
- package/dist/modules/notifications/emails/NotificationEmail.js +58 -0
- package/dist/modules/notifications/emails/NotificationEmail.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +44 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +219 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +7 -0
- package/dist/modules/notifications/index.js +14 -0
- package/dist/modules/notifications/index.js.map +7 -0
- package/dist/modules/notifications/lib/deliveryConfig.js +105 -0
- package/dist/modules/notifications/lib/deliveryConfig.js.map +7 -0
- package/dist/modules/notifications/lib/events.js +12 -0
- package/dist/modules/notifications/lib/events.js.map +7 -0
- package/dist/modules/notifications/lib/notificationBuilder.js +66 -0
- package/dist/modules/notifications/lib/notificationBuilder.js.map +7 -0
- package/dist/modules/notifications/lib/notificationFactory.js +54 -0
- package/dist/modules/notifications/lib/notificationFactory.js.map +7 -0
- package/dist/modules/notifications/lib/notificationMapper.js +34 -0
- package/dist/modules/notifications/lib/notificationMapper.js.map +7 -0
- package/dist/modules/notifications/lib/notificationRecipients.js +35 -0
- package/dist/modules/notifications/lib/notificationRecipients.js.map +7 -0
- package/dist/modules/notifications/lib/notificationService.js +279 -0
- package/dist/modules/notifications/lib/notificationService.js.map +7 -0
- package/dist/modules/notifications/lib/routeHelpers.js +101 -0
- package/dist/modules/notifications/lib/routeHelpers.js.map +7 -0
- package/dist/modules/notifications/lib/safeHref.js +24 -0
- package/dist/modules/notifications/lib/safeHref.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js +70 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js +37 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js.map +7 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js +139 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js.map +7 -0
- package/dist/modules/notifications/workers/create-notification.worker.js +70 -0
- package/dist/modules/notifications/workers/create-notification.worker.js.map +7 -0
- package/dist/modules/sales/commands/documents.js +53 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +26 -0
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/notifications.client.js +51 -0
- package/dist/modules/sales/notifications.client.js.map +7 -0
- package/dist/modules/sales/notifications.js +88 -0
- package/dist/modules/sales/notifications.js.map +7 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js +38 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/index.js +7 -0
- package/dist/modules/sales/widgets/notifications/index.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +60 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +7 -0
- package/dist/modules/staff/commands/leave-requests.js +79 -0
- package/dist/modules/staff/commands/leave-requests.js.map +2 -2
- package/dist/modules/staff/notifications.js +75 -0
- package/dist/modules/staff/notifications.js.map +7 -0
- package/dist/modules/workflows/notifications.js +28 -0
- package/dist/modules/workflows/notifications.js.map +7 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js +38 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +7 -0
- package/generated/entities/notification/index.ts +27 -0
- package/generated/entities.ids.generated.ts +5 -1
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/api_docs/frontend/docs/api/page.tsx +3 -2
- package/src/modules/auth/api/admin/nav.ts +10 -6
- package/src/modules/auth/api/profile/route.ts +160 -0
- package/src/modules/auth/api/reset/confirm.ts +25 -2
- package/src/modules/auth/api/reset.ts +23 -0
- package/src/modules/auth/api/sidebar/preferences/route.ts +21 -12
- package/src/modules/auth/backend/auth/profile/page.meta.ts +8 -0
- package/src/modules/auth/backend/auth/profile/page.tsx +127 -0
- package/src/modules/auth/commands/users.ts +68 -0
- package/src/modules/auth/i18n/de.json +29 -1
- package/src/modules/auth/i18n/en.json +29 -1
- package/src/modules/auth/i18n/es.json +29 -1
- package/src/modules/auth/i18n/pl.json +29 -1
- package/src/modules/auth/lib/setup-app.ts +1 -0
- package/src/modules/auth/notifications.ts +109 -0
- package/src/modules/auth/services/authService.ts +4 -4
- package/src/modules/business_rules/i18n/en.json +3 -1
- package/src/modules/business_rules/notifications.ts +25 -0
- package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +50 -0
- package/src/modules/catalog/i18n/en.json +3 -1
- package/src/modules/catalog/notifications.ts +25 -0
- package/src/modules/catalog/subscribers/low-stock-notification.ts +52 -0
- package/src/modules/configs/cli.ts +6 -0
- package/src/modules/customers/commands/deals.ts +39 -0
- package/src/modules/customers/i18n/en.json +5 -1
- package/src/modules/customers/notifications.ts +44 -0
- package/src/modules/notifications/acl.ts +7 -0
- package/src/modules/notifications/api/[id]/action/route.ts +70 -0
- package/src/modules/notifications/api/[id]/dismiss/route.ts +12 -0
- package/src/modules/notifications/api/[id]/read/route.ts +12 -0
- package/src/modules/notifications/api/[id]/restore/route.ts +53 -0
- package/src/modules/notifications/api/batch/route.ts +14 -0
- package/src/modules/notifications/api/feature/route.ts +14 -0
- package/src/modules/notifications/api/mark-all-read/route.ts +34 -0
- package/src/modules/notifications/api/openapi.ts +52 -0
- package/src/modules/notifications/api/role/route.ts +14 -0
- package/src/modules/notifications/api/route.ts +92 -0
- package/src/modules/notifications/api/settings/route.ts +98 -0
- package/src/modules/notifications/api/unread-count/route.ts +38 -0
- package/src/modules/notifications/backend/config/notifications/page.meta.ts +22 -0
- package/src/modules/notifications/backend/config/notifications/page.tsx +12 -0
- package/src/modules/notifications/cli.ts +18 -0
- package/src/modules/notifications/data/entities.ts +99 -0
- package/src/modules/notifications/data/validators.ts +110 -0
- package/src/modules/notifications/di.ts +11 -0
- package/src/modules/notifications/emails/NotificationEmail.tsx +98 -0
- package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +42 -0
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +231 -0
- package/src/modules/notifications/i18n/de.json +50 -0
- package/src/modules/notifications/i18n/en.json +50 -0
- package/src/modules/notifications/i18n/es.json +50 -0
- package/src/modules/notifications/i18n/pl.json +50 -0
- package/src/modules/notifications/index.ts +12 -0
- package/src/modules/notifications/lib/deliveryConfig.ts +145 -0
- package/src/modules/notifications/lib/events.ts +48 -0
- package/src/modules/notifications/lib/notificationBuilder.ts +121 -0
- package/src/modules/notifications/lib/notificationFactory.ts +76 -0
- package/src/modules/notifications/lib/notificationMapper.ts +33 -0
- package/src/modules/notifications/lib/notificationRecipients.ts +83 -0
- package/src/modules/notifications/lib/notificationService.ts +414 -0
- package/src/modules/notifications/lib/routeHelpers.ts +151 -0
- package/src/modules/notifications/lib/safeHref.ts +29 -0
- package/src/modules/notifications/migrations/.snapshot-open-mercato.json +300 -0
- package/src/modules/notifications/migrations/Migration20260123000001.ts +73 -0
- package/src/modules/notifications/migrations/Migration20260126150000.ts +39 -0
- package/src/modules/notifications/subscribers/deliver-notification.ts +175 -0
- package/src/modules/notifications/workers/create-notification.worker.ts +122 -0
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/payments.ts +33 -0
- package/src/modules/sales/i18n/de.json +20 -0
- package/src/modules/sales/i18n/en.json +25 -1
- package/src/modules/sales/i18n/es.json +20 -0
- package/src/modules/sales/i18n/pl.json +20 -0
- package/src/modules/sales/notifications.client.ts +65 -0
- package/src/modules/sales/notifications.ts +82 -0
- package/src/modules/sales/subscribers/quote-expiring-notification.ts +53 -0
- package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/index.ts +2 -0
- package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +81 -0
- package/src/modules/staff/commands/leave-requests.ts +94 -0
- package/src/modules/staff/i18n/de.json +4 -0
- package/src/modules/staff/i18n/en.json +9 -1
- package/src/modules/staff/i18n/es.json +4 -0
- package/src/modules/staff/i18n/pl.json +4 -0
- package/src/modules/staff/notifications.ts +71 -0
- package/src/modules/workflows/i18n/en.json +3 -1
- package/src/modules/workflows/notifications.ts +25 -0
- package/src/modules/workflows/subscribers/task-assigned-notification.ts +53 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { ShoppingCart, ExternalLink, DollarSign, User, Calendar } from 'lucide-react'
|
|
5
|
+
import { useRouter } from 'next/navigation'
|
|
6
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
7
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
8
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
9
|
+
import type { NotificationRendererProps } from '@open-mercato/shared/modules/notifications/types'
|
|
10
|
+
import { formatMoney } from '../../components/documents/lineItemUtils'
|
|
11
|
+
import { useSalesDocumentTotals } from './useSalesDocumentTotals'
|
|
12
|
+
|
|
13
|
+
function formatTimeAgo(dateString: string, t: (key: string, fallback?: string) => string): string {
|
|
14
|
+
const date = new Date(dateString)
|
|
15
|
+
const now = new Date()
|
|
16
|
+
const diffMs = now.getTime() - date.getTime()
|
|
17
|
+
const diffMins = Math.floor(diffMs / 60000)
|
|
18
|
+
const diffHours = Math.floor(diffMs / 3600000)
|
|
19
|
+
const diffDays = Math.floor(diffMs / 86400000)
|
|
20
|
+
|
|
21
|
+
if (diffMins < 1) return t('common.time.justNow', 'just now')
|
|
22
|
+
if (diffMins < 60) return t('common.time.minutesAgo', '{count}m ago').replace('{count}', String(diffMins))
|
|
23
|
+
if (diffHours < 24) return t('common.time.hoursAgo', '{count}h ago').replace('{count}', String(diffHours))
|
|
24
|
+
if (diffDays < 7) return t('common.time.daysAgo', '{count}d ago').replace('{count}', String(diffDays))
|
|
25
|
+
return date.toLocaleDateString()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeTotal(value?: string | null): string | null {
|
|
29
|
+
if (!value) return null
|
|
30
|
+
let trimmed = value.trim()
|
|
31
|
+
if (trimmed.startsWith('(') && trimmed.endsWith(')')) {
|
|
32
|
+
trimmed = trimmed.slice(1, -1).trim()
|
|
33
|
+
}
|
|
34
|
+
return trimmed.length ? trimmed : null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function SalesOrderCreatedRenderer({
|
|
38
|
+
notification,
|
|
39
|
+
onAction,
|
|
40
|
+
onDismiss,
|
|
41
|
+
actions = [],
|
|
42
|
+
}: NotificationRendererProps) {
|
|
43
|
+
const t = useT()
|
|
44
|
+
const router = useRouter()
|
|
45
|
+
const [executing, setExecuting] = React.useState(false)
|
|
46
|
+
const isUnread = notification.status === 'unread'
|
|
47
|
+
const orderNumber = notification.bodyVariables?.orderNumber ?? notification.titleVariables?.orderNumber
|
|
48
|
+
const fallbackTotal =
|
|
49
|
+
normalizeTotal(notification.bodyVariables?.totalAmount ?? null) ??
|
|
50
|
+
normalizeTotal(notification.bodyVariables?.total ?? null)
|
|
51
|
+
const { totals } = useSalesDocumentTotals('order', notification.sourceEntityId)
|
|
52
|
+
|
|
53
|
+
const currentTotal =
|
|
54
|
+
totals && typeof totals.grandTotalGrossAmount === 'number'
|
|
55
|
+
? formatMoney(totals.grandTotalGrossAmount, totals.currencyCode)
|
|
56
|
+
: fallbackTotal
|
|
57
|
+
|
|
58
|
+
const viewAction = actions.find((action) => action.id === 'view') ?? actions[0] ?? null
|
|
59
|
+
|
|
60
|
+
const handleView = async () => {
|
|
61
|
+
if (!viewAction) {
|
|
62
|
+
if (notification.linkHref) router.push(notification.linkHref)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
setExecuting(true)
|
|
66
|
+
try {
|
|
67
|
+
await onAction(viewAction.id)
|
|
68
|
+
} finally {
|
|
69
|
+
setExecuting(false)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
className={cn(
|
|
76
|
+
'group relative px-4 py-3 hover:bg-muted/50 cursor-pointer transition-colors border-l-4 border-l-blue-500',
|
|
77
|
+
isUnread && 'bg-blue-50/50 dark:bg-blue-950/20'
|
|
78
|
+
)}
|
|
79
|
+
onClick={handleView}
|
|
80
|
+
>
|
|
81
|
+
{isUnread && (
|
|
82
|
+
<div className="absolute left-1.5 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full bg-primary" />
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
<div className="flex gap-3">
|
|
86
|
+
<div className="flex-shrink-0 mt-0.5">
|
|
87
|
+
<div className="h-10 w-10 rounded-lg bg-blue-100 dark:bg-blue-900/40 flex items-center justify-center">
|
|
88
|
+
<ShoppingCart className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="flex-1 min-w-0">
|
|
93
|
+
<div className="flex items-start justify-between gap-2">
|
|
94
|
+
<div>
|
|
95
|
+
<h4 className={cn('text-sm font-medium', isUnread && 'font-semibold')}>
|
|
96
|
+
{notification.title}
|
|
97
|
+
</h4>
|
|
98
|
+
{orderNumber && (
|
|
99
|
+
<div className="flex items-center gap-1 mt-0.5">
|
|
100
|
+
<span className="text-xs font-mono text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
|
|
101
|
+
#{orderNumber}
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
<span className="flex-shrink-0 text-xs text-muted-foreground flex items-center gap-1">
|
|
107
|
+
<Calendar className="h-3 w-3" />
|
|
108
|
+
{formatTimeAgo(notification.createdAt, t)}
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div className="mt-2 flex items-center gap-4 text-xs text-muted-foreground">
|
|
113
|
+
{currentTotal && (
|
|
114
|
+
<div className="flex items-center gap-1">
|
|
115
|
+
<DollarSign className="h-3 w-3" />
|
|
116
|
+
<span className="font-medium text-foreground">{currentTotal}</span>
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
<div className="flex items-center gap-1">
|
|
120
|
+
<User className="h-3 w-3" />
|
|
121
|
+
<span>{t('sales.notifications.renderer.assignedToYou', 'Assigned to you')}</span>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div className="mt-3 flex gap-2">
|
|
126
|
+
<Button
|
|
127
|
+
variant="default"
|
|
128
|
+
size="sm"
|
|
129
|
+
onClick={(e) => {
|
|
130
|
+
e.stopPropagation()
|
|
131
|
+
handleView()
|
|
132
|
+
}}
|
|
133
|
+
disabled={executing || (!viewAction && !notification.linkHref)}
|
|
134
|
+
className="gap-1"
|
|
135
|
+
>
|
|
136
|
+
<ExternalLink className="h-3 w-3" />
|
|
137
|
+
{t('sales.notifications.renderer.viewOrder', 'View Order')}
|
|
138
|
+
</Button>
|
|
139
|
+
<Button
|
|
140
|
+
variant="ghost"
|
|
141
|
+
size="sm"
|
|
142
|
+
onClick={(e) => {
|
|
143
|
+
e.stopPropagation()
|
|
144
|
+
onDismiss()
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{t('notifications.actions.dismiss', 'Dismiss')}
|
|
148
|
+
</Button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default SalesOrderCreatedRenderer
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { FileText, ExternalLink, DollarSign, User, Calendar } from 'lucide-react'
|
|
5
|
+
import { useRouter } from 'next/navigation'
|
|
6
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
7
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
8
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
9
|
+
import type { NotificationRendererProps } from '@open-mercato/shared/modules/notifications/types'
|
|
10
|
+
import { formatMoney } from '../../components/documents/lineItemUtils'
|
|
11
|
+
import { useSalesDocumentTotals } from './useSalesDocumentTotals'
|
|
12
|
+
|
|
13
|
+
function formatTimeAgo(dateString: string, t: (key: string, fallback?: string) => string): string {
|
|
14
|
+
const date = new Date(dateString)
|
|
15
|
+
const now = new Date()
|
|
16
|
+
const diffMs = now.getTime() - date.getTime()
|
|
17
|
+
const diffMins = Math.floor(diffMs / 60000)
|
|
18
|
+
const diffHours = Math.floor(diffMs / 3600000)
|
|
19
|
+
const diffDays = Math.floor(diffMs / 86400000)
|
|
20
|
+
|
|
21
|
+
if (diffMins < 1) return t('common.time.justNow', 'just now')
|
|
22
|
+
if (diffMins < 60) return t('common.time.minutesAgo', '{count}m ago').replace('{count}', String(diffMins))
|
|
23
|
+
if (diffHours < 24) return t('common.time.hoursAgo', '{count}h ago').replace('{count}', String(diffHours))
|
|
24
|
+
if (diffDays < 7) return t('common.time.daysAgo', '{count}d ago').replace('{count}', String(diffDays))
|
|
25
|
+
return date.toLocaleDateString()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeTotal(value?: string | null): string | null {
|
|
29
|
+
if (!value) return null
|
|
30
|
+
let trimmed = value.trim()
|
|
31
|
+
if (trimmed.startsWith('(') && trimmed.endsWith(')')) {
|
|
32
|
+
trimmed = trimmed.slice(1, -1).trim()
|
|
33
|
+
}
|
|
34
|
+
return trimmed.length ? trimmed : null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function SalesQuoteCreatedRenderer({
|
|
38
|
+
notification,
|
|
39
|
+
onAction,
|
|
40
|
+
onDismiss,
|
|
41
|
+
actions = [],
|
|
42
|
+
}: NotificationRendererProps) {
|
|
43
|
+
const t = useT()
|
|
44
|
+
const router = useRouter()
|
|
45
|
+
const [executing, setExecuting] = React.useState(false)
|
|
46
|
+
const isUnread = notification.status === 'unread'
|
|
47
|
+
const quoteNumber = notification.bodyVariables?.quoteNumber ?? notification.titleVariables?.quoteNumber
|
|
48
|
+
const fallbackTotal =
|
|
49
|
+
normalizeTotal(notification.bodyVariables?.totalAmount ?? null) ??
|
|
50
|
+
normalizeTotal(notification.bodyVariables?.total ?? null)
|
|
51
|
+
const { totals } = useSalesDocumentTotals('quote', notification.sourceEntityId)
|
|
52
|
+
|
|
53
|
+
const currentTotal =
|
|
54
|
+
totals && typeof totals.grandTotalGrossAmount === 'number'
|
|
55
|
+
? formatMoney(totals.grandTotalGrossAmount, totals.currencyCode)
|
|
56
|
+
: fallbackTotal
|
|
57
|
+
|
|
58
|
+
const viewAction = actions.find((action) => action.id === 'view') ?? actions[0] ?? null
|
|
59
|
+
|
|
60
|
+
const handleView = async () => {
|
|
61
|
+
if (!viewAction) {
|
|
62
|
+
if (notification.linkHref) router.push(notification.linkHref)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
setExecuting(true)
|
|
66
|
+
try {
|
|
67
|
+
await onAction(viewAction.id)
|
|
68
|
+
} finally {
|
|
69
|
+
setExecuting(false)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
className={cn(
|
|
76
|
+
'group relative px-4 py-3 hover:bg-muted/50 cursor-pointer transition-colors border-l-4 border-l-amber-500',
|
|
77
|
+
isUnread && 'bg-amber-50/50 dark:bg-amber-950/20'
|
|
78
|
+
)}
|
|
79
|
+
onClick={handleView}
|
|
80
|
+
>
|
|
81
|
+
{isUnread && (
|
|
82
|
+
<div className="absolute left-1.5 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full bg-primary" />
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
<div className="flex gap-3">
|
|
86
|
+
<div className="flex-shrink-0 mt-0.5">
|
|
87
|
+
<div className="h-10 w-10 rounded-lg bg-amber-100 dark:bg-amber-900/40 flex items-center justify-center">
|
|
88
|
+
<FileText className="h-5 w-5 text-amber-600 dark:text-amber-400" />
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="flex-1 min-w-0">
|
|
93
|
+
<div className="flex items-start justify-between gap-2">
|
|
94
|
+
<div>
|
|
95
|
+
<h4 className={cn('text-sm font-medium', isUnread && 'font-semibold')}>
|
|
96
|
+
{notification.title}
|
|
97
|
+
</h4>
|
|
98
|
+
{quoteNumber && (
|
|
99
|
+
<div className="flex items-center gap-1 mt-0.5">
|
|
100
|
+
<span className="text-xs font-mono text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
|
|
101
|
+
#{quoteNumber}
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
<span className="flex-shrink-0 text-xs text-muted-foreground flex items-center gap-1">
|
|
107
|
+
<Calendar className="h-3 w-3" />
|
|
108
|
+
{formatTimeAgo(notification.createdAt, t)}
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div className="mt-2 flex items-center gap-4 text-xs text-muted-foreground">
|
|
113
|
+
{currentTotal && (
|
|
114
|
+
<div className="flex items-center gap-1">
|
|
115
|
+
<DollarSign className="h-3 w-3" />
|
|
116
|
+
<span className="font-medium text-foreground">{currentTotal}</span>
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
<div className="flex items-center gap-1">
|
|
120
|
+
<User className="h-3 w-3" />
|
|
121
|
+
<span>{t('sales.notifications.renderer.pendingReview', 'Pending review')}</span>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div className="mt-3 flex gap-2">
|
|
126
|
+
<Button
|
|
127
|
+
variant="default"
|
|
128
|
+
size="sm"
|
|
129
|
+
onClick={(e) => {
|
|
130
|
+
e.stopPropagation()
|
|
131
|
+
handleView()
|
|
132
|
+
}}
|
|
133
|
+
disabled={executing || (!viewAction && !notification.linkHref)}
|
|
134
|
+
className="gap-1"
|
|
135
|
+
>
|
|
136
|
+
<ExternalLink className="h-3 w-3" />
|
|
137
|
+
{t('sales.notifications.renderer.viewQuote', 'View Quote')}
|
|
138
|
+
</Button>
|
|
139
|
+
<Button
|
|
140
|
+
variant="ghost"
|
|
141
|
+
size="sm"
|
|
142
|
+
onClick={(e) => {
|
|
143
|
+
e.stopPropagation()
|
|
144
|
+
onDismiss()
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{t('notifications.actions.dismiss', 'Dismiss')}
|
|
148
|
+
</Button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default SalesQuoteCreatedRenderer
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
5
|
+
|
|
6
|
+
type DocumentKind = 'order' | 'quote'
|
|
7
|
+
|
|
8
|
+
type DocumentTotals = {
|
|
9
|
+
grandTotalGrossAmount: number | null
|
|
10
|
+
currencyCode: string | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type DocumentListResponse = {
|
|
14
|
+
items?: Array<{
|
|
15
|
+
grandTotalGrossAmount?: number | string | null
|
|
16
|
+
currencyCode?: string | null
|
|
17
|
+
}>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const REFRESH_INTERVAL_MS = 30000
|
|
21
|
+
|
|
22
|
+
function buildDocumentTotalsUrl(kind: DocumentKind, documentId: string) {
|
|
23
|
+
const params = new URLSearchParams({ id: documentId, page: '1', pageSize: '1' })
|
|
24
|
+
const collection = kind === 'order' ? 'orders' : 'quotes'
|
|
25
|
+
return `/api/sales/${collection}?${params.toString()}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractTotals(payload: DocumentListResponse | null): DocumentTotals | null {
|
|
29
|
+
const item = payload?.items?.[0]
|
|
30
|
+
if (!item) return null
|
|
31
|
+
const rawAmount = item.grandTotalGrossAmount
|
|
32
|
+
let grandTotalGrossAmount: number | null = null
|
|
33
|
+
if (typeof rawAmount === 'number') {
|
|
34
|
+
grandTotalGrossAmount = Number.isNaN(rawAmount) ? null : rawAmount
|
|
35
|
+
} else if (typeof rawAmount === 'string' && rawAmount.trim().length) {
|
|
36
|
+
const parsed = Number(rawAmount)
|
|
37
|
+
grandTotalGrossAmount = Number.isNaN(parsed) ? null : parsed
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
grandTotalGrossAmount,
|
|
41
|
+
currencyCode: typeof item.currencyCode === 'string' ? item.currencyCode : null,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useSalesDocumentTotals(kind: DocumentKind, documentId?: string | null) {
|
|
46
|
+
const [totals, setTotals] = React.useState<DocumentTotals | null>(null)
|
|
47
|
+
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
if (!documentId) {
|
|
50
|
+
setTotals(null)
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let active = true
|
|
55
|
+
|
|
56
|
+
const loadTotals = async () => {
|
|
57
|
+
try {
|
|
58
|
+
const call = await apiCall<DocumentListResponse>(buildDocumentTotalsUrl(kind, documentId))
|
|
59
|
+
if (!active) return
|
|
60
|
+
if (call.ok) {
|
|
61
|
+
const nextTotals = extractTotals(call.result ?? null)
|
|
62
|
+
setTotals(nextTotals)
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
if (active) {
|
|
66
|
+
setTotals(null)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
loadTotals()
|
|
72
|
+
const interval = setInterval(loadTotals, REFRESH_INTERVAL_MS)
|
|
73
|
+
|
|
74
|
+
return () => {
|
|
75
|
+
active = false
|
|
76
|
+
clearInterval(interval)
|
|
77
|
+
}
|
|
78
|
+
}, [kind, documentId])
|
|
79
|
+
|
|
80
|
+
return { totals }
|
|
81
|
+
}
|
|
@@ -21,6 +21,9 @@ import {
|
|
|
21
21
|
} from '../data/validators'
|
|
22
22
|
import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload, requireTeamMember } from './shared'
|
|
23
23
|
import { E } from '#generated/entities.ids.generated'
|
|
24
|
+
import { resolveNotificationService } from '../../notifications/lib/notificationService'
|
|
25
|
+
import { buildFeatureNotificationFromType, buildNotificationFromType } from '../../notifications/lib/notificationBuilder'
|
|
26
|
+
import { notificationTypes } from '../notifications'
|
|
24
27
|
|
|
25
28
|
const leaveRequestCrudIndexer: CrudIndexerConfig<StaffLeaveRequest> = {
|
|
26
29
|
entityType: E.staff.staff_leave_request,
|
|
@@ -258,6 +261,36 @@ const createLeaveRequestCommand: CommandHandler<StaffLeaveRequestCreateInput, {
|
|
|
258
261
|
indexer: leaveRequestCrudIndexer,
|
|
259
262
|
})
|
|
260
263
|
|
|
264
|
+
// Create notification for users who can approve/reject leave requests
|
|
265
|
+
try {
|
|
266
|
+
const notificationService = resolveNotificationService(ctx.container)
|
|
267
|
+
const typeDef = notificationTypes.find((type) => type.type === 'staff.leave_request.pending')
|
|
268
|
+
if (typeDef) {
|
|
269
|
+
const memberName = member.displayName || 'Team member'
|
|
270
|
+
const startDateStr = request.startDate.toLocaleDateString()
|
|
271
|
+
const endDateStr = request.endDate.toLocaleDateString()
|
|
272
|
+
|
|
273
|
+
const notificationInput = buildFeatureNotificationFromType(typeDef, {
|
|
274
|
+
requiredFeature: 'staff.leave_requests.manage',
|
|
275
|
+
bodyVariables: {
|
|
276
|
+
memberName,
|
|
277
|
+
startDate: startDateStr,
|
|
278
|
+
endDate: endDateStr,
|
|
279
|
+
},
|
|
280
|
+
sourceEntityType: 'staff:leave_request',
|
|
281
|
+
sourceEntityId: request.id,
|
|
282
|
+
linkHref: `/backend/staff/leave-requests/${request.id}`,
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
await notificationService.createForFeature(notificationInput, {
|
|
286
|
+
tenantId: request.tenantId,
|
|
287
|
+
organizationId: request.organizationId,
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
// Notification creation is non-critical, don't fail the command
|
|
292
|
+
}
|
|
293
|
+
|
|
261
294
|
return { requestId: request.id }
|
|
262
295
|
},
|
|
263
296
|
captureAfter: async (_input, result, ctx) => {
|
|
@@ -575,6 +608,36 @@ const acceptLeaveRequestCommand: CommandHandler<StaffLeaveRequestDecisionInput,
|
|
|
575
608
|
ruleIds: createdRuleIds,
|
|
576
609
|
})
|
|
577
610
|
|
|
611
|
+
// Send notification to the requester
|
|
612
|
+
if (request.submittedByUserId) {
|
|
613
|
+
try {
|
|
614
|
+
const notificationService = resolveNotificationService(ctx.container)
|
|
615
|
+
const typeDef = notificationTypes.find((type) => type.type === 'staff.leave_request.approved')
|
|
616
|
+
if (typeDef) {
|
|
617
|
+
const startDateStr = request.startDate.toLocaleDateString()
|
|
618
|
+
const endDateStr = request.endDate.toLocaleDateString()
|
|
619
|
+
|
|
620
|
+
const notificationInput = buildNotificationFromType(typeDef, {
|
|
621
|
+
recipientUserId: request.submittedByUserId,
|
|
622
|
+
bodyVariables: {
|
|
623
|
+
startDate: startDateStr,
|
|
624
|
+
endDate: endDateStr,
|
|
625
|
+
},
|
|
626
|
+
sourceEntityType: 'staff:leave_request',
|
|
627
|
+
sourceEntityId: request.id,
|
|
628
|
+
linkHref: `/backend/staff/leave-requests/${request.id}`,
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
await notificationService.create(notificationInput, {
|
|
632
|
+
tenantId: request.tenantId,
|
|
633
|
+
organizationId: request.organizationId,
|
|
634
|
+
})
|
|
635
|
+
}
|
|
636
|
+
} catch {
|
|
637
|
+
// Notification creation is non-critical, don't fail the command
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
578
641
|
return { requestId: request.id, ruleIds: createdRuleIds }
|
|
579
642
|
},
|
|
580
643
|
buildLog: async ({ result, ctx, snapshots }) => {
|
|
@@ -696,6 +759,37 @@ const rejectLeaveRequestCommand: CommandHandler<StaffLeaveRequestDecisionInput,
|
|
|
696
759
|
indexer: leaveRequestCrudIndexer,
|
|
697
760
|
})
|
|
698
761
|
|
|
762
|
+
// Send notification to the requester
|
|
763
|
+
if (request.submittedByUserId) {
|
|
764
|
+
try {
|
|
765
|
+
const notificationService = resolveNotificationService(ctx.container)
|
|
766
|
+
const typeDef = notificationTypes.find((type) => type.type === 'staff.leave_request.rejected')
|
|
767
|
+
if (typeDef) {
|
|
768
|
+
const startDateStr = request.startDate.toLocaleDateString()
|
|
769
|
+
const endDateStr = request.endDate.toLocaleDateString()
|
|
770
|
+
|
|
771
|
+
const notificationInput = buildNotificationFromType(typeDef, {
|
|
772
|
+
recipientUserId: request.submittedByUserId,
|
|
773
|
+
bodyVariables: {
|
|
774
|
+
startDate: startDateStr,
|
|
775
|
+
endDate: endDateStr,
|
|
776
|
+
reason: request.decisionComment ?? '',
|
|
777
|
+
},
|
|
778
|
+
sourceEntityType: 'staff:leave_request',
|
|
779
|
+
sourceEntityId: request.id,
|
|
780
|
+
linkHref: `/backend/staff/leave-requests/${request.id}`,
|
|
781
|
+
})
|
|
782
|
+
|
|
783
|
+
await notificationService.create(notificationInput, {
|
|
784
|
+
tenantId: request.tenantId,
|
|
785
|
+
organizationId: request.organizationId,
|
|
786
|
+
})
|
|
787
|
+
}
|
|
788
|
+
} catch {
|
|
789
|
+
// Notification creation is non-critical, don't fail the command
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
699
793
|
return { requestId: request.id }
|
|
700
794
|
},
|
|
701
795
|
async prepare(rawInput, ctx) {
|
|
@@ -797,6 +797,10 @@
|
|
|
797
797
|
"staff.teams.tabs.details": "Details",
|
|
798
798
|
"staff.teams.tabs.label": "Teamabschnitte",
|
|
799
799
|
"staff.teams.tabs.members": "Teammitglieder",
|
|
800
|
+
"staff.notifications.leaveRequest.pending.title": "Urlaubsantrag ausstehend",
|
|
801
|
+
"staff.notifications.leaveRequest.pending.body": "{memberName} hat Urlaub vom {startDate} bis {endDate} beantragt",
|
|
802
|
+
"staff.notifications.leaveRequest.actions.approve": "Genehmigen",
|
|
803
|
+
"staff.notifications.leaveRequest.actions.reject": "Ablehnen",
|
|
800
804
|
"staff.leaveRequests.page.title": "Urlaubsantr\u00e4ge",
|
|
801
805
|
"staff.leaveRequests.page.description": "Urlaubsantr\u00e4ge des Teams pr\u00fcfen.",
|
|
802
806
|
"staff.leaveRequests.my.title": "Meine Urlaubsantr\u00e4ge",
|
|
@@ -797,6 +797,10 @@
|
|
|
797
797
|
"staff.teams.tabs.details": "Details",
|
|
798
798
|
"staff.teams.tabs.label": "Team sections",
|
|
799
799
|
"staff.teams.tabs.members": "Team members",
|
|
800
|
+
"staff.notifications.leaveRequest.pending.title": "Leave Request Pending",
|
|
801
|
+
"staff.notifications.leaveRequest.pending.body": "{memberName} has requested leave from {startDate} to {endDate}",
|
|
802
|
+
"staff.notifications.leaveRequest.actions.approve": "Approve",
|
|
803
|
+
"staff.notifications.leaveRequest.actions.reject": "Reject",
|
|
800
804
|
"staff.leaveRequests.page.title": "Leave requests",
|
|
801
805
|
"staff.leaveRequests.page.description": "Review leave requests from your team.",
|
|
802
806
|
"staff.leaveRequests.my.title": "My leave requests",
|
|
@@ -874,5 +878,9 @@
|
|
|
874
878
|
"staff.myAvailability.readOnly.body": "Use leave requests to request changes.",
|
|
875
879
|
"staff.teamMembers.self.createTitle": "Create my profile",
|
|
876
880
|
"staff.teamMembers.self.created": "Profile created.",
|
|
877
|
-
"staff.teamMembers.self.exists": "Team member profile already exists."
|
|
881
|
+
"staff.teamMembers.self.exists": "Team member profile already exists.",
|
|
882
|
+
"staff.notifications.leaveRequest.approved.title": "Leave Request Approved",
|
|
883
|
+
"staff.notifications.leaveRequest.approved.body": "Your leave request from {startDate} to {endDate} has been approved",
|
|
884
|
+
"staff.notifications.leaveRequest.rejected.title": "Leave Request Rejected",
|
|
885
|
+
"staff.notifications.leaveRequest.rejected.body": "Your leave request from {startDate} to {endDate} has been rejected{reason, select, other { - {reason}}}"
|
|
878
886
|
}
|
|
@@ -797,6 +797,10 @@
|
|
|
797
797
|
"staff.teams.tabs.details": "Detalles",
|
|
798
798
|
"staff.teams.tabs.label": "Secciones del equipo",
|
|
799
799
|
"staff.teams.tabs.members": "Miembros del equipo",
|
|
800
|
+
"staff.notifications.leaveRequest.pending.title": "Solicitud de ausencia pendiente",
|
|
801
|
+
"staff.notifications.leaveRequest.pending.body": "{memberName} ha solicitado ausencia del {startDate} al {endDate}",
|
|
802
|
+
"staff.notifications.leaveRequest.actions.approve": "Aprobar",
|
|
803
|
+
"staff.notifications.leaveRequest.actions.reject": "Rechazar",
|
|
800
804
|
"staff.leaveRequests.page.title": "Solicitudes de ausencia",
|
|
801
805
|
"staff.leaveRequests.page.description": "Revisa las solicitudes de ausencia del equipo.",
|
|
802
806
|
"staff.leaveRequests.my.title": "Mis solicitudes de ausencia",
|
|
@@ -797,6 +797,10 @@
|
|
|
797
797
|
"staff.teams.tabs.details": "Szczeg\u00f3\u0142y",
|
|
798
798
|
"staff.teams.tabs.label": "Sekcje zespo\u0142u",
|
|
799
799
|
"staff.teams.tabs.members": "Cz\u0142onkowie zespo\u0142u",
|
|
800
|
+
"staff.notifications.leaveRequest.pending.title": "Wniosek urlopowy oczekuje",
|
|
801
|
+
"staff.notifications.leaveRequest.pending.body": "{memberName} zło\u017cył wniosek o urlop od {startDate} do {endDate}",
|
|
802
|
+
"staff.notifications.leaveRequest.actions.approve": "Zatwierd\u017a",
|
|
803
|
+
"staff.notifications.leaveRequest.actions.reject": "Odrzu\u0107",
|
|
800
804
|
"staff.leaveRequests.page.title": "Wnioski urlopowe",
|
|
801
805
|
"staff.leaveRequests.page.description": "Przegl\u0105daj wnioski urlopowe zespo\u0142u.",
|
|
802
806
|
"staff.leaveRequests.my.title": "Moje wnioski urlopowe",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'
|
|
2
|
+
|
|
3
|
+
export const notificationTypes: NotificationTypeDefinition[] = [
|
|
4
|
+
{
|
|
5
|
+
type: 'staff.leave_request.pending',
|
|
6
|
+
module: 'staff',
|
|
7
|
+
titleKey: 'staff.notifications.leaveRequest.pending.title',
|
|
8
|
+
bodyKey: 'staff.notifications.leaveRequest.pending.body',
|
|
9
|
+
icon: 'calendar-off',
|
|
10
|
+
severity: 'warning',
|
|
11
|
+
actions: [
|
|
12
|
+
{
|
|
13
|
+
id: 'approve',
|
|
14
|
+
labelKey: 'staff.notifications.leaveRequest.actions.approve',
|
|
15
|
+
variant: 'default',
|
|
16
|
+
icon: 'check',
|
|
17
|
+
commandId: 'staff.leave-requests.accept',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'reject',
|
|
21
|
+
labelKey: 'staff.notifications.leaveRequest.actions.reject',
|
|
22
|
+
variant: 'destructive',
|
|
23
|
+
icon: 'x',
|
|
24
|
+
commandId: 'staff.leave-requests.reject',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
primaryActionId: 'approve',
|
|
28
|
+
linkHref: '/backend/staff/leave-requests/{sourceEntityId}',
|
|
29
|
+
expiresAfterHours: 168,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'staff.leave_request.approved',
|
|
33
|
+
module: 'staff',
|
|
34
|
+
titleKey: 'staff.notifications.leaveRequest.approved.title',
|
|
35
|
+
bodyKey: 'staff.notifications.leaveRequest.approved.body',
|
|
36
|
+
icon: 'calendar-check',
|
|
37
|
+
severity: 'success',
|
|
38
|
+
actions: [
|
|
39
|
+
{
|
|
40
|
+
id: 'view',
|
|
41
|
+
labelKey: 'common.view',
|
|
42
|
+
variant: 'outline',
|
|
43
|
+
href: '/backend/staff/leave-requests/{sourceEntityId}',
|
|
44
|
+
icon: 'external-link',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
linkHref: '/backend/staff/leave-requests/{sourceEntityId}',
|
|
48
|
+
expiresAfterHours: 168, // 7 days
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'staff.leave_request.rejected',
|
|
52
|
+
module: 'staff',
|
|
53
|
+
titleKey: 'staff.notifications.leaveRequest.rejected.title',
|
|
54
|
+
bodyKey: 'staff.notifications.leaveRequest.rejected.body',
|
|
55
|
+
icon: 'calendar-x',
|
|
56
|
+
severity: 'warning',
|
|
57
|
+
actions: [
|
|
58
|
+
{
|
|
59
|
+
id: 'view',
|
|
60
|
+
labelKey: 'common.view',
|
|
61
|
+
variant: 'outline',
|
|
62
|
+
href: '/backend/staff/leave-requests/{sourceEntityId}',
|
|
63
|
+
icon: 'external-link',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
linkHref: '/backend/staff/leave-requests/{sourceEntityId}',
|
|
67
|
+
expiresAfterHours: 168, // 7 days
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
export default notificationTypes
|
|
@@ -676,5 +676,7 @@
|
|
|
676
676
|
"success": "Sub-workflow completed successfully",
|
|
677
677
|
"failed": "Sub-workflow failed"
|
|
678
678
|
}
|
|
679
|
-
}
|
|
679
|
+
},
|
|
680
|
+
"workflows.notifications.task.assigned.title": "Task Assigned",
|
|
681
|
+
"workflows.notifications.task.assigned.body": "You have been assigned to task \"{taskName}\" in workflow \"{workflowName}\"{dueDate, select, other { (due: {dueDate})}}"
|
|
680
682
|
}
|