From 7d9059fdfaaf685c688dd4d7fae90e1e60a5f5a6 Mon Sep 17 00:00:00 2001 From: "Thierry CH." Date: Tue, 30 Jul 2024 23:45:30 +0200 Subject: [PATCH] feat: add a select with predefined data | add manual time reasons (#2807) * feat: add a select with predefined data | add manual time reasons * feat: add predefined reson to the select input --- apps/web/app/[locale]/page-component.tsx | 4 - apps/web/app/constants.ts | 13 +- .../interfaces/timer/IManualTimeReasons.ts | 7 + .../manual-time/add-manual-time-modal.tsx | 290 +++++++++++++++++ apps/web/lib/features/task/task-filters.tsx | 300 ++---------------- apps/web/locales/ar.json | 11 + apps/web/locales/bg.json | 11 + apps/web/locales/de.json | 11 + apps/web/locales/en.json | 11 + apps/web/locales/es.json | 11 + apps/web/locales/fr.json | 11 + apps/web/locales/he.json | 11 + apps/web/locales/it.json | 11 + apps/web/locales/nl.json | 11 + apps/web/locales/pl.json | 11 + apps/web/locales/pt.json | 11 + apps/web/locales/ru.json | 11 + apps/web/locales/zh.json | 11 + 18 files changed, 477 insertions(+), 280 deletions(-) create mode 100644 apps/web/app/interfaces/timer/IManualTimeReasons.ts create mode 100644 apps/web/lib/features/manual-time/add-manual-time-modal.tsx diff --git a/apps/web/app/[locale]/page-component.tsx b/apps/web/app/[locale]/page-component.tsx index f0ee73e76..03bc54d81 100644 --- a/apps/web/app/[locale]/page-component.tsx +++ b/apps/web/app/[locale]/page-component.tsx @@ -52,9 +52,6 @@ function MainPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [path, setView]); - - - React.useEffect(() => { window && window?.localStorage.getItem('conf-fullWidth-mode'); setFullWidth(JSON.parse(window?.localStorage.getItem('conf-fullWidth-mode') || 'true')); @@ -65,7 +62,6 @@ function MainPage() { } return ( <> -
{/*
*/} diff --git a/apps/web/app/constants.ts b/apps/web/app/constants.ts index d58c076eb..ca1b52578 100644 --- a/apps/web/app/constants.ts +++ b/apps/web/app/constants.ts @@ -2,8 +2,8 @@ import { JitsuOptions } from '@jitsu/jitsu-react/dist/useJitsu'; import { I_SMTPRequest } from './interfaces/ISmtp'; import { getNextPublicEnv } from './env'; import enLanguage from '../locales/en.json'; -// import { } from 'country-flag-icons/react/3x2' import { BG, CN, DE, ES, FR, IS, IT, NL, PL, PT, RU, SA, US } from 'country-flag-icons/react/1x1'; +import { ManualTimeReasons } from './interfaces/timer/IManualTimeReasons'; export const API_BASE_URL = '/api'; export const DEFAULT_APP_PATH = '/auth/passcode'; export const DEFAULT_MAIN_PATH = '/'; @@ -298,3 +298,14 @@ export const SLACK_CLIENT_SECRET = process.env.SLACK_CLIENT_SECRET; export const TWITTER_CLIENT_ID = process.env.TWITTER_CLIENT_ID; export const TWITTER_CLIENT_SECRET = process.env.TWITTER_CLIENT_SECRET; + +// Add manual timer reason + +export const manualTimeReasons: ManualTimeReasons[] = [ + 'LOST_ELECTRICITY', + 'LOST_INTERNET', + 'FORGOT_TO_START_TIMER', + 'ERROR', + 'UNPLANNED_WORK', + 'TESTED_TIMER' +]; diff --git a/apps/web/app/interfaces/timer/IManualTimeReasons.ts b/apps/web/app/interfaces/timer/IManualTimeReasons.ts new file mode 100644 index 000000000..0567ccb8c --- /dev/null +++ b/apps/web/app/interfaces/timer/IManualTimeReasons.ts @@ -0,0 +1,7 @@ +export type ManualTimeReasons = + | 'LOST_ELECTRICITY' + | 'LOST_INTERNET' + | 'FORGOT_TO_START_TIMER' + | 'ERROR' + | 'UNPLANNED_WORK' + | 'TESTED_TIMER'; diff --git a/apps/web/lib/features/manual-time/add-manual-time-modal.tsx b/apps/web/lib/features/manual-time/add-manual-time-modal.tsx new file mode 100644 index 000000000..61b3271d8 --- /dev/null +++ b/apps/web/lib/features/manual-time/add-manual-time-modal.tsx @@ -0,0 +1,290 @@ +import '../../../styles/style.css'; +import { useOrganizationTeams, useTeamTasks } from '@app/hooks'; +import api from '@app/services/client/axios'; +import { clsxm } from '@app/utils'; +import { DatePicker } from '@components/ui/DatePicker'; +import { PencilSquareIcon } from '@heroicons/react/20/solid'; +import { format } from 'date-fns'; +import { useState, useEffect, FormEvent, useCallback } from 'react'; +import { Button, SelectItems, Modal } from 'lib/components'; +import { FaRegCalendarAlt } from 'react-icons/fa'; +import { HiMiniClock } from 'react-icons/hi2'; +import { manualTimeReasons } from '@app/constants'; +import { useTranslations } from 'next-intl'; + +interface IAddManualTimeModalProps { + isOpen: boolean; + closeModal: () => void; +} + +export function AddManualTimeModal(props: IAddManualTimeModalProps) { + const { closeModal, isOpen } = props; + const t = useTranslations(); + const [isBillable, setIsBillable] = useState(false); + const [description, setDescription] = useState(''); + const [reason, setReason] = useState(''); + const [errorMsg, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [endTime, setEndTime] = useState(''); + const [date, setDate] = useState(new Date()); + const [startTime, setStartTime] = useState(''); + const [teamId, setTeamId] = useState(''); + const [taskId, setTaskId] = useState(''); + const [timeDifference, setTimeDifference] = useState(''); + const { activeTeamTask, tasks, activeTeamId } = useTeamTasks(); + const { teams } = useOrganizationTeams(); + + useEffect(() => { + const now = new Date(); + const currentTime = now.toTimeString().slice(0, 5); + + setDate(now); + setStartTime(currentTime); + setEndTime(currentTime); + }, []); + + const handleSubmit = useCallback( + (e: FormEvent) => { + e.preventDefault(); + + const timeObject = { + date, + isBillable, + startTime, + endTime, + teamId, + taskId, + description, + reason, + timeDifference + }; + + if (date && startTime && endTime && teamId && taskId) { + setLoading(true); + setError(''); + const postData = async () => { + try { + const response = await api.post('/add_time', timeObject); + if (response.data.message) { + setLoading(false); + closeModal(); + } + } catch (err) { + setError('Failed to post data'); + setLoading(false); + } + }; + + postData(); + } else { + setError(`Please complete all required fields with a ${'*'}`); + } + }, + [closeModal, date, description, endTime, isBillable, reason, startTime, taskId, teamId, timeDifference] + ); + + const calculateTimeDifference = useCallback(() => { + const [startHours, startMinutes] = startTime.split(':').map(Number); + const [endHours, endMinutes] = endTime.split(':').map(Number); + + const startTotalMinutes = startHours * 60 + startMinutes; + const endTotalMinutes = endHours * 60 + endMinutes; + + const diffMinutes = endTotalMinutes - startTotalMinutes; + if (diffMinutes < 0) { + return; + } + + const hours = Math.floor(diffMinutes / 60); + const minutes = diffMinutes % 60; + setTimeDifference(`${String(hours).padStart(2, '0')}h ${String(minutes).padStart(2, '0')}m`); + }, [endTime, startTime]); + + useEffect(() => { + calculateTimeDifference(); + }, [calculateTimeDifference, endTime, startTime]); + + useEffect(() => { + if (activeTeamTask) { + setTaskId(activeTeamTask.id); + } + if (activeTeamId) { + setTeamId(activeTeamId); + } + }, [activeTeamTask, activeTeamId]); + + return ( + +
+
+ +
+ + {date ? ( +
+ + {format(date, 'PPP')} +
+ ) : ( + + )} +
+ } + selected={date} + onSelect={(value) => { + value && setDate(value); + }} + mode={'single'} + /> +
+
+ +
+ +
setIsBillable(!isBillable)} + style={ + isBillable + ? { background: 'linear-gradient(to right, #9d91efb7, #8a7bedb7)' } + : { background: '#6c57f4b7' } + } + > +
+
+
+
+
+ + setStartTime(e.target.value)} + className="w-full p-2 border font-bold border-slate-100 dark:border-slate-600 dark:bg-dark--theme-light rounded-md" + required + /> +
+ +
+ + + setEndTime(e.target.value)} + className="w-full p-2 border font-bold border-slate-100 dark:border-slate-600 dark:bg-dark--theme-light rounded-md" + required + /> +
+
+ +
+ +
+
+ +
+ {timeDifference} +
+
+ +
+ + setTeamId(value)} + itemId={(team) => team.id} + itemToString={(team) => team.name} + triggerClassName="border-slate-100 dark:border-slate-600" + /> +
+ +
+ + setTaskId(value)} + itemId={(task) => task.id} + itemToString={(task) => task.title} + triggerClassName="border-slate-100 dark:border-slate-600" + /> +
+ +
+ +