11'use client' ;
22
3- import React , { FC , Fragment , useCallback , useMemo , useState } from 'react' ;
3+ import React , { FC , useCallback , useMemo , useState } from 'react' ;
44import { Integrations } from '@gitroom/frontend/components/launches/calendar.context' ;
55import dayjs from 'dayjs' ;
66import { deleteDialog } from '@gitroom/react/helpers/delete.dialog' ;
@@ -16,14 +16,24 @@ import { sortBy } from 'lodash';
1616import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload' ;
1717import { useT } from '@gitroom/react/translation/get.transation.service.client' ;
1818import { newDayjs } from '@gitroom/frontend/components/layout/set.timezone' ;
19+ import clsx from 'clsx' ;
20+ import {
21+ TrashIcon ,
22+ PlusIcon ,
23+ DelayIcon ,
24+ } from '@gitroom/frontend/components/ui/icons' ;
25+
1926dayjs . extend ( utc ) ;
2027dayjs . extend ( timezone ) ;
21- const hours = [ ...Array ( 24 ) . keys ( ) ] . map ( ( i , index ) => ( {
22- value : index ,
28+
29+ const hours = [ ...Array ( 24 ) . keys ( ) ] . map ( ( i ) => ( {
30+ value : i ,
2331} ) ) ;
24- const minutes = [ ...Array ( 60 ) . keys ( ) ] . map ( ( i , index ) => ( {
25- value : index ,
32+
33+ const minutes = [ ...Array ( 60 ) . keys ( ) ] . map ( ( i ) => ( {
34+ value : i ,
2635} ) ) ;
36+
2737export const TimeTable : FC < {
2838 integration : Integrations ;
2939 mutate : ( ) => void ;
@@ -39,6 +49,7 @@ export const TimeTable: FC<{
3949 const fetch = useFetch ( ) ;
4050 const modal = useModals ( ) ;
4151 usePreventWindowUnload ( true ) ;
52+
4253 const askClose = useCallback ( async ( ) => {
4354 if (
4455 ! ( await deleteDialog (
@@ -53,7 +64,9 @@ export const TimeTable: FC<{
5364 }
5465 modal . closeAll ( ) ;
5566 } , [ ] ) ;
67+
5668 useKeypress ( 'Escape' , askClose ) ;
69+
5770 const removeSlot = useCallback (
5871 ( index : number ) => async ( ) => {
5972 if (
@@ -70,21 +83,24 @@ export const TimeTable: FC<{
7083 } ,
7184 [ ]
7285 ) ;
86+
7387 const addHour = useCallback ( ( ) => {
7488 const calculateMinutes =
7589 newDayjs ( )
7690 . utc ( )
7791 . startOf ( 'day' )
7892 . add ( hour , 'hours' )
7993 . add ( minute , 'minutes' )
80- . diff ( newDayjs ( ) . utc ( ) . startOf ( 'day' ) , 'minutes' ) - dayjs . tz ( ) . utcOffset ( ) ;
94+ . diff ( newDayjs ( ) . utc ( ) . startOf ( 'day' ) , 'minutes' ) -
95+ dayjs . tz ( ) . utcOffset ( ) ;
8196 setCurrentTimes ( ( prev ) => [
8297 ...prev ,
8398 {
8499 time : calculateMinutes ,
85100 } ,
86101 ] ) ;
87102 } , [ hour , minute ] ) ;
103+
88104 const times = useMemo ( ( ) => {
89105 return sortBy (
90106 currentTimes . map ( ( { time } ) => ( {
@@ -99,6 +115,7 @@ export const TimeTable: FC<{
99115 ( p ) => p . value
100116 ) ;
101117 } , [ currentTimes ] ) ;
118+
102119 const save = useCallback ( async ( ) => {
103120 await fetch ( `/integrations/${ props . integration . id } /time` , {
104121 method : 'POST' ,
@@ -109,72 +126,105 @@ export const TimeTable: FC<{
109126 mutate ( ) ;
110127 modal . closeAll ( ) ;
111128 } , [ currentTimes ] ) ;
129+
112130 return (
113- < div className = "relative w-full" >
114- < div >
115- < div className = "text-[16px] font-bold mt-[16px]" >
131+ < div className = "relative w-full max-w-[400px] mx-auto" >
132+ { /* Add Time Slot Section */ }
133+ < div className = "bg-newBgColorInner rounded-[12px] p-[20px] border border-newTableBorder" >
134+ < div className = "text-[15px] font-semibold mb-[16px] flex items-center gap-[8px]" >
135+ < DelayIcon size = { 18 } className = "text-[#612BD3]" />
116136 { t ( 'add_time_slot' , 'Add Time Slot' ) }
117137 </ div >
118- < div className = "flex flex-col" >
119- < div className = "mt-[16px] flex justify-center gap-[16px]" >
120- < div className = "w-[100px]" >
121- < Select
122- label = { t ( 'hour' , 'Hour' ) }
123- name = "hour"
124- disableForm = { true }
125- className = "w-[100px] mt-[8px]"
126- value = { hour }
127- onChange = { ( e ) => setHour ( Number ( e . target . value ) ) }
128- >
129- { hours . map ( ( hour ) => (
130- < option key = { hour . value } value = { hour . value } >
131- { hour . value . toString ( ) . length === 1 ? '0' : '' }
132- { hour . value }
133- </ option >
134- ) ) }
135- </ Select >
136- </ div >
137- < div className = "w-[100px]" >
138- < Select
139- label = { t ( 'minutes' , 'Minutes' ) }
140- name = "minutes"
141- disableForm = { true }
142- className = "w-[100px] mt-[8px]"
143- value = { minute }
144- onChange = { ( e ) => setMinute ( Number ( e . target . value ) ) }
145- >
146- { minutes . map ( ( minute ) => (
147- < option key = { minute . value } value = { minute . value } >
148- { minute . value . toString ( ) . length === 1 ? '0' : '' }
149- { minute . value }
150- </ option >
151- ) ) }
152- </ Select >
153- </ div >
138+
139+ < div className = "flex gap-[12px] items-end" >
140+ < div className = "flex-1" >
141+ < Select
142+ label = { t ( 'hour' , 'Hour' ) }
143+ name = "hour"
144+ disableForm = { true }
145+ hideErrors = { true }
146+ value = { hour }
147+ onChange = { ( e ) => setHour ( Number ( e . target . value ) ) }
148+ >
149+ { hours . map ( ( h ) => (
150+ < option key = { h . value } value = { h . value } >
151+ { h . value . toString ( ) . padStart ( 2 , '0' ) }
152+ </ option >
153+ ) ) }
154+ </ Select >
154155 </ div >
155- < div className = "flex w-[215px] mx-auto justify-center mb-[50px]" >
156- < Button type = "button" className = "w-full" onClick = { addHour } >
157- { t ( 'add_slot' , 'Add Slot' ) }
158- </ Button >
156+ < div className = "flex-1" >
157+ < Select
158+ label = { t ( 'minutes' , 'Minutes' ) }
159+ name = "minutes"
160+ disableForm = { true }
161+ hideErrors = { true }
162+ value = { minute }
163+ onChange = { ( e ) => setMinute ( Number ( e . target . value ) ) }
164+ >
165+ { minutes . map ( ( m ) => (
166+ < option key = { m . value } value = { m . value } >
167+ { m . value . toString ( ) . padStart ( 2 , '0' ) }
168+ </ option >
169+ ) ) }
170+ </ Select >
159171 </ div >
172+ < button
173+ type = "button"
174+ onClick = { addHour }
175+ className = "h-[42px] px-[16px] bg-[#612BD3] hover:bg-[#7640e0] transition-colors rounded-[8px] flex items-center gap-[6px] text-white text-[14px] font-medium"
176+ >
177+ < PlusIcon size = { 14 } />
178+ { t ( 'add' , 'Add' ) }
179+ </ button >
160180 </ div >
161181 </ div >
162- < div className = "mt-[16px] grid grid-cols-2 place-items-center w-[100px] mx-auto" >
163- { times . map ( ( timeSlot , index ) => (
164- < Fragment key = { timeSlot . formatted } >
165- < div className = "text-start w-full" > { timeSlot . formatted } </ div >
166- < div
167- className = "cursor-pointer text-red-400 text-start w-full"
168- onClick = { removeSlot ( index ) }
169- >
170- X
171- </ div >
172- </ Fragment >
173- ) ) }
182+
183+ { /* Time Slots List */ }
184+ < div className = "mt-[20px]" >
185+ < div className = "text-[14px] text-newTextColor/60 mb-[12px]" >
186+ { t ( 'scheduled_times' , 'Scheduled Times' ) } ({ times . length } )
187+ </ div >
188+
189+ { times . length === 0 ? (
190+ < div className = "text-center py-[32px] text-newTextColor/40 text-[14px] border border-dashed border-newTableBorder rounded-[12px]" >
191+ { t ( 'no_time_slots' , 'No time slots added yet' ) }
192+ </ div >
193+ ) : (
194+ < div className = "flex flex-col gap-[8px]" >
195+ { times . map ( ( timeSlot , index ) => (
196+ < div
197+ key = { `${ timeSlot . value } -${ index } ` }
198+ className = { clsx (
199+ 'group flex items-center justify-between' ,
200+ 'h-[48px] px-[16px] rounded-[8px]' ,
201+ 'bg-newBgColorInner border border-newTableBorder' ,
202+ 'hover:border-[#612BD3]/40 transition-colors'
203+ ) }
204+ >
205+ < div className = "flex items-center gap-[12px]" >
206+ < div className = "w-[8px] h-[8px] rounded-full bg-[#612BD3]" />
207+ < span className = "text-[15px] font-medium tabular-nums" >
208+ { timeSlot . formatted }
209+ </ span >
210+ </ div >
211+ < button
212+ type = "button"
213+ onClick = { removeSlot ( index ) }
214+ className = "opacity-0 group-hover:opacity-100 transition-opacity p-[8px] hover:bg-red-500/10 rounded-[6px] text-red-400 hover:text-red-500"
215+ >
216+ < TrashIcon size = { 16 } />
217+ </ button >
218+ </ div >
219+ ) ) }
220+ </ div >
221+ ) }
174222 </ div >
175- < div className = "flex w-[215px] mx-auto justify-center mb-[50px]" >
176- < Button type = "button" className = "w-full" onClick = { save } >
177- { t ( 'save' , 'Save' ) }
223+
224+ { /* Save Button */ }
225+ < div className = "mt-[24px]" >
226+ < Button type = "button" className = "w-full rounded-[8px]" onClick = { save } >
227+ { t ( 'save_changes' , 'Save Changes' ) }
178228 </ Button >
179229 </ div >
180230 </ div >
0 commit comments