diff --git a/src/components/Datatable/DataTable.tsx b/src/components/Datatable/DataTable.tsx index e941a1fcd..deddcddf7 100644 --- a/src/components/Datatable/DataTable.tsx +++ b/src/components/Datatable/DataTable.tsx @@ -197,6 +197,7 @@ export function DataTable(props) { DataTable.defaultProps = { pagination: false, hidePaginationNoPages: true, + hideTableHeader: false, size: null, spinnerProps: { size: 30 }, diff --git a/src/components/Datatable/TableHeader.tsx b/src/components/Datatable/TableHeader.tsx index 425849fb7..bc99362d4 100644 --- a/src/components/Datatable/TableHeader.tsx +++ b/src/components/Datatable/TableHeader.tsx @@ -79,9 +79,19 @@ function TableHeaderGroup({ headerGroup }) { export default function TableHeader() { const { table: { headerGroups, page }, - props: { TableHeaderSkeletonRenderer, headerLoading, progressBarLoading }, + props: { + TableHeaderSkeletonRenderer, + headerLoading, + progressBarLoading, + hideTableHeader, + }, } = useContext(TableContext); + // Can't contiunue if the thead is disabled. + if (hideTableHeader) { + return null; + } + if (headerLoading && TableHeaderSkeletonRenderer) { return ; } diff --git a/src/components/DialogsContainer.tsx b/src/components/DialogsContainer.tsx index 10e60b3ec..39b363c7f 100644 --- a/src/components/DialogsContainer.tsx +++ b/src/components/DialogsContainer.tsx @@ -40,6 +40,11 @@ import BranchActivateDialog from '@/containers/Dialogs/BranchActivateDialog'; import WarehouseActivateDialog from '@/containers/Dialogs/WarehouseActivateDialog'; import CustomerOpeningBalanceDialog from '@/containers/Dialogs/CustomerOpeningBalanceDialog'; import VendorOpeningBalanceDialog from '@/containers/Dialogs/VendorOpeningBalanceDialog'; +import ProjectFormDialog from '@/containers/Projects/containers/ProjectFormDialog'; +import ProjectTaskFormDialog from '@/containers/Projects/containers/ProjectTaskFormDialog'; +import ProjectTimeEntryFormDialog from '@/containers/Projects/containers/ProjectTimeEntryFormDialog'; +import ProjectExpenseForm from '@/containers/Projects/containers/ProjectExpenseForm'; +import EstimatedExpenseFormDialog from '@/containers/Projects/containers/EstimatedExpenseFormDialog'; import { DialogsName } from '@/constants/dialogs'; @@ -116,6 +121,15 @@ export default function DialogsContainer() { + + + + + ); } diff --git a/src/constants/dialogs.ts b/src/constants/dialogs.ts index 0aa5c21b0..01b34d594 100644 --- a/src/constants/dialogs.ts +++ b/src/constants/dialogs.ts @@ -38,4 +38,9 @@ export enum DialogsName { WarehouseActivateForm = 'warehouse-activate', CustomerOpeningBalanceForm = 'customer-opening-balance', VendorOpeningBalanceForm = 'vendor-opening-balance', + ProjectForm = 'project-form', + ProjectTaskForm = 'project-task-form', + ProjectTimeEntryForm = 'project-time-entry-form', + ProjectExpenseForm = 'project-expense-form', + EstimateExpenseForm = 'estimate-expense-form', } diff --git a/src/constants/sidebarMenu.tsx b/src/constants/sidebarMenu.tsx index 2b38073e6..b003a90a8 100644 --- a/src/constants/sidebarMenu.tsx +++ b/src/constants/sidebarMenu.tsx @@ -538,6 +538,38 @@ export const SidebarMenu = [ }, ], }, + // --------------------- + // # Projects Management + // --------------------- + { + text: 'Projects', + type: ISidebarMenuItemType.Overlay, + overlayId: ISidebarMenuOverlayIds.Projects, + children: [ + { + text: 'Projects Management', + type: ISidebarMenuItemType.Group, + children: [ + { + text: 'Projects', + href: '/projects', + type: ISidebarMenuItemType.Link, + }, + ], + }, + { + text: , + type: ISidebarMenuItemType.Group, + children: [ + { + text: , + type: ISidebarMenuItemType.Dialog, + dialogName: 'project-form', + }, + ], + }, + ], + }, // --------------- // # Reports // --------------- diff --git a/src/containers/AlertsContainer/registered.tsx b/src/containers/AlertsContainer/registered.tsx index 6fd76562f..309a42cd1 100644 --- a/src/containers/AlertsContainer/registered.tsx +++ b/src/containers/AlertsContainer/registered.tsx @@ -23,6 +23,7 @@ import TransactionsLockingAlerts from '@/containers/TransactionsLocking/Transact import WarehousesAlerts from '@/containers/Preferences/Warehouses/WarehousesAlerts'; import WarehousesTransfersAlerts from '@/containers/WarehouseTransfers/WarehousesTransfersAlerts'; import BranchesAlerts from '@/containers/Preferences/Branches/BranchesAlerts'; +import ProjectAlerts from '@/containers/Projects/containers/ProjectAlerts'; export default [ ...AccountsAlerts, @@ -50,4 +51,5 @@ export default [ ...WarehousesAlerts, ...WarehousesTransfersAlerts, ...BranchesAlerts, + ...ProjectAlerts, ]; diff --git a/src/containers/Projects/containers/TaskFormDialog/components.tsx b/src/containers/Projects/components/ChangeTypesSelect.tsx similarity index 57% rename from src/containers/Projects/containers/TaskFormDialog/components.tsx rename to src/containers/Projects/components/ChangeTypesSelect.tsx index d5633208d..2587f59d6 100644 --- a/src/containers/Projects/containers/TaskFormDialog/components.tsx +++ b/src/containers/Projects/components/ChangeTypesSelect.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { MenuItem, Button } from '@blueprintjs/core'; -import { FSelect } from 'components'; +import { FSelect } from '@/components'; /** * @@ -8,7 +8,7 @@ import { FSelect } from 'components'; * @param {*} param1 * @returns */ -const taskModalChargeRenderer = (item, { handleClick, modifiers, query }) => { +const chargeTypeItemRenderer = (item, { handleClick, modifiers, query }) => { return ( { ); }; -const taskModalChargeSelectProps = { - itemRenderer: taskModalChargeRenderer, +const chargeTypeSelectProps = { + itemRenderer: chargeTypeItemRenderer, valueAccessor: 'value', labelAccessor: 'name', }; @@ -30,22 +30,21 @@ const taskModalChargeSelectProps = { * @param param0 * @returns */ -export function TaskModalChargeSelect({ items, ...rest }) { +export function ChangeTypesSelect({ items, ...rest }) { return ( ); } - /** * * @param param0 * @returns */ -function TaskModalChargeSelectButton({ label }) { +function ChargeTypeSelectButton({ label }) { return + + + + ); +} + +export default compose(withDialogActions)(EstimatedExpenseFormFloatingActions); diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormProvider.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormProvider.tsx new file mode 100644 index 000000000..0ec265aea --- /dev/null +++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormProvider.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { DialogContent } from '@/components'; + +const EstimatedExpenseFormContext = React.createContext(); + +/** + * Estimated expense form provider. + * @returns + */ +function EstimatedExpenseFormProvider({ + //#OwnProps + dialogName, + estimatedExpenseId, + ...props +}) { + // state provider. + const provider = { + dialogName, + }; + return ( + + + + ); +} + +const useEstimatedExpenseFormContext = () => + React.useContext(EstimatedExpenseFormContext); + +export { EstimatedExpenseFormProvider, useEstimatedExpenseFormContext }; diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/index.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/index.tsx new file mode 100644 index 000000000..38a3a988a --- /dev/null +++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/index.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Dialog, DialogSuspense, FormattedMessage as T } from '@/components'; +import withDialogRedux from '@/components/DialogReduxConnect'; +import { compose } from '@/utils'; + +const EstimatedExpenseFormDialogContent = React.lazy( + () => import('./EstimatedExpenseFormDialogContent'), +); + +/** + * Estimate expense form dialog. + * @returns + */ +function EstimatedExpenseFormDialog({ + dialogName, + payload: { projectId = null }, + isOpen, +}) { + return ( + } + isOpen={isOpen} + autoFocus={true} + canEscapeKeyClose={true} + style={{ width: '400px' }} + > + + + + + ); +} + +export default compose(withDialogRedux())(EstimatedExpenseFormDialog); + +const EstimateExpenseFormDialogRoot = styled(Dialog)` + .bp3-dialog-body { + .bp3-form-group { + margin-bottom: 15px; + + label.bp3-label { + margin-bottom: 3px; + font-size: 13px; + } + } + } + .bp3-dialog-footer { + padding-top: 10px; + } +`; diff --git a/src/containers/Projects/containers/ProjectAlerts/ProjectDeleteAlert.tsx b/src/containers/Projects/containers/ProjectAlerts/ProjectDeleteAlert.tsx new file mode 100644 index 000000000..d5544f186 --- /dev/null +++ b/src/containers/Projects/containers/ProjectAlerts/ProjectDeleteAlert.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from '@/components'; +import { Intent, Alert } from '@blueprintjs/core'; +import { AppToaster } from '@/components'; +import { useDeleteProject } from '../../hooks'; + +import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect'; +import withAlertActions from '@/containers/Alert/withAlertActions'; + +import { compose } from '@/utils'; + +/** + * Project delete alert. + */ +function ProjectDeleteAlert({ + name, + + // #withAlertStoreConnect + isOpen, + payload: { projectId }, + + // #withAlertActions + closeAlert, + + // #withDrawerActions + closeDrawer, +}) { + const { mutateAsync: deleteProjectMutate, isLoading } = useDeleteProject(); + + // handle cancel delete project alert. + const handleCancelDeleteAlert = () => { + closeAlert(name); + }; + + // handleConfirm delete project + const handleConfirmProjectDelete = () => { + deleteProjectMutate(projectId) + .then(() => { + AppToaster.show({ + message: intl.get('projects.alert.delete_message'), + intent: Intent.SUCCESS, + }); + }) + .catch( + ({ + response: { + data: { errors }, + }, + }) => {}, + ) + .finally(() => { + closeAlert(name); + }); + }; + + return ( + } + confirmButtonText={} + icon="trash" + intent={Intent.DANGER} + isOpen={isOpen} + onCancel={handleCancelDeleteAlert} + onConfirm={handleConfirmProjectDelete} + loading={isLoading} + > +

+ +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(ProjectDeleteAlert); diff --git a/src/containers/Projects/containers/ProjectAlerts/index.ts b/src/containers/Projects/containers/ProjectAlerts/index.ts new file mode 100644 index 000000000..8900c1269 --- /dev/null +++ b/src/containers/Projects/containers/ProjectAlerts/index.ts @@ -0,0 +1,8 @@ +import React from 'react'; + +const ProjectDeleteAlert = React.lazy(() => import('./ProjectDeleteAlert')); + +/** + * Project alerts. + */ +export default [{ name: 'project-delete', component: ProjectDeleteAlert }]; diff --git a/src/containers/Projects/containers/ProjectDetails/ProjectDetailActionsBar.tsx b/src/containers/Projects/containers/ProjectDetails/ProjectDetailActionsBar.tsx index 09608d960..46e0d67fa 100644 --- a/src/containers/Projects/containers/ProjectDetails/ProjectDetailActionsBar.tsx +++ b/src/containers/Projects/containers/ProjectDetails/ProjectDetailActionsBar.tsx @@ -1,4 +1,3 @@ -// @ts-nocheck import React from 'react'; import { useHistory } from 'react-router-dom'; import { @@ -8,18 +7,19 @@ import { NavbarGroup, Alignment, } from '@blueprintjs/core'; -import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import { Icon, FormattedMessage as T, DashboardRowsHeightButton, -} from 'components'; -import { TransactionSelect } from './components'; -import withSettings from '../../../Settings/withSettings'; -import withSettingsActions from '../../../Settings/withSettingsActions'; -import withDialogActions from 'containers/Dialog/withDialogActions'; + DashboardActionsBar, +} from '@/components'; +import { ProjectTransactionsSelect } from './components'; +import withSettings from '@/containers/Settings/withSettings'; +import withSettingsActions from '@/containers/Settings/withSettingsActions'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { projectTranslations } from './common'; import { useProjectDetailContext } from './ProjectDetailProvider'; -import { compose } from 'utils'; +import { compose } from '@/utils'; /** * Project detail actions bar. @@ -40,7 +40,13 @@ function ProjectDetailActionsBar({ // Handle new transaction button click. const handleNewTransactionBtnClick = ({ path }) => { - history.push(`/${path}`); + switch (path) { + case 'expense': + openDialog('project-expense-form', { projectId }); + break; + case 'estimated_expense': + openDialog('estimated-expense-form', { projectId }); + } }; const handleEditProjectBtnClick = () => { @@ -50,11 +56,13 @@ function ProjectDetailActionsBar({ }; // Handle table row size change. const handleTableRowSizeChange = (size) => { - addSetting('timesheets', 'tableSize', size); + addSetting('timesheets', 'tableSize', size) && + addSetting('sales', 'tableSize', size) && + addSetting('purchases', 'tableSize', size); }; const handleTimeEntryBtnClick = () => { - openDialog('time-entry-form', { + openDialog('project-time-entry-form', { projectId, }); }; @@ -65,11 +73,8 @@ function ProjectDetailActionsBar({ return ( - + + + + ); +} + +export default compose(withDialogActions)(ProjectExpneseFormFloatingActions); diff --git a/src/containers/Projects/containers/ProjectExpenseForm/index.tsx b/src/containers/Projects/containers/ProjectExpenseForm/index.tsx new file mode 100644 index 000000000..b11f96c2c --- /dev/null +++ b/src/containers/Projects/containers/ProjectExpenseForm/index.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Dialog, DialogSuspense, FormattedMessage as T } from '@/components'; +import withDialogRedux from '@/components/DialogReduxConnect'; +import { compose } from '@/utils'; + +const ProjectExpenseFormeDialogContent = React.lazy( + () => import('./ProjectExpenseFormDialogContent'), +); + +/** + * Project expense form dialog. + * @returns + */ +function ProjectExpenseFormDialog({ + dialogName, + payload: { projectId = null }, + isOpen, +}) { + return ( + } + isOpen={isOpen} + autoFocus={true} + canEscapeKeyClose={true} + style={{ width: '400px' }} + > + + + + + ); +} + +export default compose(withDialogRedux())(ProjectExpenseFormDialog); + +const ProjectExpenseFormDialogRoot = styled(Dialog)` + .bp3-dialog-body { + .bp3-form-group { + margin-bottom: 15px; + + label.bp3-label { + margin-bottom: 3px; + font-size: 13px; + } + } + } + .bp3-dialog-footer { + padding-top: 10px; + } +`; diff --git a/src/containers/Projects/containers/ProjectFormDialog/ProjectForm.schema.tsx b/src/containers/Projects/containers/ProjectFormDialog/ProjectForm.schema.tsx index 530fec56b..03e6ff34b 100644 --- a/src/containers/Projects/containers/ProjectFormDialog/ProjectForm.schema.tsx +++ b/src/containers/Projects/containers/ProjectFormDialog/ProjectForm.schema.tsx @@ -1,19 +1,18 @@ import * as Yup from 'yup'; import intl from 'react-intl-universal'; -import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ - contact: Yup.string().label(intl.get('project.schema.label.contact')), - projectName: Yup.string() + contact_id: Yup.string().label(intl.get('project.schema.label.contact')), + name: Yup.string() .label(intl.get('project.schema.label.project_name')) .required(), - projectDeadline: Yup.date() + deadline: Yup.date() .label(intl.get('project.schema.label.deadline')) .required(), - projectState: Yup.boolean().label( + published: Yup.boolean().label( intl.get('project.schema.label.project_state'), ), - projectCost: Yup.number().label( + cost_estimate: Yup.number().label( intl.get('project.schema.label.project_cost'), ), }); diff --git a/src/containers/Projects/containers/ProjectFormDialog/ProjectForm.tsx b/src/containers/Projects/containers/ProjectFormDialog/ProjectForm.tsx index af039a715..67bc43e0d 100644 --- a/src/containers/Projects/containers/ProjectFormDialog/ProjectForm.tsx +++ b/src/containers/Projects/containers/ProjectFormDialog/ProjectForm.tsx @@ -1,22 +1,22 @@ -// @ts-nocheck import React from 'react'; import moment from 'moment'; import intl from 'react-intl-universal'; import { Formik } from 'formik'; -import { AppToaster } from 'components'; +import { Intent } from '@blueprintjs/core'; +import { AppToaster } from '@/components'; import ProjectFormContent from './ProjectFormContent'; import { CreateProjectFormSchema } from './ProjectForm.schema'; import { useProjectFormContext } from './ProjectFormProvider'; -import withDialogActions from 'containers/Dialog/withDialogActions'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; -import { compose } from 'utils'; +import { compose, transformToForm } from '@/utils'; const defaultInitialValues = { - contact: '', - projectName: '', - projectDeadline: moment(new Date()).format('YYYY-MM-DD'), - projectState: false, - projectCost: '', + contact_id: '', + name: '', + deadline: moment(new Date()).format('YYYY-MM-DD'), + published: false, + cost_estimate: '', }; /** @@ -28,20 +28,37 @@ function ProjectForm({ closeDialog, }) { // project form dialog context. - const { dialogName } = useProjectFormContext(); + const { + dialogName, + project, + isNewMode, + projectId, + createProjectMutate, + editProjectMutate, + } = useProjectFormContext(); // Initial form values const initialValues = { ...defaultInitialValues, + ...transformToForm(project, defaultInitialValues), }; // Handles the form submit. const handleFormSubmit = (values, { setSubmitting, setErrors }) => { - const form = {}; + setSubmitting(true); + const form = { ...values }; // Handle request response success. const onSuccess = (response) => { - AppToaster.show({}); + AppToaster.show({ + message: intl.get( + isNewMode + ? 'projects.dialog.success_message' + : 'projects.dialog.edit_success_message', + ), + + intent: Intent.SUCCESS, + }); closeDialog(dialogName); }; @@ -53,6 +70,12 @@ function ProjectForm({ }) => { setSubmitting(false); }; + + if (isNewMode) { + createProjectMutate(form).then(onSuccess).catch(onError); + } else { + editProjectMutate([projectId, form]).then(onSuccess).catch(onError); + } }; return ( diff --git a/src/containers/Projects/containers/ProjectFormDialog/ProjectFormFields.tsx b/src/containers/Projects/containers/ProjectFormDialog/ProjectFormFields.tsx index 239530454..bc1f47f25 100644 --- a/src/containers/Projects/containers/ProjectFormDialog/ProjectFormFields.tsx +++ b/src/containers/Projects/containers/ProjectFormDialog/ProjectFormFields.tsx @@ -1,11 +1,10 @@ -// @ts-nocheck import React from 'react'; import intl from 'react-intl-universal'; import { useFormikContext } from 'formik'; import { Classes, Position, FormGroup, ControlGroup } from '@blueprintjs/core'; import { FastField } from 'formik'; -import { CLASSES } from 'common/classes'; +import { CLASSES } from '@/constants/classes'; import classNames from 'classnames'; import { FFormGroup, @@ -17,13 +16,13 @@ import { FormattedMessage as T, FieldRequiredHint, CustomerSelectField, -} from 'components'; +} from '@/components'; import { inputIntent, momentFormatter, tansformDateValue, handleDateChange, -} from 'utils'; +} from '@/utils'; import { useProjectFormContext } from './ProjectFormProvider'; /** @@ -40,7 +39,7 @@ function ProjectFormFields() { return (
{/*------------ Contact -----------*/} - + {({ form, field: { value }, meta: { error, touched } }) => ( { - form.setFieldValue('contact', customer.id); + form.setFieldValue('contact_id', customer.id); }} allowCreate={true} popoverFill={true} @@ -64,19 +63,20 @@ function ProjectFormFields() { {/*------------ Project Name -----------*/} } > - + {/*------------ DeadLine -----------*/} date.toLocaleString()} popoverProps={{ position: Position.BOTTOM, @@ -86,22 +86,23 @@ function ProjectFormFields() { {/*------------ CheckBox -----------*/} - + {/*------------ Cost Estimate -----------*/} } > diff --git a/src/containers/Projects/containers/ProjectFormDialog/ProjectFormFloatingActions.tsx b/src/containers/Projects/containers/ProjectFormDialog/ProjectFormFloatingActions.tsx index 57b798d35..de8f89d99 100644 --- a/src/containers/Projects/containers/ProjectFormDialog/ProjectFormFloatingActions.tsx +++ b/src/containers/Projects/containers/ProjectFormDialog/ProjectFormFloatingActions.tsx @@ -1,11 +1,10 @@ -// @ts-nocheck import React from 'react'; import { useFormikContext } from 'formik'; import { Intent, Button, Classes } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'components'; +import { FormattedMessage as T } from '@/components'; import { useProjectFormContext } from './ProjectFormProvider'; -import withDialogActions from 'containers/Dialog/withDialogActions'; -import { compose } from 'utils'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { compose } from '@/utils'; /** * Project form floating actions. @@ -15,12 +14,12 @@ function ProjectFormFloatingActions({ // #withDialogActions closeDialog, }) { - // project form dialog context. - const { dialogName } = useProjectFormContext(); - // Formik context. const { isSubmitting } = useFormikContext(); + // project form dialog context. + const { dialogName } = useProjectFormContext(); + // Handle close button click. const handleCancelBtnClick = () => { closeDialog(dialogName); @@ -29,7 +28,7 @@ function ProjectFormFloatingActions({ return (
- + + + } + /> + ); +} + +export default compose(withDialogActions)(ProjectsEmptyStatus); diff --git a/src/containers/Projects/containers/ProjectsLanding/ProjectsList.tsx b/src/containers/Projects/containers/ProjectsLanding/ProjectsList.tsx index 77ad204b0..36442fb9b 100644 --- a/src/containers/Projects/containers/ProjectsLanding/ProjectsList.tsx +++ b/src/containers/Projects/containers/ProjectsLanding/ProjectsList.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { DashboardPageContent, DashboardContentTable } from 'components'; +import { DashboardPageContent, DashboardContentTable } from '@/components'; import ProjectsActionsBar from './ProjectsActionsBar'; import ProjectsViewTabs from './ProjectsViewTabs'; @@ -9,7 +9,7 @@ import withProjects from './withProjects'; import withProjectsActions from './withProjectsActions'; import { ProjectsListProvider } from './ProjectsListProvider'; -import { compose, transformTableStateToQuery } from 'utils'; +import { compose, transformTableStateToQuery } from '@/utils'; /** * Projects list. diff --git a/src/containers/Projects/containers/ProjectsLanding/ProjectsListProvider.tsx b/src/containers/Projects/containers/ProjectsLanding/ProjectsListProvider.tsx index a87f2d1f4..1b47bc919 100644 --- a/src/containers/Projects/containers/ProjectsLanding/ProjectsListProvider.tsx +++ b/src/containers/Projects/containers/ProjectsLanding/ProjectsListProvider.tsx @@ -1,7 +1,8 @@ -//@ts-nocheck import React from 'react'; -import { useResourceViews, useResourceMeta } from 'hooks/query'; -import DashboardInsider from '../../../../components/Dashboard/DashboardInsider'; +import { isEmpty } from 'lodash'; +import { useResourceViews, useResourceMeta } from '@/hooks/query'; +import { DashboardInsider } from '@/components'; +import { useProjects } from '../../hooks'; const ProjectsListContext = React.createContext(); @@ -14,16 +15,32 @@ function ProjectsListProvider({ query, tableStateChanged, ...props }) { const { data: projectsViews, isLoading: isViewsLoading } = useResourceViews('projects'); + // Fetch accounts list according to the given custom view id. + const { + data: { projects }, + isFetching: isProjectsFetching, + isLoading: isProjectsLoading, + } = useProjects(query, { keepPreviousData: true }); + + // Detarmines the datatable empty status. + const isEmptyStatus = + isEmpty(projects) && !tableStateChanged && !isProjectsLoading; + // provider payload. const provider = { + projects, + projectsViews, + + isProjectsLoading, + isProjectsFetching, + isViewsLoading, + + isEmptyStatus, }; return ( - + ); diff --git a/src/containers/Projects/containers/ProjectsLanding/ProjectsViewTabs.tsx b/src/containers/Projects/containers/ProjectsLanding/ProjectsViewTabs.tsx index 30b6e70dd..eaa142121 100644 --- a/src/containers/Projects/containers/ProjectsLanding/ProjectsViewTabs.tsx +++ b/src/containers/Projects/containers/ProjectsLanding/ProjectsViewTabs.tsx @@ -1,14 +1,13 @@ -//@ts-nocheck import React from 'react'; import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core'; -import { DashboardViewsTabs } from 'components'; +import { DashboardViewsTabs } from '@/components'; import withProjects from './withProjects'; import withProjectsActions from './withProjectsActions'; import { useProjectsListContext } from './ProjectsListProvider'; -import { compose, transfromViewsToTabs } from 'utils'; +import { compose, transfromViewsToTabs } from '@/utils'; /** * Projects views tabs. diff --git a/src/containers/Projects/containers/ProjectsLanding/components.tsx b/src/containers/Projects/containers/ProjectsLanding/components.tsx index d4ed1a586..5a6fccea2 100644 --- a/src/containers/Projects/containers/ProjectsLanding/components.tsx +++ b/src/containers/Projects/containers/ProjectsLanding/components.tsx @@ -10,8 +10,8 @@ import { Intent, ProgressBar, } from '@blueprintjs/core'; -import { Icon, FormatDate, Choose, FormattedMessage as T } from 'components'; -import { safeCallback, firstLettersArgs, calculateStatus } from 'utils'; +import { Icon, FormatDate, Choose, FormattedMessage as T } from '@/components'; +import { safeCallback, firstLettersArgs, calculateStatus } from '@/utils'; /** * project status. @@ -58,7 +58,7 @@ export const StatusAccessor = (project) => { */ export const AvatarCell = ({ row: { original }, size }) => ( - {firstLettersArgs(original?.display_name, original?.name)} + {firstLettersArgs(original?.contact_display_name, original?.name)} ); @@ -102,13 +102,15 @@ export const ActionsMenu = ({ export const ProjectsAccessor = (row) => ( - {row.display_name} + + {row.contact_display_name} + {row.name} - + {intl.get('projects.label.cost_estimate', { - value: row.cost_estimate, + value: row.cost_estimate_formatted, })} @@ -175,10 +177,10 @@ const ProjectItemDescription = styled.div` const ProjectStatusRoot = styled.div` display: flex; align-items: center; - /* justify-content: flex-end; */ margin-right: 0.5rem; flex-direction: row-reverse; `; + const ProjectStatusTaskAmount = styled.div` text-align: right; font-weight: 400; @@ -198,6 +200,7 @@ const ProjectProgressBar = styled(ProgressBar)` } } `; + const StatusTag = styled(Tag)` min-width: 65px; text-align: center; diff --git a/src/containers/Projects/containers/ProjectsLanding/withProjects.tsx b/src/containers/Projects/containers/ProjectsLanding/withProjects.tsx index 35b73c42f..4035c79f1 100644 --- a/src/containers/Projects/containers/ProjectsLanding/withProjects.tsx +++ b/src/containers/Projects/containers/ProjectsLanding/withProjects.tsx @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; import { getProjectsTableStateFactory, isProjectsTableStateChangedFactory, -} from '../../../../store/Project/projects.selectors'; +} from '@/store/Project/projects.selectors'; export default (mapState) => { const getProjectsTableState = getProjectsTableStateFactory(); diff --git a/src/containers/Projects/containers/ProjectsLanding/withProjectsActions.tsx b/src/containers/Projects/containers/ProjectsLanding/withProjectsActions.tsx index d74590f07..4504942e7 100644 --- a/src/containers/Projects/containers/ProjectsLanding/withProjectsActions.tsx +++ b/src/containers/Projects/containers/ProjectsLanding/withProjectsActions.tsx @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { setProjectsTableState, resetProjectsTableState, -} from '../../../../store/Project/projects.actions'; +} from '@/store/Project/projects.actions'; const mapDispatchToProps = (dispatch) => ({ setProjectsTableState: (state) => dispatch(setProjectsTableState(state)), diff --git a/src/containers/Projects/containers/TaskFormDialog/TaskFormContent.tsx b/src/containers/Projects/containers/TaskFormDialog/TaskFormContent.tsx deleted file mode 100644 index 1d1f371f5..000000000 --- a/src/containers/Projects/containers/TaskFormDialog/TaskFormContent.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { Form } from 'formik'; -import TaskFormFields from './TaskFormFields'; -import TaskFormFloatingActions from './TaskFormFloatingActions'; - -/** - * Task form content. - * @returns - */ -export default function TaskFormContent() { - return ( -
- - - - ); -} diff --git a/src/containers/Projects/containers/TaskFormDialog/TaskFormDialogContent.tsx b/src/containers/Projects/containers/TaskFormDialog/TaskFormDialogContent.tsx deleted file mode 100644 index 441356e65..000000000 --- a/src/containers/Projects/containers/TaskFormDialog/TaskFormDialogContent.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { TaskFormProvider } from './TaskFormProvider'; -import TaskForm from './TaskForm'; - -/** - * Task form dialog content. - */ -export default function TaskFormDialogContent({ - // #ownProps - dialogName, - task, -}) { - return ( - - - - ); -} diff --git a/src/containers/Projects/containers/TaskFormDialog/TaskFormProvider.tsx b/src/containers/Projects/containers/TaskFormDialog/TaskFormProvider.tsx deleted file mode 100644 index ea9bf5be3..000000000 --- a/src/containers/Projects/containers/TaskFormDialog/TaskFormProvider.tsx +++ /dev/null @@ -1,31 +0,0 @@ -//@ts-nocheck -import React from 'react'; -import { DialogContent } from 'components'; - -const TaskFormContext = React.createContext(); - -/** - * Task form provider. - * @returns - */ -function TaskFormProvider({ - // #ownProps - dialogName, - taskId, - ...props -}) { - // State provider. - const provider = { - dialogName, - }; - - return ( - - - - ); -} - -const useTaskFormContext = () => React.useContext(TaskFormContext); - -export { TaskFormProvider, useTaskFormContext }; diff --git a/src/containers/Projects/containers/TaskFormDialog/index.tsx b/src/containers/Projects/containers/TaskFormDialog/index.tsx deleted file mode 100644 index ee74f4719..000000000 --- a/src/containers/Projects/containers/TaskFormDialog/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import intl from 'react-intl-universal'; -import { Dialog, DialogSuspense, FormattedMessage as T } from 'components'; -import withDialogRedux from 'components/DialogReduxConnect'; -import { compose } from 'utils'; - -const TaskFormDialogContent = React.lazy( - () => import('./TaskFormDialogContent'), -); - -/** - * Task form dialog. - * @returns - */ -function TaskFormDialog({ dialogName, payload: { taskId = null }, isOpen }) { - return ( - - - - - - ); -} -export default compose(withDialogRedux())(TaskFormDialog); - -const TaskFormDialogRoot = styled(Dialog)``; diff --git a/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryForm.schema.tsx b/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryForm.schema.tsx deleted file mode 100644 index 92ae4ebac..000000000 --- a/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryForm.schema.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import * as Yup from 'yup'; -import intl from 'react-intl-universal'; -import { DATATYPES_LENGTH } from 'common/dataTypes'; - -const Schema = Yup.object().shape({ - date: Yup.date().label(intl.get('time_entry.schema.label.date')).required(), - projectId: Yup.string() - .label(intl.get('time_entry.schema.label.project_name')) - .required(), - taskId: Yup.string() - .label(intl.get('time_entry.schema.label.task_name')) - .required(), - description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT), - duration: Yup.string() - .label(intl.get('time_entry.schema.label.duration')) - .required(), -}); - -export const CreateTimeEntryFormSchema = Schema; diff --git a/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryFormContent.tsx b/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryFormContent.tsx deleted file mode 100644 index dc45632f5..000000000 --- a/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryFormContent.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { Form } from 'formik'; -import TimeEntryFormFields from './TimeEntryFormFields'; -import TimeEntryFormFloatingActions from './TimeEntryFormFloatingActions'; - -/** - * Time entry form content. - * @returns - */ -export default function TimeEntryFormContent() { - return ( -
- - - - ); -} diff --git a/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryFormDialogContent.tsx b/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryFormDialogContent.tsx deleted file mode 100644 index 138914eb4..000000000 --- a/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryFormDialogContent.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { TimeEntryFormProvider } from './TimeEntryFormProvider'; -import TimeEntryForm from './TimeEntryForm'; - -/** - * Time entry form dialog content. - * @returns {ReactNode} - */ -export default function TimeEntryFormDialogContent({ - // #ownProps - dialogName, - project, - timeEntry, -}) { - return ( - - - - ); -} diff --git a/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryFormProvider.tsx b/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryFormProvider.tsx deleted file mode 100644 index 56c8aeb47..000000000 --- a/src/containers/Projects/containers/TimeEntryFormDialog/TimeEntryFormProvider.tsx +++ /dev/null @@ -1,31 +0,0 @@ -//@ts-nocheck -import React from 'react'; -import { DialogContent } from 'components'; - -const TimeEntryFormContext = React.createContext(); - -/** - * Time entry form provider. - * @returns - */ -function TimeEntryFormProvider({ - // #ownProps - dialogName, - projectId, - timeEntryId, - ...props -}) { - const provider = { - dialogName, - }; - - return ( - - - - ); -} - -const useTimeEntryFormContext = () => React.useContext(TimeEntryFormContext); - -export { TimeEntryFormProvider, useTimeEntryFormContext }; diff --git a/src/containers/Projects/containers/TimeEntryFormDialog/components/ProjectSelect.tsx b/src/containers/Projects/containers/TimeEntryFormDialog/components/ProjectSelect.tsx deleted file mode 100644 index 901dce75f..000000000 --- a/src/containers/Projects/containers/TimeEntryFormDialog/components/ProjectSelect.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import intl from 'react-intl-universal'; -import { MenuItem, Button } from '@blueprintjs/core'; -import { FSelect } from '../../../../../components/Forms'; - -/** - * - * @param {*} query - * @param {*} project - * @param {*} _index - * @param {*} exactMatch - * @returns - */ -const projectItemPredicate = (query, project, _index, exactMatch) => { - const normalizedTitle = project.name.toLowerCase(); - const normalizedQuery = query.toLowerCase(); - - if (exactMatch) { - return normalizedTitle === normalizedQuery; - } else { - return `${project.name}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0; - } -}; - -/** - * - * @param {*} project - * @param {*} param1 - * @returns - */ -const projectItemRenderer = (project, { handleClick, modifiers, query }) => { - return ( - - ); -}; - -const projectSelectProps = { - // itemPredicate: projectItemPredicate, - itemRenderer: projectItemRenderer, - valueAccessor: 'id', - labelAccessor: 'name', -}; - -export function ProjectSelect({ projects, ...rest }) { - return ( - - ); -} - -function ProjectSelectButton({ label }) { - return