@@ -6,19 +6,13 @@ import { useEffect, useMemo, useState } from 'react';
66import { type Database , databaseApi } from '../../../../entity/databases' ;
77import { Period } from '../../../../entity/databases/model/Period' ;
88import { type Interval , IntervalType } from '../../../../entity/intervals' ;
9-
10- interface Props {
11- database : Database ;
12-
13- isShowName ?: boolean ;
14-
15- isShowCancelButton ?: boolean ;
16- onCancel : ( ) => void ;
17-
18- saveButtonText ?: string ;
19- isSaveToApi : boolean ;
20- onSaved : ( database : Database ) => void ;
21- }
9+ import {
10+ getLocalDayOfMonth ,
11+ getLocalWeekday ,
12+ getUserTimeFormat ,
13+ getUtcDayOfMonth ,
14+ getUtcWeekday ,
15+ } from '../../../../shared/time/utils' ;
2216
2317const weekdayOptions = [
2418 { value : 1 , label : 'Mon' } ,
@@ -30,22 +24,23 @@ const weekdayOptions = [
3024 { value : 7 , label : 'Sun' } ,
3125] ;
3226
33- // Function to detect if user prefers 12-hour format based on their locale
34- const getUserTimeFormat = ( ) => {
35- const locale = navigator . language || 'en-US' ;
36- const testDate = new Date ( 2023 , 0 , 1 , 13 , 0 , 0 ) ; // 1 PM
37- const timeString = testDate . toLocaleTimeString ( locale , { hour : 'numeric' } ) ;
38- return timeString . includes ( 'PM' ) || timeString . includes ( 'AM' ) ;
39- } ;
27+ interface Props {
28+ database : Database ;
29+
30+ isShowName ?: boolean ;
31+ isShowCancelButton ?: boolean ;
32+ onCancel : ( ) => void ;
33+
34+ saveButtonText ?: string ;
35+ isSaveToApi : boolean ;
36+ onSaved : ( db : Database ) => void ;
37+ }
4038
4139export const EditDatabaseBaseInfoComponent = ( {
4240 database,
43-
4441 isShowName,
45-
4642 isShowCancelButton,
4743 onCancel,
48-
4944 saveButtonText,
5045 isSaveToApi,
5146 onSaved,
@@ -54,73 +49,83 @@ export const EditDatabaseBaseInfoComponent = ({
5449 const [ isUnsaved , setIsUnsaved ] = useState ( false ) ;
5550 const [ isSaving , setIsSaving ] = useState ( false ) ;
5651
57- // Detect user's preferred time format (12-hour vs 24-hour)
5852 const timeFormat = useMemo ( ( ) => {
59- const is12Hour = getUserTimeFormat ( ) ;
60- return {
61- use12Hours : is12Hour ,
62- format : is12Hour ? 'h:mm A' : 'HH:mm' ,
63- } ;
53+ const is12 = getUserTimeFormat ( ) ;
54+ return { use12Hours : is12 , format : is12 ? 'h:mm A' : 'HH:mm' } ;
6455 } , [ ] ) ;
6556
6657 const updateDatabase = ( patch : Partial < Database > ) => {
67- if ( ! editingDatabase ) return ;
68- setEditingDatabase ( { ...editingDatabase , ...patch } ) ;
58+ setEditingDatabase ( ( prev ) => ( prev ? { ...prev , ...patch } : prev ) ) ;
59+ setIsUnsaved ( true ) ;
60+ } ;
61+
62+ const saveInterval = ( patch : Partial < Interval > ) => {
63+ setEditingDatabase ( ( prev ) => {
64+ if ( ! prev ) return prev ;
65+
66+ const updatedBackupInterval = { ...( prev . backupInterval ?? { } ) , ...patch } ;
67+
68+ if ( ! updatedBackupInterval . id && prev . backupInterval ?. id ) {
69+ updatedBackupInterval . id = prev . backupInterval . id ;
70+ }
71+
72+ return { ...prev , backupInterval : updatedBackupInterval as Interval } ;
73+ } ) ;
74+
6975 setIsUnsaved ( true ) ;
7076 } ;
7177
7278 const saveDatabase = async ( ) => {
7379 if ( ! editingDatabase ) return ;
74-
7580 if ( isSaveToApi ) {
7681 setIsSaving ( true ) ;
77-
7882 try {
7983 await databaseApi . updateDatabase ( editingDatabase ) ;
8084 setIsUnsaved ( false ) ;
8185 } catch ( e ) {
8286 alert ( ( e as Error ) . message ) ;
8387 }
84-
8588 setIsSaving ( false ) ;
8689 }
87-
8890 onSaved ( editingDatabase ) ;
8991 } ;
9092
91- const saveInterval = ( patch : Partial < Interval > ) => {
92- if ( ! editingDatabase ) return ;
93- const current = editingDatabase . backupInterval ?? ( { } as Interval ) ;
94- updateDatabase ( { backupInterval : { ...current , ...patch } } ) ;
95- } ;
96-
9793 useEffect ( ( ) => {
9894 setIsSaving ( false ) ;
9995 setIsUnsaved ( false ) ;
100-
10196 setEditingDatabase ( { ...database } ) ;
10297 } , [ database ] ) ;
10398
10499 if ( ! editingDatabase ) return null ;
105-
106100 const { backupInterval } = editingDatabase ;
107101
102+ // UTC → local conversions for display
108103 const localTime : Dayjs | undefined = backupInterval ?. timeOfDay
109- ? dayjs . utc ( backupInterval . timeOfDay , 'HH:mm' ) . local ( ) /* cast to user tz */
104+ ? dayjs . utc ( backupInterval . timeOfDay , 'HH:mm' ) . local ( )
110105 : undefined ;
111106
112- let isAllFieldsFilled = true ;
113-
114- if ( ! editingDatabase . name ) isAllFieldsFilled = false ;
115- if ( ! editingDatabase . storePeriod ) isAllFieldsFilled = false ;
116-
117- if ( ! editingDatabase . backupInterval ?. interval ) isAllFieldsFilled = false ;
118- if ( editingDatabase . backupInterval ?. interval === IntervalType . WEEKLY ) {
119- if ( ! editingDatabase . backupInterval ?. weekday ) isAllFieldsFilled = false ;
120- }
121- if ( editingDatabase . backupInterval ?. interval === IntervalType . MONTHLY ) {
122- if ( ! editingDatabase . backupInterval . dayOfMonth ) isAllFieldsFilled = false ;
123- }
107+ const displayedWeekday : number | undefined =
108+ backupInterval ?. interval === IntervalType . WEEKLY &&
109+ backupInterval . weekday &&
110+ backupInterval . timeOfDay
111+ ? getLocalWeekday ( backupInterval . weekday , backupInterval . timeOfDay )
112+ : backupInterval ?. weekday ;
113+
114+ const displayedDayOfMonth : number | undefined =
115+ backupInterval ?. interval === IntervalType . MONTHLY &&
116+ backupInterval . dayOfMonth &&
117+ backupInterval . timeOfDay
118+ ? getLocalDayOfMonth ( backupInterval . dayOfMonth , backupInterval . timeOfDay )
119+ : backupInterval ?. dayOfMonth ;
120+
121+ // mandatory-field check
122+ const isAllFieldsFilled =
123+ Boolean ( editingDatabase . name ) &&
124+ Boolean ( editingDatabase . storePeriod ) &&
125+ Boolean ( backupInterval ?. interval ) &&
126+ ( ! backupInterval ||
127+ ( ( backupInterval . interval !== IntervalType . WEEKLY || displayedWeekday ) &&
128+ ( backupInterval . interval !== IntervalType . MONTHLY || displayedDayOfMonth ) ) ) ;
124129
125130 return (
126131 < div >
@@ -129,9 +134,7 @@ export const EditDatabaseBaseInfoComponent = ({
129134 < div className = "min-w-[150px]" > Name</ div >
130135 < Input
131136 value = { editingDatabase . name || '' }
132- onChange = { ( e ) => {
133- updateDatabase ( { name : e . target . value } ) ;
134- } }
137+ onChange = { ( e ) => updateDatabase ( { name : e . target . value } ) }
135138 size = "small"
136139 placeholder = "My favourite DB"
137140 className = "max-w-[200px] grow"
@@ -143,9 +146,7 @@ export const EditDatabaseBaseInfoComponent = ({
143146 < div className = "min-w-[150px]" > Backup interval</ div >
144147 < Select
145148 value = { backupInterval ?. interval }
146- onChange = { ( v ) => {
147- saveInterval ( { interval : v } ) ;
148- } }
149+ onChange = { ( v ) => saveInterval ( { interval : v } ) }
149150 size = "small"
150151 className = "max-w-[200px] grow"
151152 options = { [
@@ -154,22 +155,22 @@ export const EditDatabaseBaseInfoComponent = ({
154155 { label : 'Weekly' , value : IntervalType . WEEKLY } ,
155156 { label : 'Monthly' , value : IntervalType . MONTHLY } ,
156157 ] }
157- placeholder = "Select backup interval"
158158 />
159159 </ div >
160160
161161 { backupInterval ?. interval === IntervalType . WEEKLY && (
162162 < div className = "mb-1 flex w-full items-center" >
163163 < div className = "min-w-[150px]" > Backup weekday</ div >
164164 < Select
165- value = { backupInterval . weekday }
166- onChange = { ( v ) => {
167- saveInterval ( { weekday : v } ) ;
165+ value = { displayedWeekday }
166+ onChange = { ( localWeekday ) => {
167+ if ( ! localWeekday ) return ;
168+ const ref = localTime ?? dayjs ( ) ;
169+ saveInterval ( { weekday : getUtcWeekday ( localWeekday , ref ) } ) ;
168170 } }
169171 size = "small"
170172 className = "max-w-[200px] grow"
171173 options = { weekdayOptions }
172- placeholder = "Select backup weekday"
173174 />
174175 </ div >
175176 ) }
@@ -180,13 +181,14 @@ export const EditDatabaseBaseInfoComponent = ({
180181 < InputNumber
181182 min = { 1 }
182183 max = { 31 }
183- value = { backupInterval . dayOfMonth }
184- onChange = { ( v ) => {
185- saveInterval ( { dayOfMonth : v ?? 1 } ) ;
184+ value = { displayedDayOfMonth }
185+ onChange = { ( localDom ) => {
186+ if ( ! localDom ) return ;
187+ const ref = localTime ?? dayjs ( ) ;
188+ saveInterval ( { dayOfMonth : getUtcDayOfMonth ( localDom , ref ) } ) ;
186189 } }
187190 size = "small"
188191 className = "max-w-[200px] grow"
189- placeholder = "Select backup day of month"
190192 />
191193 </ div >
192194 ) }
@@ -198,15 +200,22 @@ export const EditDatabaseBaseInfoComponent = ({
198200 value = { localTime }
199201 format = { timeFormat . format }
200202 use12Hours = { timeFormat . use12Hours }
201- onChange = { ( t ) => {
202- if ( ! t ) return ;
203- // convert local picker value → UTC "HH:mm"
204- const utcString = t . utc ( ) . format ( 'HH:mm' ) ;
205- saveInterval ( { timeOfDay : utcString } ) ;
206- } }
207203 allowClear = { false }
208204 size = "small"
209205 className = "max-w-[200px] grow"
206+ onChange = { ( t ) => {
207+ if ( ! t ) return ;
208+ const patch : Partial < Interval > = { timeOfDay : t . utc ( ) . format ( 'HH:mm' ) } ;
209+
210+ if ( backupInterval ?. interval === IntervalType . WEEKLY && displayedWeekday ) {
211+ patch . weekday = getUtcWeekday ( displayedWeekday , t ) ;
212+ }
213+ if ( backupInterval ?. interval === IntervalType . MONTHLY && displayedDayOfMonth ) {
214+ patch . dayOfMonth = getUtcDayOfMonth ( displayedDayOfMonth , t ) ;
215+ }
216+
217+ saveInterval ( patch ) ;
218+ } }
210219 />
211220 </ div >
212221 ) }
@@ -215,9 +224,7 @@ export const EditDatabaseBaseInfoComponent = ({
215224 < div className = "min-w-[150px]" > Store period</ div >
216225 < Select
217226 value = { editingDatabase . storePeriod }
218- onChange = { ( v ) => {
219- updateDatabase ( { storePeriod : v } ) ;
220- } }
227+ onChange = { ( v ) => updateDatabase ( { storePeriod : v } ) }
221228 size = "small"
222229 className = "max-w-[200px] grow"
223230 options = { [
@@ -236,23 +243,22 @@ export const EditDatabaseBaseInfoComponent = ({
236243 />
237244 < Tooltip
238245 className = "cursor-pointer"
239- title = "How long to keep the backups? Make sure that you have enough space on the storage you are using (local, S3, Goole Drive, etc.) ."
246+ title = "How long to keep the backups? Make sure you have enough storage space ."
240247 >
241248 < InfoCircleOutlined className = "ml-2" style = { { color : 'gray' } } />
242249 </ Tooltip >
243250 </ div >
244251
245252 < div className = "mt-5 flex" >
246253 { isShowCancelButton && (
247- < Button className = "mr-1" danger ghost onClick = { ( ) => onCancel ( ) } >
254+ < Button danger ghost className = "mr-1" onClick = { onCancel } >
248255 Cancel
249256 </ Button >
250257 ) }
251-
252258 < Button
253- className = { `${ isShowCancelButton ? 'ml-1' : 'ml-auto' } mr-5` }
254259 type = "primary"
255- onClick = { ( ) => saveDatabase ( ) }
260+ className = { `${ isShowCancelButton ? 'ml-1' : 'ml-auto' } mr-5` }
261+ onClick = { saveDatabase }
256262 loading = { isSaving }
257263 disabled = { ! isUnsaved || ! isAllFieldsFilled }
258264 >
0 commit comments