diff --git a/src/components/DialogsContainer.js b/src/components/DialogsContainer.js
index 8ddff3496..4cad1a95a 100644
--- a/src/components/DialogsContainer.js
+++ b/src/components/DialogsContainer.js
@@ -40,6 +40,8 @@ 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 ProjectDialog from '../containers/Dialogs/ProjectDialog';
+import TaskDialog from '../containers/Dialogs/TaskDialog';
/**
* Dialogs container.
@@ -90,6 +92,8 @@ export default function DialogsContainer() {
+
+
);
}
diff --git a/src/containers/Dialogs/ProjectDialog/containers/ProjectDialogContent.tsx b/src/containers/Dialogs/ProjectDialog/containers/ProjectDialogContent.tsx
new file mode 100644
index 000000000..e4aa08c56
--- /dev/null
+++ b/src/containers/Dialogs/ProjectDialog/containers/ProjectDialogContent.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+
+import 'style/pages/Projects/ProjectFormDialog.scss';
+
+import { ProjectFormProvider } from './ProjectFormProvider';
+import ProjectForm from './ProjectForm';
+
+/**
+ * Project dialog content.
+ * @returns
+ */
+export default function ProjectDialogContent({
+ // #ownProps
+ dialogName,
+ project,
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Dialogs/ProjectDialog/containers/ProjectForm.schema.tsx b/src/containers/Dialogs/ProjectDialog/containers/ProjectForm.schema.tsx
new file mode 100644
index 000000000..6738904f0
--- /dev/null
+++ b/src/containers/Dialogs/ProjectDialog/containers/ProjectForm.schema.tsx
@@ -0,0 +1,13 @@
+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(),
+ project_name: Yup.string(),
+ project_deadline: Yup.date(),
+ project_state: Yup.boolean(),
+ project_cost: Yup.number(),
+});
+
+export const CreateProjectFormSchema = Schema;
diff --git a/src/containers/Dialogs/ProjectDialog/containers/ProjectForm.tsx b/src/containers/Dialogs/ProjectDialog/containers/ProjectForm.tsx
new file mode 100644
index 000000000..cd7359898
--- /dev/null
+++ b/src/containers/Dialogs/ProjectDialog/containers/ProjectForm.tsx
@@ -0,0 +1,71 @@
+// @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 withDialogActions from 'containers/Dialog/withDialogActions';
+
+import ProjectFormContent from './ProjectFormContent';
+import { CreateProjectFormSchema } from './ProjectForm.schema';
+import { useProjectFormContext } from './ProjectFormProvider';
+
+import { compose } from 'utils';
+
+const defaultInitialValues = {
+ contact:'',
+ project_name:'',
+ project_deadline: moment(new Date()).format('YYYY-MM-DD'),
+ project_state: false,
+ project_cost:''
+};
+
+/**
+ * Project form
+ * @returns
+ */
+function ProjectForm({
+ // #withDialogActions
+ closeDialog,
+}) {
+ // project form dialog context.
+ const { dialogName } = useProjectFormContext();
+
+ // Initial form values
+ const initialValues = {
+ ...defaultInitialValues,
+ };
+
+ // Handles the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
+ const form = {};
+
+ // Handle request response success.
+ const onSuccess = (response) => {
+ AppToaster.show({});
+ closeDialog(dialogName);
+ };
+
+ // Handle request response errors.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ setSubmitting(false);
+ };
+ };
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(ProjectForm);
diff --git a/src/containers/Dialogs/ProjectDialog/containers/ProjectFormContent.tsx b/src/containers/Dialogs/ProjectDialog/containers/ProjectFormContent.tsx
new file mode 100644
index 000000000..0bcbed687
--- /dev/null
+++ b/src/containers/Dialogs/ProjectDialog/containers/ProjectFormContent.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Form } from 'formik';
+
+import ProjectFormFields from './ProjectFormFields';
+import ProjectFormFloatingActions from './ProjectFormFloatingActions';
+
+/**
+ * Project form content.
+ */
+export default function ProjectFormContent() {
+ return (
+
+ );
+}
diff --git a/src/containers/Dialogs/ProjectDialog/containers/ProjectFormFields.tsx b/src/containers/Dialogs/ProjectDialog/containers/ProjectFormFields.tsx
new file mode 100644
index 000000000..d01b1962c
--- /dev/null
+++ b/src/containers/Dialogs/ProjectDialog/containers/ProjectFormFields.tsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { Classes, Position, FormGroup, ControlGroup } from '@blueprintjs/core';
+import { FastField } from 'formik';
+import { DateInput } from '@blueprintjs/datetime';
+import { CLASSES } from 'common/classes';
+import classNames from 'classnames';
+import {
+ FFormGroup,
+ FInputGroup,
+ FCheckbox,
+ FMoneyInputGroup,
+ InputPrependText,
+ FormattedMessage as T,
+ CustomerSelectField,
+ FCustomerSelectField,
+} from 'components';
+import {
+ inputIntent,
+ momentFormatter,
+ tansformDateValue,
+ handleDateChange,
+} from 'utils';
+
+/**
+ * Project form fields.
+ * @returns
+ */
+function ProjectFormFields() {
+ return (
+
+ {/*------------ Contact -----------*/}
+
+ {({ form, field: { value }, meta: { error, touched } }) => (
+
+ {
+ form.setFieldValue('contact', customer.id);
+ }}
+ allowCreate={true}
+ popoverFill={true}
+ />
+
+ )}
+
+
+ {/*------------ Project Name -----------*/}
+
+
+
+ {/*------------ DeadLine -----------*/}
+
+ {
+ // })}
+ // value={tansformDateValue(value)}
+ popoverProps={{
+ position: Position.BOTTOM,
+ minimal: true,
+ }}
+ />
+
+
+ {/*------------ CheckBox -----------*/}
+
+
+
+ {/*------------ Cost Estimate -----------*/}
+
+
+
+
+
+
+
+ );
+}
+
+export default ProjectFormFields;
diff --git a/src/containers/Dialogs/ProjectDialog/containers/ProjectFormFloatingActions.tsx b/src/containers/Dialogs/ProjectDialog/containers/ProjectFormFloatingActions.tsx
new file mode 100644
index 000000000..1f118c72f
--- /dev/null
+++ b/src/containers/Dialogs/ProjectDialog/containers/ProjectFormFloatingActions.tsx
@@ -0,0 +1,48 @@
+// @ts-nocheck
+import React from 'react';
+import { useFormikContext } from 'formik';
+import { Intent, Button, Classes } from '@blueprintjs/core';
+import { FormattedMessage as T } from 'components';
+import { useProjectFormContext } from './ProjectFormProvider';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+/**
+ * Project form floating actions.
+ * @returns
+ */
+function ProjectFormFloatingActions({
+ // #withDialogActions
+ closeDialog,
+}) {
+ // project form dialog context.
+ const { dialogName } = useProjectFormContext();
+
+ // Formik context.
+ const { isSubmitting } = useFormikContext();
+
+ // Handle close button click.
+ const handleCancelBtnClick = () => {
+ closeDialog(dialogName);
+ };
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(ProjectFormFloatingActions);
diff --git a/src/containers/Dialogs/ProjectDialog/containers/ProjectFormProvider.tsx b/src/containers/Dialogs/ProjectDialog/containers/ProjectFormProvider.tsx
new file mode 100644
index 000000000..fc9561836
--- /dev/null
+++ b/src/containers/Dialogs/ProjectDialog/containers/ProjectFormProvider.tsx
@@ -0,0 +1,33 @@
+// @ts-nocheck
+import React from 'react';
+
+import { DialogContent } from 'components';
+
+const ProjectFormContext = React.createContext();
+
+/**
+ * Project form provider.
+ * @returns
+ */
+
+function ProjectFormProvider({
+ // #ownProps
+ dialogName,
+ projectId,
+ ...props
+}) {
+ // State provider.
+ const provider = {};
+
+ return (
+
+
+
+ );
+}
+
+const useProjectFormContext = () => React.useContext(ProjectFormContext);
+
+export { ProjectFormProvider, useProjectFormContext };
diff --git a/src/containers/Dialogs/ProjectDialog/index.tsx b/src/containers/Dialogs/ProjectDialog/index.tsx
new file mode 100644
index 000000000..c306c7402
--- /dev/null
+++ b/src/containers/Dialogs/ProjectDialog/index.tsx
@@ -0,0 +1,51 @@
+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 ProjectDialogContent = React.lazy(
+ () => import('./containers/ProjectDialogContent'),
+);
+
+/**
+ * Project dialog.
+ * @returns
+ */
+function ProjectDialog({ dialogName, payload: { projectId = null }, isOpen }) {
+ return (
+
+ );
+}
+
+export default compose(withDialogRedux())(ProjectDialog);
+
+// const ProjectDialogRoot = styled(Dialog)`
+// .bp3-dialog-body {
+// .bp3-form-group {
+// margin-bottom: 15px;
+// margin-top: 15px;
+
+// label.bp3-label {
+// margin-bottom: 3px;
+// font-size: 13px;
+// }
+// }
+
+// .bp3-dialog-footer {
+// padding-top: 10px;
+// }
+// }
+// `;
diff --git a/src/routes/dashboard.js b/src/routes/dashboard.js
index 9283b492f..de351dfc6 100644
--- a/src/routes/dashboard.js
+++ b/src/routes/dashboard.js
@@ -969,6 +969,13 @@ export const getDashboardRoutes = () => [
),
pageTitle: intl.get('sidebar.transactions_locaking'),
},
+ {
+ path: '/projects',
+ component: lazy(() =>
+ import('../containers/Projects/containers/ProjectsList'),
+ ),
+ pageTitle: 'Projects',
+ },
// Homepage
{
path: `/`,
diff --git a/src/store/Project/projects.actions.ts b/src/store/Project/projects.actions.ts
new file mode 100644
index 000000000..f39531d81
--- /dev/null
+++ b/src/store/Project/projects.actions.ts
@@ -0,0 +1,14 @@
+import t from 'store/types';
+
+export const setProjectsTableState = (queries) => {
+ return {
+ type: t.PROJECTS_TABLE_STATE_SET,
+ payload: { queries },
+ };
+};
+
+export const resetProjectsTableState = () => {
+ return {
+ type: t.PROJECTS_TABLE_STATE_RESET,
+ };
+};
diff --git a/src/store/Project/projects.reducer.ts b/src/store/Project/projects.reducer.ts
new file mode 100644
index 000000000..cd219035b
--- /dev/null
+++ b/src/store/Project/projects.reducer.ts
@@ -0,0 +1,33 @@
+import { createReducer } from '@reduxjs/toolkit';
+import { persistReducer, purgeStoredState } from 'redux-persist';
+import storage from 'redux-persist/lib/storage';
+import { createTableStateReducers } from 'store/tableState.reducer';
+import t from 'store/types';
+
+export const defaultTableQuery = {
+ pageSize: 20,
+ pageIndex: 0,
+ filterRoles: [],
+ viewSlug: null,
+};
+
+const initialState = {
+ tableState: defaultTableQuery,
+};
+
+const STORAGE_KEY = 'bigcapital:projects';
+
+const CONFIG = {
+ key: STORAGE_KEY,
+ whitelist: [],
+ storage,
+};
+const reducerInstance = createReducer(initialState, {
+ ...createTableStateReducers('PROJECTS', defaultTableQuery),
+
+ [t.RESET]: () => {
+ purgeStoredState(CONFIG);
+ },
+});
+
+export default persistReducer(CONFIG, reducerInstance);
diff --git a/src/store/Project/projects.selectors.ts b/src/store/Project/projects.selectors.ts
new file mode 100644
index 000000000..10ab19fab
--- /dev/null
+++ b/src/store/Project/projects.selectors.ts
@@ -0,0 +1,24 @@
+import { isEqual } from 'lodash';
+import { createDeepEqualSelector } from 'utils';
+import { paginationLocationQuery } from 'store/selectors';
+import { defaultTableQuery } from './projects.reducer';
+
+const projectsTableState = (state) => state.projects.tableState;
+
+// Retrieve projects table query.
+export const getProjectsTableStateFactory = () =>
+ createDeepEqualSelector(
+ paginationLocationQuery,
+ projectsTableState,
+ (locationQuery, tableState) => {
+ return {
+ ...locationQuery,
+ ...tableState,
+ };
+ },
+ );
+
+export const isProjectsTableStateChangedFactory = () =>
+ createDeepEqualSelector(projectsTableState, (tableState) => {
+ return !isEqual(tableState, defaultTableQuery);
+ });
diff --git a/src/store/Project/projects.type.ts b/src/store/Project/projects.type.ts
new file mode 100644
index 000000000..8026b0cf6
--- /dev/null
+++ b/src/store/Project/projects.type.ts
@@ -0,0 +1,4 @@
+export default {
+ PROJECTS_TABLE_STATE_SET: 'PROJECTS/TABLE_STATE_SET',
+ PROJECTS_TABLE_STATE_RESET: 'PROJECTS/TABLE_STATE_RESET',
+};
diff --git a/src/store/reducers.js b/src/store/reducers.js
index 0223270ad..b89241911 100644
--- a/src/store/reducers.js
+++ b/src/store/reducers.js
@@ -35,6 +35,7 @@ import plans from './plans/plans.reducer';
import creditNotes from './CreditNote/creditNote.reducer';
import vendorCredit from './VendorCredit/VendorCredit.reducer';
import warehouseTransfers from './WarehouseTransfer/warehouseTransfer.reducer';
+import projects from './Project/projects.reducer';
const appReducer = combineReducers({
authentication,
@@ -70,6 +71,7 @@ const appReducer = combineReducers({
creditNotes,
vendorCredit,
warehouseTransfers,
+ projects,
});
// Reset the state of a redux store
diff --git a/src/store/types.js b/src/store/types.js
index 079344984..602953034 100644
--- a/src/store/types.js
+++ b/src/store/types.js
@@ -31,6 +31,7 @@ import inventoryAdjustments from './inventoryAdjustments/inventoryAdjustment.typ
import creditNote from './CreditNote/creditNote.type';
import vendorCredit from './VendorCredit/vendorCredit.type';
import WarehouseTransfer from './WarehouseTransfer/warehouseTransfer.type';
+import projects from './Project/projects.type'
import plans from './plans/plans.types';
export default {
@@ -68,4 +69,5 @@ export default {
...creditNote,
...vendorCredit,
...WarehouseTransfer,
+ ...projects
};
diff --git a/src/style/pages/Projects/ProjectFormDialog.scss b/src/style/pages/Projects/ProjectFormDialog.scss
new file mode 100644
index 000000000..31801f7ee
--- /dev/null
+++ b/src/style/pages/Projects/ProjectFormDialog.scss
@@ -0,0 +1,20 @@
+.dialog--project-form {
+ width: 650px;
+
+ .bp3-dialog-body {
+ .bp3-form-group {
+ margin-bottom: 15px;
+ margin-top: 15px;
+
+ label.bp3-label {
+ margin-bottom: 3px;
+ font-size: 13px;
+ }
+ }
+
+
+ }
+ .bp3-dialog-footer {
+ padding-top: 10px;
+ }
+}