Skip to content

Commit 8a577de

Browse files
committed
feat: time table improvements
1 parent 809476a commit 8a577de

File tree

2 files changed

+115
-65
lines changed

2 files changed

+115
-65
lines changed

apps/frontend/src/components/launches/menu/menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export const Menu: FC<{
168168
(integration) => integration.id === id
169169
);
170170
modal.openModal({
171-
withCloseButton: false,
171+
withCloseButton: true,
172172
closeOnEscape: false,
173173
closeOnClickOutside: false,
174174
askClose: true,

apps/frontend/src/components/launches/time.table.tsx

Lines changed: 114 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import React, { FC, Fragment, useCallback, useMemo, useState } from 'react';
3+
import React, { FC, useCallback, useMemo, useState } from 'react';
44
import { Integrations } from '@gitroom/frontend/components/launches/calendar.context';
55
import dayjs from 'dayjs';
66
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
@@ -16,14 +16,24 @@ import { sortBy } from 'lodash';
1616
import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload';
1717
import { useT } from '@gitroom/react/translation/get.transation.service.client';
1818
import { 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+
1926
dayjs.extend(utc);
2027
dayjs.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+
2737
export 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

Comments
 (0)