@open-mercato/core 0.6.4-develop.4199.1.86677441c2 → 0.6.4-develop.4217.1.c9aa050183
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js +14 -14
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/audit_logs/backend/audit-logs/page.js +3 -3
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +3 -7
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +34 -49
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/ChangelogEntryRow.js +2 -2
- package/dist/modules/customers/components/detail/ChangelogEntryRow.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailHeader.js +10 -1
- package/dist/modules/customers/components/detail/CompanyDetailHeader.js.map +2 -2
- package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js +7 -51
- package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js.map +2 -2
- package/dist/modules/customers/components/detail/DetailTabsLayout.js +1 -1
- package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
- package/dist/modules/customers/components/detail/ManageTagsDialog.js +25 -33
- package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCard.js +3 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailHeader.js +3 -2
- package/dist/modules/customers/components/detail/PersonDetailHeader.js.map +2 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -5
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
- package/dist/modules/customers/components/detail/utils.js +0 -7
- package/dist/modules/customers/components/detail/utils.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +3 -8
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/dictionaries/components/AppearanceSelector.js +3 -4
- package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +17 -17
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +1 -1
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +65 -1
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/planner/lib/deleteAvailabilityRuleSet.js +20 -0
- package/dist/modules/planner/lib/deleteAvailabilityRuleSet.js.map +7 -0
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +14 -37
- package/dist/modules/sales/api/quotes/accept/route.js.map +3 -3
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +1 -1
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +1 -1
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +6 -2
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +3 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +1 -1
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/translations/components/TranslationDrawerAction.js +27 -65
- package/dist/modules/translations/components/TranslationDrawerAction.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +2 -2
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +54 -92
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +14 -14
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +2 -2
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +3 -3
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -8
- package/src/modules/customers/components/detail/ActivityCard.tsx +2 -4
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +28 -55
- package/src/modules/customers/components/detail/ChangelogEntryRow.tsx +6 -4
- package/src/modules/customers/components/detail/CompanyDetailHeader.tsx +7 -3
- package/src/modules/customers/components/detail/DealLinkedEntitiesTab.tsx +11 -49
- package/src/modules/customers/components/detail/DetailTabsLayout.tsx +1 -1
- package/src/modules/customers/components/detail/ManageTagsDialog.tsx +27 -36
- package/src/modules/customers/components/detail/PersonCard.tsx +3 -4
- package/src/modules/customers/components/detail/PersonDetailHeader.tsx +3 -4
- package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -7
- package/src/modules/customers/components/detail/utils.ts +0 -7
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +4 -9
- package/src/modules/dictionaries/components/AppearanceSelector.tsx +3 -4
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +21 -21
- package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +1 -1
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +62 -0
- package/src/modules/planner/lib/deleteAvailabilityRuleSet.ts +35 -0
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +2 -2
- package/src/modules/sales/api/quotes/accept/route.ts +22 -38
- package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +1 -1
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +1 -1
- package/src/modules/sales/commands/documents.ts +16 -2
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +3 -2
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +1 -1
- package/src/modules/staff/i18n/de.json +5 -0
- package/src/modules/staff/i18n/en.json +5 -0
- package/src/modules/staff/i18n/es.json +5 -0
- package/src/modules/staff/i18n/pl.json +5 -0
- package/src/modules/translations/components/TranslationDrawerAction.tsx +31 -66
- package/src/modules/translations/components/TranslationManager.tsx +2 -2
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +53 -84
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { Ellipsis } from 'lucide-react'
|
|
5
5
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
6
|
+
import { ColorPicker } from '@open-mercato/ui/primitives/color-picker'
|
|
6
7
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
7
8
|
import { ICON_LIBRARY, ICON_SUGGESTIONS, type IconOption, renderDictionaryColor, renderDictionaryIcon } from './dictionaryAppearance'
|
|
8
9
|
|
|
@@ -119,12 +120,10 @@ export function AppearanceSelector({
|
|
|
119
120
|
{labels.colorHelp ? <span className="text-xs font-normal text-muted-foreground">{labels.colorHelp}</span> : null}
|
|
120
121
|
</label>
|
|
121
122
|
<div className="flex flex-wrap items-center gap-2">
|
|
122
|
-
<
|
|
123
|
-
type="color"
|
|
123
|
+
<ColorPicker
|
|
124
124
|
value={normalizedColor}
|
|
125
|
-
onChange={(
|
|
125
|
+
onChange={(next) => onColorChange(next)}
|
|
126
126
|
disabled={disabled}
|
|
127
|
-
className="h-10 w-12 cursor-pointer rounded border border-border bg-background"
|
|
128
127
|
aria-label={labels.colorLabel}
|
|
129
128
|
/>
|
|
130
129
|
<Button
|
|
@@ -148,16 +148,16 @@ type DataSyncRunDetail = {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
const LOG_LEVEL_STYLES: Record<string, string> = {
|
|
151
|
-
info: 'bg-
|
|
152
|
-
warn: 'bg-
|
|
153
|
-
error: 'bg-
|
|
151
|
+
info: 'bg-status-info-bg text-status-info-text',
|
|
152
|
+
warn: 'bg-status-warning-bg text-status-warning-text',
|
|
153
|
+
error: 'bg-status-error-bg text-status-error-text',
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
const HEALTH_STATUS_STYLES: Record<string, string> = {
|
|
157
|
-
healthy: 'bg-
|
|
158
|
-
degraded: 'bg-
|
|
159
|
-
unhealthy: 'bg-
|
|
160
|
-
unconfigured: 'bg-
|
|
157
|
+
healthy: 'bg-status-success-bg text-status-success-text',
|
|
158
|
+
degraded: 'bg-status-warning-bg text-status-warning-text',
|
|
159
|
+
unhealthy: 'bg-status-error-bg text-status-error-text',
|
|
160
|
+
unconfigured: 'bg-status-neutral-bg text-status-neutral-text',
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
const HEALTH_STATUS_ICONS: Record<string, React.ElementType> = {
|
|
@@ -203,12 +203,12 @@ function RunActivityStrip({
|
|
|
203
203
|
const processed = typeof run.progressJob?.processedCount === 'number' ? run.progressJob.processedCount : 0
|
|
204
204
|
const total = typeof run.progressJob?.totalCount === 'number' ? run.progressJob.totalCount : null
|
|
205
205
|
const statusClass = run.status === 'completed'
|
|
206
|
-
? 'border-
|
|
206
|
+
? 'border-status-success-border bg-status-success-bg text-status-success-text'
|
|
207
207
|
: run.status === 'failed'
|
|
208
|
-
? 'border-
|
|
208
|
+
? 'border-status-error-border bg-status-error-bg text-status-error-text'
|
|
209
209
|
: run.status === 'cancelled'
|
|
210
|
-
? 'border-
|
|
211
|
-
: 'border-
|
|
210
|
+
? 'border-status-neutral-border bg-status-neutral-bg text-status-neutral-text'
|
|
211
|
+
: 'border-status-info-border bg-status-info-bg text-status-info-text'
|
|
212
212
|
|
|
213
213
|
return (
|
|
214
214
|
<div className="flex flex-wrap items-center gap-3 rounded-lg border bg-muted/20 px-4 py-3 text-sm">
|
|
@@ -958,8 +958,8 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
958
958
|
] satisfies IntegrationDetailTab[]
|
|
959
959
|
const StateIcon = resolvedState?.isEnabled ? CheckCircle2 : XCircle
|
|
960
960
|
const stateBadgeClass = resolvedState?.isEnabled
|
|
961
|
-
? 'border-
|
|
962
|
-
: 'border-
|
|
961
|
+
? 'border-status-success-border bg-status-success-bg text-status-success-text'
|
|
962
|
+
: 'border-status-neutral-border bg-status-neutral-bg text-status-neutral-text'
|
|
963
963
|
|
|
964
964
|
const showCredentialActions = showCredentialsTab && activeTab === 'credentials' && credentialFormFields.length > 0
|
|
965
965
|
|
|
@@ -1150,7 +1150,7 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
1150
1150
|
{showCredentialsTab ? (
|
|
1151
1151
|
<TabsTrigger
|
|
1152
1152
|
value="credentials"
|
|
1153
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-
|
|
1153
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1154
1154
|
>
|
|
1155
1155
|
<span className="inline-flex items-center gap-2">
|
|
1156
1156
|
<Key className="h-4 w-4" />
|
|
@@ -1161,7 +1161,7 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
1161
1161
|
{leadingInjectedTab ? (
|
|
1162
1162
|
<TabsTrigger
|
|
1163
1163
|
value={leadingInjectedTab.id}
|
|
1164
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-
|
|
1164
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1165
1165
|
>
|
|
1166
1166
|
<span className="inline-flex items-center gap-2">
|
|
1167
1167
|
<Settings className="h-4 w-4" />
|
|
@@ -1172,7 +1172,7 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
1172
1172
|
{showVersionTab ? (
|
|
1173
1173
|
<TabsTrigger
|
|
1174
1174
|
value="version"
|
|
1175
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-
|
|
1175
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1176
1176
|
>
|
|
1177
1177
|
<span className="inline-flex items-center gap-2">
|
|
1178
1178
|
<RefreshCw className="h-4 w-4" />
|
|
@@ -1183,7 +1183,7 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
1183
1183
|
{showDataSyncScheduleTab ? (
|
|
1184
1184
|
<TabsTrigger
|
|
1185
1185
|
value="data-sync-schedule"
|
|
1186
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-
|
|
1186
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1187
1187
|
>
|
|
1188
1188
|
<span className="inline-flex items-center gap-2">
|
|
1189
1189
|
<Calendar className="h-4 w-4" />
|
|
@@ -1194,7 +1194,7 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
1194
1194
|
{showHealthTab ? (
|
|
1195
1195
|
<TabsTrigger
|
|
1196
1196
|
value="health"
|
|
1197
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-
|
|
1197
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1198
1198
|
>
|
|
1199
1199
|
<span className="inline-flex items-center gap-2">
|
|
1200
1200
|
<Activity className="h-4 w-4" />
|
|
@@ -1205,7 +1205,7 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
1205
1205
|
{showLogsTab ? (
|
|
1206
1206
|
<TabsTrigger
|
|
1207
1207
|
value="logs"
|
|
1208
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-
|
|
1208
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1209
1209
|
>
|
|
1210
1210
|
<span className="inline-flex items-center gap-2">
|
|
1211
1211
|
<FileText className="h-4 w-4" />
|
|
@@ -1217,7 +1217,7 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
1217
1217
|
<TabsTrigger
|
|
1218
1218
|
key={tab.id}
|
|
1219
1219
|
value={tab.id}
|
|
1220
|
-
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-
|
|
1220
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-accent-indigo aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
1221
1221
|
>
|
|
1222
1222
|
<span className="inline-flex items-center gap-2">
|
|
1223
1223
|
<Settings className="h-4 w-4" />
|
|
@@ -1231,7 +1231,7 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
1231
1231
|
<TabsContent value="credentials" className="mt-0">
|
|
1232
1232
|
<section className="space-y-4 rounded-lg border bg-card p-6">
|
|
1233
1233
|
{detail.bundle ? (
|
|
1234
|
-
<div className="rounded-lg border border-
|
|
1234
|
+
<div className="rounded-lg border border-status-info-border bg-status-info-bg p-3 text-sm text-status-info-text">
|
|
1235
1235
|
{t('integrations.detail.credentials.bundleShared', { bundle: detail.bundle.title })}
|
|
1236
1236
|
</div>
|
|
1237
1237
|
) : null}
|
|
@@ -166,7 +166,7 @@ export default function PlannerAvailabilityRuleSetDetailPage({ params }: { param
|
|
|
166
166
|
size="sm"
|
|
167
167
|
className={`relative -mb-px h-auto rounded-none border-b-2 px-0 py-2 font-medium ${
|
|
168
168
|
activeTab === tab.id
|
|
169
|
-
? 'border-
|
|
169
|
+
? 'border-accent-indigo text-foreground'
|
|
170
170
|
: 'border-transparent text-muted-foreground hover:text-foreground'
|
|
171
171
|
}`}
|
|
172
172
|
onClick={() => setActiveTab(tab.id as 'details' | 'availability')}
|
|
@@ -27,7 +27,9 @@ import {
|
|
|
27
27
|
} from '@open-mercato/core/modules/planner/components/unavailabilityReasons'
|
|
28
28
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
29
29
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
30
|
+
import { normalizeCrudServerError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
30
31
|
import { parseAvailabilityRuleWindow } from '@open-mercato/core/modules/planner/lib/availabilitySchedule'
|
|
32
|
+
import { deleteAvailabilityRuleSet } from '@open-mercato/core/modules/planner/lib/deleteAvailabilityRuleSet'
|
|
31
33
|
import { CrudForm, type CrudField } from '@open-mercato/ui/backend/CrudForm'
|
|
32
34
|
import { Calendar, Clock, List, PencilLine, Plus, Trash2 } from 'lucide-react'
|
|
33
35
|
import {
|
|
@@ -80,6 +82,7 @@ export type AvailabilityRulesEditorProps = {
|
|
|
80
82
|
initialTimezone?: string
|
|
81
83
|
rulesetId?: string | null
|
|
82
84
|
onRulesetChange?: (rulesetId: string | null) => Promise<void>
|
|
85
|
+
allowRuleSetDelete?: boolean
|
|
83
86
|
buildScheduleItems: AvailabilityScheduleItemBuilder
|
|
84
87
|
loadBookedEvents?: (range: ScheduleRange) => Promise<AvailabilityBookedEvent[]>
|
|
85
88
|
readOnly?: boolean
|
|
@@ -357,6 +360,7 @@ export function AvailabilityRulesEditor({
|
|
|
357
360
|
initialTimezone,
|
|
358
361
|
rulesetId,
|
|
359
362
|
onRulesetChange,
|
|
363
|
+
allowRuleSetDelete,
|
|
360
364
|
buildScheduleItems,
|
|
361
365
|
loadBookedEvents,
|
|
362
366
|
readOnly,
|
|
@@ -469,6 +473,11 @@ export function AvailabilityRulesEditor({
|
|
|
469
473
|
ruleSetCreateTimezoneLabel: t(`${labelPrefix}.availability.ruleset.createTimezone`, 'Timezone'),
|
|
470
474
|
ruleSetCreateSuccess: t(`${labelPrefix}.availability.ruleset.createSuccess`, 'Schedule saved.'),
|
|
471
475
|
ruleSetCreateError: t(`${labelPrefix}.availability.ruleset.createError`, 'Failed to save schedule.'),
|
|
476
|
+
ruleSetDelete: t(`${labelPrefix}.availability.ruleset.delete`, 'Delete schedule'),
|
|
477
|
+
ruleSetDeleteConfirm: t(`${labelPrefix}.availability.ruleset.deleteConfirm`, 'Delete schedule "{{name}}"?'),
|
|
478
|
+
ruleSetDeleteSubmit: t(`${labelPrefix}.availability.ruleset.deleteSubmit`, 'Delete'),
|
|
479
|
+
ruleSetDeleteSuccess: t(`${labelPrefix}.availability.ruleset.deleteSuccess`, 'Schedule deleted.'),
|
|
480
|
+
ruleSetDeleteError: t(`${labelPrefix}.availability.ruleset.deleteError`, 'Failed to delete schedule.'),
|
|
472
481
|
editTitle: t(`${modeBase}.form.title.edit`, 'Edit availability'),
|
|
473
482
|
addTitle: t(`${modeBase}.form.title.create`, 'Add availability'),
|
|
474
483
|
applyLabel: t(`${labelPrefix}.availability.actions.apply`, 'Apply'),
|
|
@@ -1009,6 +1018,47 @@ export function AvailabilityRulesEditor({
|
|
|
1009
1018
|
isReadOnly,
|
|
1010
1019
|
])
|
|
1011
1020
|
|
|
1021
|
+
const handleDeleteRuleSet = React.useCallback(async () => {
|
|
1022
|
+
if (isReadOnly || !onRulesetChange || !rulesetId) return
|
|
1023
|
+
const selected = ruleSets.find((entry) => entry.id === rulesetId)
|
|
1024
|
+
const name = selected?.name ?? rulesetId
|
|
1025
|
+
await deleteAvailabilityRuleSet({
|
|
1026
|
+
ruleSetId: rulesetId,
|
|
1027
|
+
confirmDelete: () => confirm({
|
|
1028
|
+
title: listLabels.ruleSetDeleteConfirm.replace('{{name}}', name),
|
|
1029
|
+
confirmText: listLabels.ruleSetDeleteSubmit,
|
|
1030
|
+
variant: 'destructive',
|
|
1031
|
+
}),
|
|
1032
|
+
deleteRuleSet: async (id) => {
|
|
1033
|
+
await deleteCrud('planner/availability-rule-sets', id, { errorMessage: listLabels.ruleSetDeleteError })
|
|
1034
|
+
},
|
|
1035
|
+
clearAssignment: async () => {
|
|
1036
|
+
setCustomOverridesEnabled(false)
|
|
1037
|
+
await onRulesetChange(null)
|
|
1038
|
+
await refreshAvailability()
|
|
1039
|
+
},
|
|
1040
|
+
refreshRuleSets,
|
|
1041
|
+
onSuccess: () => flash(listLabels.ruleSetDeleteSuccess, 'success'),
|
|
1042
|
+
onError: (error) => {
|
|
1043
|
+
console.error('planner.availability-rule-sets.delete', error)
|
|
1044
|
+
const normalized = normalizeCrudServerError(error)
|
|
1045
|
+
flash(normalized.message ?? listLabels.ruleSetDeleteError, 'error')
|
|
1046
|
+
},
|
|
1047
|
+
})
|
|
1048
|
+
}, [
|
|
1049
|
+
confirm,
|
|
1050
|
+
isReadOnly,
|
|
1051
|
+
listLabels.ruleSetDeleteConfirm,
|
|
1052
|
+
listLabels.ruleSetDeleteError,
|
|
1053
|
+
listLabels.ruleSetDeleteSubmit,
|
|
1054
|
+
listLabels.ruleSetDeleteSuccess,
|
|
1055
|
+
onRulesetChange,
|
|
1056
|
+
refreshAvailability,
|
|
1057
|
+
refreshRuleSets,
|
|
1058
|
+
ruleSets,
|
|
1059
|
+
rulesetId,
|
|
1060
|
+
])
|
|
1061
|
+
|
|
1012
1062
|
const ruleSetFormSchema = React.useMemo(
|
|
1013
1063
|
() => z.object({
|
|
1014
1064
|
name: z.string().min(1, t('ui.forms.errors.required', 'Required')),
|
|
@@ -1369,6 +1419,18 @@ export function AvailabilityRulesEditor({
|
|
|
1369
1419
|
{listLabels.ruleSetReset}
|
|
1370
1420
|
</Button>
|
|
1371
1421
|
) : null}
|
|
1422
|
+
{allowRuleSetDelete && rulesetId ? (
|
|
1423
|
+
<Button
|
|
1424
|
+
type="button"
|
|
1425
|
+
variant="destructive-outline"
|
|
1426
|
+
size="sm"
|
|
1427
|
+
onClick={() => { void handleDeleteRuleSet() }}
|
|
1428
|
+
disabled={isReadOnly}
|
|
1429
|
+
>
|
|
1430
|
+
<Trash2 className="size-4 mr-2" aria-hidden />
|
|
1431
|
+
{listLabels.ruleSetDelete}
|
|
1432
|
+
</Button>
|
|
1433
|
+
) : null}
|
|
1372
1434
|
</div>
|
|
1373
1435
|
) : null}
|
|
1374
1436
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type DeleteAvailabilityRuleSetOutcome = 'deleted' | 'cancelled' | 'noop' | 'failed'
|
|
2
|
+
|
|
3
|
+
export type DeleteAvailabilityRuleSetActions = {
|
|
4
|
+
ruleSetId: string | null | undefined
|
|
5
|
+
confirmDelete: () => Promise<boolean>
|
|
6
|
+
deleteRuleSet: (ruleSetId: string) => Promise<void>
|
|
7
|
+
clearAssignment: () => Promise<void>
|
|
8
|
+
refreshRuleSets: () => Promise<void>
|
|
9
|
+
onSuccess: () => void
|
|
10
|
+
onError: (error: unknown) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Orchestrates deleting an availability schedule (rule set) from the availability
|
|
15
|
+
* editor: confirm, delete the schedule, clear it from the current subject so no
|
|
16
|
+
* dangling assignment remains, refresh the selector, and report the outcome.
|
|
17
|
+
*/
|
|
18
|
+
export async function deleteAvailabilityRuleSet(
|
|
19
|
+
actions: DeleteAvailabilityRuleSetActions,
|
|
20
|
+
): Promise<DeleteAvailabilityRuleSetOutcome> {
|
|
21
|
+
const ruleSetId = actions.ruleSetId
|
|
22
|
+
if (!ruleSetId) return 'noop'
|
|
23
|
+
const confirmed = await actions.confirmDelete()
|
|
24
|
+
if (!confirmed) return 'cancelled'
|
|
25
|
+
try {
|
|
26
|
+
await actions.deleteRuleSet(ruleSetId)
|
|
27
|
+
await actions.clearAssignment()
|
|
28
|
+
await actions.refreshRuleSets()
|
|
29
|
+
actions.onSuccess()
|
|
30
|
+
return 'deleted'
|
|
31
|
+
} catch (error) {
|
|
32
|
+
actions.onError(error)
|
|
33
|
+
return 'failed'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -543,7 +543,7 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
543
543
|
size="sm"
|
|
544
544
|
className={`relative -mb-px h-auto rounded-none border-b-2 px-0 py-2 font-medium ${
|
|
545
545
|
activeTab === tab.id
|
|
546
|
-
? 'border-
|
|
546
|
+
? 'border-accent-indigo text-foreground'
|
|
547
547
|
: 'border-transparent text-muted-foreground hover:text-foreground'
|
|
548
548
|
}`}
|
|
549
549
|
onClick={() => setActiveTab(tab.id as 'details' | 'availability')}
|
|
@@ -567,7 +567,7 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
567
567
|
size="sm"
|
|
568
568
|
className={`relative -mb-px h-auto rounded-none border-b-2 px-0 py-1 font-medium ${
|
|
569
569
|
activeDetailTab === tab.id
|
|
570
|
-
? 'border-
|
|
570
|
+
? 'border-accent-indigo text-foreground'
|
|
571
571
|
: 'border-transparent text-muted-foreground hover:text-foreground'
|
|
572
572
|
}`}
|
|
573
573
|
onClick={() => setActiveDetailTab(tab.id)}
|
|
@@ -67,7 +67,15 @@ export async function POST(req: Request) {
|
|
|
67
67
|
const hashedToken = hashAuthToken(token)
|
|
68
68
|
const tenantScope = auth?.tenantId ? { tenantId: auth.tenantId } : undefined
|
|
69
69
|
|
|
70
|
-
const
|
|
70
|
+
const commandBus = container.resolve('commandBus') as CommandBus
|
|
71
|
+
|
|
72
|
+
// Lock the quote, flip it to confirmed, and convert it to an order inside a
|
|
73
|
+
// single transaction. The conversion command reuses this transaction (and its
|
|
74
|
+
// PESSIMISTIC_WRITE lock) via ctx.transactionalEm, so the status flip and the
|
|
75
|
+
// order creation are atomic: if conversion fails the whole transaction rolls
|
|
76
|
+
// back, leaving the quote in its prior 'sent' state with no partial order and
|
|
77
|
+
// no need for an out-of-band compensating write.
|
|
78
|
+
const { quote, orderId } = await em.transactional(async (trx) => {
|
|
71
79
|
const findQuoteByToken = (acceptanceToken: string) =>
|
|
72
80
|
findOneWithDecryption(
|
|
73
81
|
trx,
|
|
@@ -106,45 +114,21 @@ export async function POST(req: Request) {
|
|
|
106
114
|
trx.persist(quote)
|
|
107
115
|
await trx.flush()
|
|
108
116
|
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
const ctx: CommandRuntimeContext = {
|
|
118
|
+
container,
|
|
119
|
+
auth: null,
|
|
120
|
+
organizationScope: null,
|
|
121
|
+
selectedOrganizationId: quote.organizationId,
|
|
122
|
+
organizationIds: [quote.organizationId],
|
|
123
|
+
request: req,
|
|
124
|
+
transactionalEm: trx,
|
|
125
|
+
}
|
|
111
126
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
container,
|
|
115
|
-
auth: null,
|
|
116
|
-
organizationScope: null,
|
|
117
|
-
selectedOrganizationId: quote.organizationId,
|
|
118
|
-
organizationIds: [quote.organizationId],
|
|
119
|
-
request: req,
|
|
120
|
-
}
|
|
127
|
+
const result = (await commandBus.execute('sales.quotes.convert_to_order', { input: { quoteId: quote.id }, ctx })) as ConvertToOrderResult | null
|
|
128
|
+
const orderId = result?.result?.orderId ?? result?.orderId ?? quote.id
|
|
121
129
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
result = (await commandBus.execute('sales.quotes.convert_to_order', { input: { quoteId: quote.id }, ctx })) as ConvertToOrderResult | null
|
|
125
|
-
} catch (conversionError) {
|
|
126
|
-
const freshEm = (container.resolve('em') as EntityManager).fork()
|
|
127
|
-
const staleQuote = await findOneWithDecryption(
|
|
128
|
-
freshEm,
|
|
129
|
-
SalesQuote,
|
|
130
|
-
{ id: quote.id, deletedAt: null },
|
|
131
|
-
{},
|
|
132
|
-
tenantScope,
|
|
133
|
-
)
|
|
134
|
-
if (staleQuote) {
|
|
135
|
-
staleQuote.status = 'sent'
|
|
136
|
-
staleQuote.statusEntryId = await resolveStatusEntryIdByValue(freshEm, {
|
|
137
|
-
tenantId: staleQuote.tenantId,
|
|
138
|
-
organizationId: staleQuote.organizationId,
|
|
139
|
-
value: 'sent',
|
|
140
|
-
})
|
|
141
|
-
staleQuote.updatedAt = new Date()
|
|
142
|
-
freshEm.persist(staleQuote)
|
|
143
|
-
await freshEm.flush()
|
|
144
|
-
}
|
|
145
|
-
throw conversionError
|
|
146
|
-
}
|
|
147
|
-
const orderId = result?.result?.orderId ?? result?.orderId ?? quote.id
|
|
130
|
+
return { quote, orderId }
|
|
131
|
+
})
|
|
148
132
|
|
|
149
133
|
const order = await findOneWithDecryption(em, SalesOrder, { id: orderId, deletedAt: null }, {}, tenantScope)
|
|
150
134
|
const orderNumber = order?.orderNumber ?? orderId
|
|
@@ -102,7 +102,7 @@ export default function EditChannelPage({ params }: { params?: { channelId?: str
|
|
|
102
102
|
<button
|
|
103
103
|
key={value}
|
|
104
104
|
type="button"
|
|
105
|
-
className={`px-4 py-2 text-sm font-medium border-b-2 ${activeTab === value ? 'border-
|
|
105
|
+
className={`px-4 py-2 text-sm font-medium border-b-2 ${activeTab === value ? 'border-accent-indigo text-foreground' : 'border-transparent text-muted-foreground'}`}
|
|
106
106
|
onClick={() => handleTabSelect(value)}
|
|
107
107
|
>
|
|
108
108
|
{label}
|
|
@@ -4783,7 +4783,7 @@ export default function SalesDocumentDetailPage({
|
|
|
4783
4783
|
className={cn(
|
|
4784
4784
|
'h-auto rounded-none border-b-2 px-3 py-2 text-sm font-medium transition-colors hover:bg-transparent',
|
|
4785
4785
|
activeTab === tab.id
|
|
4786
|
-
? 'border-b-2 border-
|
|
4786
|
+
? 'border-b-2 border-accent-indigo text-foreground'
|
|
4787
4787
|
: 'border-transparent text-muted-foreground hover:text-foreground'
|
|
4788
4788
|
)}
|
|
4789
4789
|
onClick={() => setActiveTab(tab.id)}
|
|
@@ -5870,7 +5870,12 @@ const convertQuoteToOrderCommand: CommandHandler<
|
|
|
5870
5870
|
},
|
|
5871
5871
|
async execute(rawInput, ctx) {
|
|
5872
5872
|
const payload = quoteConvertToOrderSchema.parse(rawInput ?? {});
|
|
5873
|
-
|
|
5873
|
+
// When the caller supplies an existing transactional EntityManager, reuse it
|
|
5874
|
+
// (and its row locks) so the quote status flip and the order materialization
|
|
5875
|
+
// commit or roll back as one atomic unit — no out-of-band compensating write.
|
|
5876
|
+
const callerEm = ctx.transactionalEm;
|
|
5877
|
+
const rootEm =
|
|
5878
|
+
callerEm ?? (ctx.container.resolve("em") as EntityManager).fork();
|
|
5874
5879
|
const transactionalEm = rootEm as EntityManager & {
|
|
5875
5880
|
transactional?: <TResult>(
|
|
5876
5881
|
callback: (trx: EntityManager) => Promise<TResult>,
|
|
@@ -6195,12 +6200,21 @@ const convertQuoteToOrderCommand: CommandHandler<
|
|
|
6195
6200
|
|
|
6196
6201
|
return { orderId: order.id };
|
|
6197
6202
|
};
|
|
6203
|
+
if (callerEm) {
|
|
6204
|
+
// Already inside the caller's transaction — run directly so the whole flow
|
|
6205
|
+
// stays a single transaction (no nested savepoint).
|
|
6206
|
+
return runConversion(callerEm);
|
|
6207
|
+
}
|
|
6198
6208
|
return typeof transactionalEm.transactional === "function"
|
|
6199
6209
|
? transactionalEm.transactional((trx) => runConversion(trx))
|
|
6200
6210
|
: runConversion(rootEm);
|
|
6201
6211
|
},
|
|
6202
6212
|
captureAfter: async (_input, result, ctx) => {
|
|
6203
|
-
|
|
6213
|
+
// Prefer the caller's transactional EM so the just-created (still
|
|
6214
|
+
// uncommitted) order is visible when building the audit "after" snapshot.
|
|
6215
|
+
const em =
|
|
6216
|
+
ctx.transactionalEm ??
|
|
6217
|
+
(ctx.container.resolve("em") as EntityManager).fork();
|
|
6204
6218
|
return loadOrderSnapshot(em, result.orderId);
|
|
6205
6219
|
},
|
|
6206
6220
|
buildLog: async ({ snapshots, result }) => {
|
|
@@ -369,7 +369,7 @@ export default function StaffTeamMemberDetailPage({ params }: { params?: { id?:
|
|
|
369
369
|
onClick={() => setActivePanel(tab.id)}
|
|
370
370
|
className={`relative -mb-px border-b-2 px-0 py-2 text-sm font-medium transition-colors ${
|
|
371
371
|
activePanel === tab.id
|
|
372
|
-
? 'border-
|
|
372
|
+
? 'border-accent-indigo text-foreground'
|
|
373
373
|
: 'border-transparent text-muted-foreground hover:text-foreground'
|
|
374
374
|
}`}
|
|
375
375
|
>
|
|
@@ -431,7 +431,7 @@ export default function StaffTeamMemberDetailPage({ params }: { params?: { id?:
|
|
|
431
431
|
onClick={() => setActiveTab(tab.id)}
|
|
432
432
|
className={`relative -mb-px border-b-2 px-0 py-1 text-sm font-medium transition-colors ${
|
|
433
433
|
activeTab === tab.id
|
|
434
|
-
? 'border-
|
|
434
|
+
? 'border-accent-indigo text-foreground'
|
|
435
435
|
: 'border-transparent text-muted-foreground hover:text-foreground'
|
|
436
436
|
}`}
|
|
437
437
|
>
|
|
@@ -556,6 +556,7 @@ export default function StaffTeamMemberDetailPage({ params }: { params?: { id?:
|
|
|
556
556
|
mode="availability"
|
|
557
557
|
rulesetId={availabilityRuleSetId}
|
|
558
558
|
onRulesetChange={handleRulesetChange}
|
|
559
|
+
allowRuleSetDelete
|
|
559
560
|
buildScheduleItems={({ availabilityRules, translate: translateLabel }) => (
|
|
560
561
|
buildMemberScheduleItems({ availabilityRules, translate: translateLabel })
|
|
561
562
|
)}
|
|
@@ -306,7 +306,7 @@ export default function StaffTeamEditPage({ params }: { params?: { id?: string }
|
|
|
306
306
|
onClick={() => setActiveTab(tab.id as 'details' | 'members')}
|
|
307
307
|
className={`relative -mb-px border-b-2 px-0 py-2 text-sm font-medium transition-colors ${
|
|
308
308
|
activeTab === tab.id
|
|
309
|
-
? 'border-
|
|
309
|
+
? 'border-accent-indigo text-foreground'
|
|
310
310
|
: 'border-transparent text-muted-foreground hover:text-foreground'
|
|
311
311
|
}`}
|
|
312
312
|
>
|
|
@@ -481,6 +481,11 @@
|
|
|
481
481
|
"staff.teamMembers.availability.ruleset.createTitle": "Als Zeitplan speichern",
|
|
482
482
|
"staff.teamMembers.availability.ruleset.customize": "Zeitplan anpassen",
|
|
483
483
|
"staff.teamMembers.availability.ruleset.customizePrompt": "Dieser Zeitplan basiert auf einem gemeinsamen Regelwerk. Passe ihn an, um Änderungen vorzunehmen.",
|
|
484
|
+
"staff.teamMembers.availability.ruleset.delete": "Zeitplan löschen",
|
|
485
|
+
"staff.teamMembers.availability.ruleset.deleteConfirm": "Zeitplan „{{name}}“ löschen?",
|
|
486
|
+
"staff.teamMembers.availability.ruleset.deleteError": "Zeitplan konnte nicht gelöscht werden.",
|
|
487
|
+
"staff.teamMembers.availability.ruleset.deleteSubmit": "Löschen",
|
|
488
|
+
"staff.teamMembers.availability.ruleset.deleteSuccess": "Zeitplan gelöscht.",
|
|
484
489
|
"staff.teamMembers.availability.ruleset.error": "Zeitpläne konnten nicht geladen werden.",
|
|
485
490
|
"staff.teamMembers.availability.ruleset.label": "Zeitplan",
|
|
486
491
|
"staff.teamMembers.availability.ruleset.loading": "Zeitpläne werden geladen...",
|
|
@@ -481,6 +481,11 @@
|
|
|
481
481
|
"staff.teamMembers.availability.ruleset.createTitle": "Save as schedule",
|
|
482
482
|
"staff.teamMembers.availability.ruleset.customize": "Customize schedule",
|
|
483
483
|
"staff.teamMembers.availability.ruleset.customizePrompt": "This schedule is based on a shared ruleset. Customize it to make changes.",
|
|
484
|
+
"staff.teamMembers.availability.ruleset.delete": "Delete schedule",
|
|
485
|
+
"staff.teamMembers.availability.ruleset.deleteConfirm": "Delete schedule \"{{name}}\"?",
|
|
486
|
+
"staff.teamMembers.availability.ruleset.deleteError": "Failed to delete schedule.",
|
|
487
|
+
"staff.teamMembers.availability.ruleset.deleteSubmit": "Delete",
|
|
488
|
+
"staff.teamMembers.availability.ruleset.deleteSuccess": "Schedule deleted.",
|
|
484
489
|
"staff.teamMembers.availability.ruleset.error": "Failed to load schedules.",
|
|
485
490
|
"staff.teamMembers.availability.ruleset.label": "Schedule",
|
|
486
491
|
"staff.teamMembers.availability.ruleset.loading": "Loading schedules...",
|
|
@@ -481,6 +481,11 @@
|
|
|
481
481
|
"staff.teamMembers.availability.ruleset.createTitle": "Guardar como horario",
|
|
482
482
|
"staff.teamMembers.availability.ruleset.customize": "Personalizar horario",
|
|
483
483
|
"staff.teamMembers.availability.ruleset.customizePrompt": "Este horario se basa en un conjunto de reglas compartido. Personalízalo para hacer cambios.",
|
|
484
|
+
"staff.teamMembers.availability.ruleset.delete": "Eliminar horario",
|
|
485
|
+
"staff.teamMembers.availability.ruleset.deleteConfirm": "¿Eliminar el horario \"{{name}}\"?",
|
|
486
|
+
"staff.teamMembers.availability.ruleset.deleteError": "No se pudo eliminar el horario.",
|
|
487
|
+
"staff.teamMembers.availability.ruleset.deleteSubmit": "Eliminar",
|
|
488
|
+
"staff.teamMembers.availability.ruleset.deleteSuccess": "Horario eliminado.",
|
|
484
489
|
"staff.teamMembers.availability.ruleset.error": "No se pudieron cargar los horarios.",
|
|
485
490
|
"staff.teamMembers.availability.ruleset.label": "Horario",
|
|
486
491
|
"staff.teamMembers.availability.ruleset.loading": "Cargando horarios...",
|
|
@@ -481,6 +481,11 @@
|
|
|
481
481
|
"staff.teamMembers.availability.ruleset.createTitle": "Zapisz jako harmonogram",
|
|
482
482
|
"staff.teamMembers.availability.ruleset.customize": "Dostosuj harmonogram",
|
|
483
483
|
"staff.teamMembers.availability.ruleset.customizePrompt": "Ten harmonogram jest oparty na wspólnym zestawie reguł. Dostosuj go, aby wprowadzić zmiany.",
|
|
484
|
+
"staff.teamMembers.availability.ruleset.delete": "Usuń harmonogram",
|
|
485
|
+
"staff.teamMembers.availability.ruleset.deleteConfirm": "Usunąć harmonogram „{{name}}”?",
|
|
486
|
+
"staff.teamMembers.availability.ruleset.deleteError": "Nie udało się usunąć harmonogramu.",
|
|
487
|
+
"staff.teamMembers.availability.ruleset.deleteSubmit": "Usuń",
|
|
488
|
+
"staff.teamMembers.availability.ruleset.deleteSuccess": "Harmonogram usunięty.",
|
|
484
489
|
"staff.teamMembers.availability.ruleset.error": "Nie udało się wczytać harmonogramów.",
|
|
485
490
|
"staff.teamMembers.availability.ruleset.label": "Harmonogram",
|
|
486
491
|
"staff.teamMembers.availability.ruleset.loading": "Wczytywanie harmonogramów...",
|