diff --git a/src/components/form/NewProjectForm.tsx b/src/components/form/NewProjectForm.tsx index 61542b8..3ee61e7 100644 --- a/src/components/form/NewProjectForm.tsx +++ b/src/components/form/NewProjectForm.tsx @@ -24,10 +24,14 @@ interface NewProjectFormProp { alertMessageState: ModalState; onCancel$?: QRL; allowNewEntry?: boolean; + preSelectedData: Signal<{ + customer?: string; + project?: string; + }>; } export const NewProjectForm = component$( - ({ timeEntry, alertMessageState, onCancel$, allowNewEntry }) => { + ({ timeEntry, alertMessageState, onCancel$, allowNewEntry, preSelectedData }) => { const { dataCustomersSig, dataProjectsSig, @@ -61,7 +65,9 @@ export const NewProjectForm = component$( onCancel$ && onCancel$(); }); - const _projectSelected = useSignal(projectSelected.value.name); + const _projectSelected = useSignal( + preSelectedData.value.project ?? projectSelected.value.name + ); const _taskSelected = useSignal(taskSelected.value.name); const _projectOptions = useComputed$(() => { @@ -102,6 +108,7 @@ export const NewProjectForm = component$( useTask$(({ track }) => { track(() => projectSelected.value); _projectSelected.value = projectSelected.value.name; + _projectTypeSelected.value = projectSelected.value.type; }); useTask$(({ track }) => { @@ -109,9 +116,24 @@ export const NewProjectForm = component$( _taskSelected.value = taskSelected.value.name; }); - useVisibleTask$(({ track }) => { - track(() => projectSelected.value); - _projectTypeSelected.value = projectSelected.value.type; + useVisibleTask$(async ({ track }) => { + track(() => preSelectedData.value); + + if ( + preSelectedData.value.customer === undefined && + preSelectedData.value.project === undefined + ) { + return; + } + + if (preSelectedData.value.customer) { + customerSelected.value = preSelectedData.value.customer; + await onChangeCustomer(preSelectedData.value.customer); + } + + if (preSelectedData.value.project) { + await _onChangeProject(preSelectedData.value.project); + } }); return ( diff --git a/src/components/modals/NewTimeEntryModal.tsx b/src/components/modals/NewTimeEntryModal.tsx index 0657503..be71e85 100644 --- a/src/components/modals/NewTimeEntryModal.tsx +++ b/src/components/modals/NewTimeEntryModal.tsx @@ -1,8 +1,23 @@ -import { $, component$, Slot, useComputed$, useSignal } from '@builder.io/qwik'; +import { + $, + component$, + Signal, + Slot, + useComputed$, + useSignal, + useVisibleTask$, +} from '@builder.io/qwik'; import { t } from 'src/locale/labels'; import { getIcon } from '../icons'; -export const NewTimeEntryModal = component$(() => { +interface NewTimeEntryModalProp { + preSelectedData?: Signal<{ + customer?: string; + project?: string; + }>; +} + +export const NewTimeEntryModal = component$(({ preSelectedData }) => { const modalVisible = useSignal(false); const modalToggle = $(() => { @@ -15,6 +30,18 @@ export const NewTimeEntryModal = component$(() => { }; }); + useVisibleTask$(({ track }) => { + track(() => preSelectedData?.value); + + if ( + preSelectedData && + (preSelectedData.value.customer !== undefined || + preSelectedData.value.project !== undefined) + ) { + modalVisible.value = true; + } + }); + return ( <>
diff --git a/src/components/registry/CustomerAccordion.tsx b/src/components/registry/CustomerAccordion.tsx index 3a0df3a..7ba2df3 100644 --- a/src/components/registry/CustomerAccordion.tsx +++ b/src/components/registry/CustomerAccordion.tsx @@ -1,4 +1,13 @@ -import { $, component$, QRL, useSignal, useStore } from '@builder.io/qwik'; +import { + $, + component$, + QRL, + Signal, + sync$, + useSignal, + useStore, + useVisibleTask$, +} from '@builder.io/qwik'; import { Customer } from '@models/customer'; import { ModalState } from '@models/modalState'; import { useCustomers } from 'src/hooks/useCustomers'; @@ -16,112 +25,153 @@ import { ProjectAccordion } from './ProjectAccordion'; interface CustomerAccordionProps { customer: Customer; refresh?: QRL; + preSelectedData: Signal<{ + customer?: string; + project?: string; + }>; + preOpenData: Signal<{ + customer?: string; + project?: string; + beenOpened?: boolean; + }>; } -export const CustomerAccordion = component$(({ customer, refresh }) => { - const { addEvent } = useNotification(); - const { updateCustomer, removeCustomer } = useCustomers(); - const { projects, fetchProjects, isLoading } = useProjects(); - const visibleBody = useSignal(false); - - const name = useSignal(customer); - - const initFormSignals = $(() => { - name.value = customer; - }); - - const customerModalState = useStore({ - title: t('EDIT_CUSTOMER'), - onCancel$: $(() => { - initFormSignals(); - }), - onConfirm$: $(async () => { - if (await updateCustomer(customer, name.value)) { - refresh && refresh(); - addEvent({ - type: 'success', - message: t('EDIT_CUSTOMER_SUCCESS_MESSAGE'), - autoclose: true, - }); - } - }), - cancelLabel: t('ACTION_CANCEL'), - confirmLabel: t('ACTION_CONFIRM'), - }); - - const customerDeleteModalState = useStore({ - title: t('CUSTOMER_DELETE_TITLE'), - message: tt('CUSTOMER_DELETE_MESSAGE', { - name: customer, - }), - onConfirm$: $(async () => { - if (await removeCustomer(customer)) { - refresh && refresh(); - addEvent({ - type: 'success', - message: t('DELETE_CUSTOMER_SUCCESS_MESSAGE'), - autoclose: true, - }); +export const CustomerAccordion = component$( + ({ customer, refresh, preSelectedData, preOpenData }) => { + const { addEvent } = useNotification(); + const { updateCustomer, removeCustomer } = useCustomers(); + const { projects, fetchProjects, isLoading } = useProjects(); + const visibleBody = useSignal(false); + + const name = useSignal(customer); + + const initFormSignals = $(() => { + name.value = customer; + }); + + const customerModalState = useStore({ + title: t('EDIT_CUSTOMER'), + onCancel$: $(() => { + initFormSignals(); + }), + onConfirm$: $(async () => { + if (await updateCustomer(customer, name.value)) { + refresh && refresh(); + addEvent({ + type: 'success', + message: t('EDIT_CUSTOMER_SUCCESS_MESSAGE'), + autoclose: true, + }); + } + }), + cancelLabel: t('ACTION_CANCEL'), + confirmLabel: t('ACTION_CONFIRM'), + }); + + const customerDeleteModalState = useStore({ + title: t('CUSTOMER_DELETE_TITLE'), + message: tt('CUSTOMER_DELETE_MESSAGE', { + name: customer, + }), + onConfirm$: $(async () => { + if (await removeCustomer(customer)) { + refresh && refresh(); + addEvent({ + type: 'success', + message: t('DELETE_CUSTOMER_SUCCESS_MESSAGE'), + autoclose: true, + }); + } + }), + cancelLabel: t('ACTION_CANCEL'), + confirmLabel: t('ACTION_CONFIRM'), + }); + + const openBody = sync$(async () => { + visibleBody.value = !visibleBody.value; + if (visibleBody.value) await fetchProjects(customer); + }); + + const selectPreselected = $(() => { + preSelectedData.value = { + customer: customer, + project: undefined, + }; + }); + + useVisibleTask$(async () => { + if (preOpenData.value.customer === customer && !preOpenData.value.beenOpened) { + await openBody().then(() => + preOpenData.value.project === undefined + ? (preOpenData.value.beenOpened = true) + : null + ); } - }), - cancelLabel: t('ACTION_CANCEL'), - confirmLabel: t('ACTION_CONFIRM'), - }); - - const openBody = $(() => { - visibleBody.value = !visibleBody.value; - if (visibleBody.value) fetchProjects(customer); - }); - - return ( - <> -

-
-
- {customer} {isLoading.value && } -
-
- - - - - + }); + + return ( + <> +

+
+
+ {customer} {isLoading.value && } +
+
+ + + + + + + +
-

-

- - {/* accordion body */} -
-
-
- {projects.value.map((project) => { - return ( - - ); - })} + + + {/* accordion body */} +
+
+
+ {projects.value.map((project) => { + return ( + + ); + })} +
-
- - - + + + - - - ); -}); + + + ); + } +); diff --git a/src/components/registry/ProjectAccordion.tsx b/src/components/registry/ProjectAccordion.tsx index 0ef878c..9a7ad68 100644 --- a/src/components/registry/ProjectAccordion.tsx +++ b/src/components/registry/ProjectAccordion.tsx @@ -1,4 +1,13 @@ -import { $, component$, QRL, useSignal, useStore } from '@builder.io/qwik'; +import { + $, + component$, + QRL, + Signal, + sync$, + useSignal, + useStore, + useVisibleTask$, +} from '@builder.io/qwik'; import { Customer } from '@models/customer'; import { ModalState } from '@models/modalState'; import { Project } from '@models/project'; @@ -18,10 +27,19 @@ interface ProjectAccordionProps { customer: Customer; project: Project; refresh?: QRL; + preSelectedData: Signal<{ + customer?: string; + project?: string; + }>; + preOpenData: Signal<{ + customer?: string; + project?: string; + beenOpened?: boolean; + }>; } export const ProjectAccordion = component$( - ({ customer, project, refresh }) => { + ({ customer, project, refresh, preSelectedData, preOpenData }) => { const visibleBody = useSignal(false); const { addEvent } = useNotification(); const { tasks, fetchTasks, isLoading } = useTasks(); @@ -79,15 +97,35 @@ export const ProjectAccordion = component$( confirmLabel: t('ACTION_CONFIRM'), }); - const openBody = $(() => { + const openBody = sync$(async () => { visibleBody.value = !visibleBody.value; - if (visibleBody.value) fetchTasks(customer, project); + if (visibleBody.value) await fetchTasks(customer, project); + }); + + const selectPreselected = $(() => { + preSelectedData.value = { + customer: customer, + project: project.name, + }; + }); + + useVisibleTask$(async () => { + if (preOpenData.value.project === project.name && !preOpenData.value.beenOpened) { + await openBody().then(() => (preOpenData.value.beenOpened = true)); + } }); return ( <>

-
+
{project.name}{' '} {isLoading.value && } @@ -107,6 +145,9 @@ export const ProjectAccordion = component$( > {getIcon('Edit')} +
diff --git a/src/components/timesheet/TimeSheetTable.tsx b/src/components/timesheet/TimeSheetTable.tsx index 8787167..f86641e 100644 --- a/src/components/timesheet/TimeSheetTable.tsx +++ b/src/components/timesheet/TimeSheetTable.tsx @@ -86,6 +86,11 @@ export const TimeSheetTable = component$( entryExists.endHour !== '00:00' ) { return false; + } else if ( + startHour !== entryExists.startHour || + endHour !== entryExists.endHour + ) { + return false; } // Return true if the new hours are the same as the existing hours return entryExists.hours === hours; diff --git a/src/hooks/timesheet/useNewTimeEntry.ts b/src/hooks/timesheet/useNewTimeEntry.ts index 09f4cc6..8198956 100644 --- a/src/hooks/timesheet/useNewTimeEntry.ts +++ b/src/hooks/timesheet/useNewTimeEntry.ts @@ -9,6 +9,7 @@ import { useTask$, } from '@builder.io/qwik'; import { ModalState } from '@models/modalState'; +import { getCurrentRoute, navigateTo } from 'src/router'; import { INIT_PROJECT_VALUE, INIT_TASK_VALUE } from 'src/utils/constants'; import { t, tt } from '../../locale/labels'; import { Customer } from '../../models/customer'; @@ -53,7 +54,9 @@ export const useNewTimeEntry = ( if (customer !== undefined) { if (customer === '' || dataCustomersSig.value.includes(customer)) { projectTypeEnabled.newCustomer = false; - projectSelected.value = INIT_PROJECT_VALUE; + if (project === undefined || project.type === '') { + projectSelected.value = INIT_PROJECT_VALUE; + } } else { projectTypeEnabled.newCustomer = true; } @@ -68,6 +71,9 @@ export const useNewTimeEntry = ( }); const onChangeCustomer = $(async (value: string) => { + if (value !== '' && value === customerSelected.value && projectSelected.value.name !== '') { + return; + } projectSelected.value = INIT_PROJECT_VALUE; taskSelected.value = INIT_TASK_VALUE; if (value != '') { @@ -84,9 +90,9 @@ export const useNewTimeEntry = ( }); const onChangeProject = $(async (value: Project) => { - if (customerSelected.value != '') { + if (customerSelected.value !== '') { taskSelected.value = INIT_TASK_VALUE; - if (value.name != '') { + if (value.name !== '') { dataTasksSign.value = await getTasks(customerSelected.value, value); taskEnableSig.value = true; } else { @@ -133,25 +139,38 @@ export const useNewTimeEntry = ( type: 'success', message: tt('INSERT_NEW_PROJECT_SUCCESS_MESSAGE', { customer: customerSelected.value, - project: projectSelected.value?.name ?? '', + project: projectSelected.value.name ?? '', task: taskSelected.value.name, }), autoclose: true, }); + if (allowNewEntry) { + if (getCurrentRoute() === 'registry') { + navigateTo('registry', { + customer: customerSelected.value, + project: projectSelected.value.name, + }); + } + } + clearForm(); closeForm && closeForm(); } }); const newEntityExist = (): boolean => { - return Boolean( - dataCustomersSig.value.find((customer) => customer === customerSelected.value) && - dataProjectsSig.value.find( - (project) => project.name === projectSelected.value.name - ) && - dataTasksSign.value.find((task) => task.name === taskSelected.value.name) + const dataCustomerExist = dataCustomersSig.value.find( + (customer) => customer === customerSelected.value ); + const dataProjectExist = dataProjectsSig.value.find( + (project) => project.name === projectSelected.value.name + ); + const dataTaskExist = dataTasksSign.value.find( + (task) => task.name === taskSelected.value.name + ); + + return Boolean(dataCustomerExist && dataProjectExist && dataTaskExist); }; const showAlert = (props: ModalState) => { diff --git a/src/pages/Registry.tsx b/src/pages/Registry.tsx index 15ff0c7..f538038 100644 --- a/src/pages/Registry.tsx +++ b/src/pages/Registry.tsx @@ -32,6 +32,17 @@ export const Registry = component$(() => { await fetchCustomers(); }); + const preselectedDataRegistry = useSignal<{ + customer?: string; + project?: string; + }>({}); + + const preOpenDataRegistry = useSignal<{ + customer?: string; + project?: string; + beenOpened?: boolean; + }>({}); + useVisibleTask$(({ track }) => { track(() => isLoading.value); appStore.isLoading = isLoading.value; @@ -42,6 +53,15 @@ export const Registry = component$(() => { await fetchCustomers(); }); + useVisibleTask$(async () => { + const params = new URL(location.href).searchParams; + preOpenDataRegistry.value = { + customer: params.get('customer') ?? undefined, + project: params.get('project') ?? undefined, + beenOpened: false, + }; + }); + return ( <>
@@ -50,19 +70,28 @@ export const Registry = component$(() => { {t('REGISTRY_PAGE_TITLE')}

- +
{customers.value.map((customer) => ( - + ))}
diff --git a/src/router.tsx b/src/router.tsx index c7615a8..c956acc 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -23,12 +23,17 @@ export const routes = { chartpreview: , }; -export const navigateTo = (route: Route): void => { - window.history.pushState({}, '', `/${route}`); +export const navigateTo = (route: Route, params?: Record): void => { + const url = `/${route}${params ? `?${new URLSearchParams(params).toString()}` : ''}`; + if (getCurrentRoute() === route) { + window.location.search = params ? `?${new URLSearchParams(params).toString()}` : ''; + } else { + window.history.pushState({}, '', url); + } dispatchEvent(new PopStateEvent('popstate')); }; -const getCurrentRoute = (): Route => window.location.pathname.replaceAll('/', '') as Route; +export const getCurrentRoute = (): Route => window.location.pathname.replaceAll('/', '') as Route; export const useRouter = (defaultRoute: Route = 'auth'): Signal => { const currentRouteSignal = useSignal(defaultRoute);