diff --git a/src/common/classes.js b/src/common/classes.js
index 1bbcf9873..c00f489f3 100644
--- a/src/common/classes.js
+++ b/src/common/classes.js
@@ -71,6 +71,7 @@ const CLASSES = {
PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT: 'preferences-page__inside-content--accountant',
PREFERENCES_PAGE_INSIDE_CONTENT_SMS_INTEGRATION: 'preferences-page__inside-content--sms-integration',
PREFERENCES_PAGE_INSIDE_CONTENT_ROLES_FORM: 'preferences-page__inside-content--roles-form',
+ PREFERENCES_PAGE_INSIDE_CONTENT_BRANCHES: 'preferences-page__inside-content--branches',
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
diff --git a/src/components/DialogsContainer.js b/src/components/DialogsContainer.js
index f52002200..ed46913da 100644
--- a/src/components/DialogsContainer.js
+++ b/src/components/DialogsContainer.js
@@ -35,6 +35,7 @@ import UnlockingPartialTransactionsDialog from '../containers/Dialogs/UnlockingP
import CreditNotePdfPreviewDialog from '../containers/Dialogs/CreditNotePdfPreviewDialog';
import PaymentReceivePdfPreviewDialog from '../containers/Dialogs/PaymentReceivePdfPreviewDialog';
import WarehouseFormDialog from '../containers/Dialogs/WarehouseFormDialog';
+import BranchFormDialog from '../containers/Dialogs/BranchFormDialog';
/**
* Dialogs container.
@@ -80,6 +81,7 @@ export default function DialogsContainer() {
+
);
}
diff --git a/src/components/Preferences/PreferencesTopbar.js b/src/components/Preferences/PreferencesTopbar.js
index 7b27a83e8..7212b2116 100644
--- a/src/components/Preferences/PreferencesTopbar.js
+++ b/src/components/Preferences/PreferencesTopbar.js
@@ -7,6 +7,7 @@ import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
import UsersActions from 'containers/Preferences/Users/UsersActions';
import CurrenciesActions from 'containers/Preferences/Currencies/CurrenciesActions';
import WarehousesActions from '../../containers/Preferences/Warehouses/WarehousesActions'
+import BranchesActions from '../../containers/Preferences/Branches/BranchesActions';
import withDashboard from 'containers/Dashboard/withDashboard';
import { compose } from 'utils';
@@ -41,6 +42,11 @@ function PreferencesTopbar({ preferencesPageTitle }) {
path={'/preferences/warehouses'}
component={WarehousesActions}
/>
+
diff --git a/src/config/preferencesMenu.js b/src/config/preferencesMenu.js
index f01ca6e12..555b46fe8 100644
--- a/src/config/preferencesMenu.js
+++ b/src/config/preferencesMenu.js
@@ -13,13 +13,16 @@ export default [
},
{
text: ,
-
href: '/preferences/currencies',
},
{
text: ,
href: '/preferences/warehouses',
},
+ {
+ text: ,
+ href: '/preferences/branches',
+ },
{
text: ,
disabled: false,
diff --git a/src/containers/Alerts/Branches/BranchDeleteAlert.js b/src/containers/Alerts/Branches/BranchDeleteAlert.js
new file mode 100644
index 000000000..a58bb63ea
--- /dev/null
+++ b/src/containers/Alerts/Branches/BranchDeleteAlert.js
@@ -0,0 +1,77 @@
+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 { useDeleteBranch } from 'hooks/query';
+
+import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
+import withAlertActions from 'containers/Alert/withAlertActions';
+
+import { compose } from 'utils';
+
+/**
+ * Branch delete alert.
+ */
+function BranchDeleteAlert({
+ name,
+
+ // #withAlertStoreConnect
+ isOpen,
+ payload: { branchId },
+
+ // #withAlertActions
+ closeAlert,
+}) {
+
+ const { mutateAsync: deleteBranch, isLoading } = useDeleteBranch();
+
+ // Handle cancel delete alert.
+ const handleCancelDelete = () => {
+ closeAlert(name);
+ };
+
+ // Handle confirm delete branch.
+ const handleConfirmDeleteBranch = () => {
+ deleteBranch(branchId)
+ .then(() => {
+ AppToaster.show({
+ message: intl.get('branch.alert.delete_message'),
+ intent: Intent.SUCCESS,
+ });
+ })
+ .catch(
+ ({
+ response: {
+ data: { errors },
+ },
+ }) => {},
+ )
+ .finally(() => {
+ closeAlert(name);
+ });
+ };
+
+ return (
+ }
+ confirmButtonText={}
+ icon="trash"
+ intent={Intent.DANGER}
+ isOpen={isOpen}
+ onCancel={handleCancelDelete}
+ onConfirm={handleConfirmDeleteBranch}
+ loading={isLoading}
+ >
+
+
+
+
+ );
+}
+
+export default compose(
+ withAlertStoreConnect(),
+ withAlertActions,
+)(BranchDeleteAlert);
diff --git a/src/containers/AlertsContainer/registered.js b/src/containers/AlertsContainer/registered.js
index dfd8f839d..e84ffcbb8 100644
--- a/src/containers/AlertsContainer/registered.js
+++ b/src/containers/AlertsContainer/registered.js
@@ -22,6 +22,7 @@ import VendorCreditNotesAlerts from '../Purchases/CreditNotes/VendorCreditNotesA
import TransactionsLockingAlerts from '../TransactionsLocking/TransactionsLockingAlerts';
import WarehousesAlerts from '../Preferences/Warehouses/WarehousesAlerts';
import WarehousesTransfersAlerts from '../WarehouseTransfers/WarehousesTransfersAlerts'
+import BranchesAlerts from '../Preferences/Branches/BranchesAlerts';
export default [
...AccountsAlerts,
@@ -47,5 +48,6 @@ export default [
...VendorCreditNotesAlerts,
...TransactionsLockingAlerts,
...WarehousesAlerts,
- ...WarehousesTransfersAlerts
+ ...WarehousesTransfersAlerts,
+ ...BranchesAlerts,
];
diff --git a/src/containers/Dialogs/BranchFormDialog/BranchForm.js b/src/containers/Dialogs/BranchFormDialog/BranchForm.js
new file mode 100644
index 000000000..17f2c8504
--- /dev/null
+++ b/src/containers/Dialogs/BranchFormDialog/BranchForm.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { Formik } from 'formik';
+import { Intent } from '@blueprintjs/core';
+
+import { AppToaster } from 'components';
+import { CreateBranchFormSchema } from './BranchForm.schema';
+
+import BranchFormContent from './BranchFormContent';
+import { useBranchFormContext } from './BranchFormProvider';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose, transformToForm } from 'utils';
+
+const defaultInitialValues = {
+ name: '',
+ code: '',
+ address: '',
+ phone_number: '',
+ email: '',
+ website: '',
+ city: '',
+ country: '',
+};
+
+function BranchForm({
+ // #withDialogActions
+ closeDialog,
+}) {
+ const {
+ dialogName,
+ branch,
+ branchId,
+ createBranchMutate,
+ editBranchMutate,
+ } = useBranchFormContext();
+
+ // Initial form values.
+ const initialValues = {
+ ...defaultInitialValues,
+ ...transformToForm(branch, defaultInitialValues),
+ };
+
+ // Handles the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
+ const form = { ...values };
+
+ // Handle request response success.
+ const onSuccess = (response) => {
+ AppToaster.show({
+ message: intl.get('branch.dialog.success_message'),
+ intent: Intent.SUCCESS,
+ });
+ closeDialog(dialogName);
+ };
+
+ // Handle request response errors.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ if (errors) {
+ }
+ setSubmitting(false);
+ };
+
+ if (branchId) {
+ editBranchMutate([branchId, form]).then(onSuccess).catch(onError);
+ } else {
+ createBranchMutate(form).then(onSuccess).catch(onError);
+ }
+ };
+
+ return (
+
+ );
+}
+export default compose(withDialogActions)(BranchForm);
diff --git a/src/containers/Dialogs/BranchFormDialog/BranchForm.schema.js b/src/containers/Dialogs/BranchFormDialog/BranchForm.schema.js
new file mode 100644
index 000000000..4995f2413
--- /dev/null
+++ b/src/containers/Dialogs/BranchFormDialog/BranchForm.schema.js
@@ -0,0 +1,16 @@
+import * as Yup from 'yup';
+import intl from 'react-intl-universal';
+import { DATATYPES_LENGTH } from 'common/dataTypes';
+
+const Schema = Yup.object().shape({
+ name: Yup.string().required().label(intl.get('branch_name')),
+ code: Yup.string().trim().min(0).max(DATATYPES_LENGTH.STRING),
+ address: Yup.string().trim(),
+ city: Yup.string().trim(),
+ country: Yup.string().trim(),
+ website: Yup.string().url().nullable(),
+ phone_number: Yup.number(),
+ email: Yup.string().email().nullable(),
+});
+
+export const CreateBranchFormSchema = Schema;
diff --git a/src/containers/Dialogs/BranchFormDialog/BranchFormContent.js b/src/containers/Dialogs/BranchFormDialog/BranchFormContent.js
new file mode 100644
index 000000000..e47e5706e
--- /dev/null
+++ b/src/containers/Dialogs/BranchFormDialog/BranchFormContent.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Form } from 'formik';
+
+import BranchFormFields from './BranchFormFields';
+import BranchFormFloatingActions from './BranchFormFloatingActions';
+
+/**
+ * Branch form content.
+ */
+export default function BranchFormContent() {
+ return (
+
+ );
+}
diff --git a/src/containers/Dialogs/BranchFormDialog/BranchFormDialogContent.js b/src/containers/Dialogs/BranchFormDialog/BranchFormDialogContent.js
new file mode 100644
index 000000000..336a37b22
--- /dev/null
+++ b/src/containers/Dialogs/BranchFormDialog/BranchFormDialogContent.js
@@ -0,0 +1,21 @@
+import React from 'react';
+
+import '../../../style/pages/Branches/BranchFormDialog.scss';
+
+import { BranchFormProvider } from './BranchFormProvider';
+import BranchForm from './BranchForm';
+
+/**
+ * Branch form dialog content.
+ */
+export default function BranchFormDialogContent({
+ // #ownProps
+ dialogName,
+ branchId,
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Dialogs/BranchFormDialog/BranchFormFields.js b/src/containers/Dialogs/BranchFormDialog/BranchFormFields.js
new file mode 100644
index 000000000..6d6a7203e
--- /dev/null
+++ b/src/containers/Dialogs/BranchFormDialog/BranchFormFields.js
@@ -0,0 +1,153 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { FastField, ErrorMessage, Field } from 'formik';
+import styled from 'styled-components';
+import {
+ Classes,
+ FormGroup,
+ InputGroup,
+ ControlGroup,
+ Position,
+} from '@blueprintjs/core';
+import { inputIntent } from 'utils';
+import { FieldRequiredHint, Col, Row, FormattedMessage as T } from 'components';
+import { useBranchFormContext } from './BranchFormProvider';
+
+/**
+ * Branch form dialog fields.
+ */
+function BranchFormFields() {
+ return (
+
+ {/*------------ Branch Name -----------*/}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ intent={inputIntent({ error, touched })}
+ inline={true}
+ helperText={}
+ className={'form-group--branch_name'}
+ >
+
+
+ )}
+
+ {/*------------ Branch Code -----------*/}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ intent={inputIntent({ error, touched })}
+ inline={true}
+ helperText={}
+ className={'form-group--branch_name'}
+ >
+
+
+ )}
+
+
+ {/*------------ Branch Address -----------*/}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ className={'form-group--branch_address'}
+ >
+
+
+ )}
+
+
+ {/*------------ Branch Address City & Country-----------*/}
+ }
+ >
+
+
+ {({ field, meta: { error, touched } }) => (
+
+ )}
+
+
+
+ {({ field, meta: { error, touched } }) => (
+
+ )}
+
+
+
+
+
+ {/*------------ Phone Number -----------*/}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ className={'form-group--phone_number'}
+ >
+
+
+ )}
+
+
+ {/*------------ Email -----------*/}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ className={'form-group--email'}
+ >
+
+
+ )}
+
+
+ {/*------------ Website -----------*/}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ className={'form-group--website'}
+ >
+
+
+ )}
+
+
+ );
+}
+
+export default BranchFormFields;
+
+const BranchAddressWrap = styled.div`
+ margin-left: 160px;
+`;
diff --git a/src/containers/Dialogs/BranchFormDialog/BranchFormFloatingActions.js b/src/containers/Dialogs/BranchFormDialog/BranchFormFloatingActions.js
new file mode 100644
index 000000000..8b2c558bc
--- /dev/null
+++ b/src/containers/Dialogs/BranchFormDialog/BranchFormFloatingActions.js
@@ -0,0 +1,46 @@
+import React from 'react';
+
+import { Intent, Button, Classes } from '@blueprintjs/core';
+import { useFormikContext } from 'formik';
+import { FormattedMessage as T } from 'components';
+
+import { useBranchFormContext } from './BranchFormProvider';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+/**
+ * Branch form floating actions.
+ */
+function BranchFormFloatingActions({
+ // #withDialogActions
+ closeDialog,
+}) {
+ // Formik context.
+ const { isSubmitting } = useFormikContext();
+
+ const { dialogName } = useBranchFormContext();
+
+ // Handle close button click.
+ const handleCancelBtnClick = () => {
+ closeDialog(dialogName);
+ };
+
+ return (
+
+ );
+}
+export default compose(withDialogActions)(BranchFormFloatingActions);
diff --git a/src/containers/Dialogs/BranchFormDialog/BranchFormProvider.js b/src/containers/Dialogs/BranchFormDialog/BranchFormProvider.js
new file mode 100644
index 000000000..7d9ee8a3d
--- /dev/null
+++ b/src/containers/Dialogs/BranchFormDialog/BranchFormProvider.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import { DialogContent } from 'components';
+import { useCreateBranch, useEditBranch, useBranch } from 'hooks/query';
+
+const BranchFormContext = React.createContext();
+
+/**
+ * Branch form dialog provider.
+ */
+function BranchFormProvider({ dialogName, branchId, ...props }) {
+ // Create and edit warehouse mutations.
+ const { mutateAsync: createBranchMutate } = useCreateBranch();
+ const { mutateAsync: editBranchMutate } = useEditBranch();
+
+ // Handle fetch branch detail.
+ const { data: branch, isLoading: isBranchLoading } = useBranch(branchId, {
+ enabled: !!branchId,
+ });
+
+ // State provider.
+ const provider = {
+ dialogName,
+ branch,
+ branchId,
+ createBranchMutate,
+ editBranchMutate,
+ };
+
+ return (
+
+
+
+ );
+}
+const useBranchFormContext = () => React.useContext(BranchFormContext);
+
+export { BranchFormProvider, useBranchFormContext };
diff --git a/src/containers/Dialogs/BranchFormDialog/index.js b/src/containers/Dialogs/BranchFormDialog/index.js
new file mode 100644
index 000000000..f5b611148
--- /dev/null
+++ b/src/containers/Dialogs/BranchFormDialog/index.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import { FormattedMessage as T } from 'components';
+import { Dialog, DialogSuspense } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+
+import { compose } from 'utils';
+
+const BranchFormDialogContent = React.lazy(() =>
+ import('./BranchFormDialogContent'),
+);
+
+/**
+ * Branch form form dialog.
+ */
+function BranchFormDialog({
+ dialogName,
+ payload: { branchId, action },
+ isOpen,
+}) {
+ return (
+
+ ) : (
+
+ )
+ }
+ isOpen={isOpen}
+ canEscapeJeyClose={true}
+ autoFocus={true}
+ className={'dialog--branch-form'}
+ >
+
+
+
+
+ );
+}
+export default compose(withDialogRedux())(BranchFormDialog);
diff --git a/src/containers/Preferences/Branches/Branches.js b/src/containers/Preferences/Branches/Branches.js
new file mode 100644
index 000000000..af93b014c
--- /dev/null
+++ b/src/containers/Preferences/Branches/Branches.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+
+import BranchesDataTable from './BranchesDataTable';
+import withDashboardActions from 'containers/Dashboard/withDashboardActions';
+
+import { compose } from 'utils';
+
+function Branches({
+ // #withDashboardActions
+ changePreferencesPageTitle,
+}) {
+ React.useEffect(() => {
+ changePreferencesPageTitle(intl.get('branches.label'));
+ }, [changePreferencesPageTitle]);
+
+ return ;
+}
+export default compose(withDashboardActions)(Branches);
diff --git a/src/containers/Preferences/Branches/BranchesActions.js b/src/containers/Preferences/Branches/BranchesActions.js
new file mode 100644
index 000000000..a59309d69
--- /dev/null
+++ b/src/containers/Preferences/Branches/BranchesActions.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Button, Intent } from '@blueprintjs/core';
+
+import { FormattedMessage as T, Icon } from 'components';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+function BranchesActions({
+ //#ownProps
+ openDialog,
+}) {
+ const handleClickNewBranche = () => {
+ openDialog('branch-form');
+ };
+
+ return (
+
+ }
+ onClick={handleClickNewBranche}
+ intent={Intent.PRIMARY}
+ >
+
+
+
+ );
+}
+
+export default compose(withDialogActions)(BranchesActions);
diff --git a/src/containers/Preferences/Branches/BranchesAlerts.js b/src/containers/Preferences/Branches/BranchesAlerts.js
new file mode 100644
index 000000000..c444030db
--- /dev/null
+++ b/src/containers/Preferences/Branches/BranchesAlerts.js
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const BranchDeleteAlert = React.lazy(() =>
+ import('../../Alerts/Branches/BranchDeleteAlert'),
+);
+
+export default [{ name: 'branch-delete', component: BranchDeleteAlert }];
diff --git a/src/containers/Preferences/Branches/BranchesDataTable.js b/src/containers/Preferences/Branches/BranchesDataTable.js
new file mode 100644
index 000000000..2f238fafa
--- /dev/null
+++ b/src/containers/Preferences/Branches/BranchesDataTable.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import styled from 'styled-components';
+
+import { DataTable } from 'components';
+import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
+import { useBranchesTableColumns, ActionsMenu } from './components';
+import { useBranchesContext } from './BranchesProvider';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import withAlertActions from 'containers/Alert/withAlertActions';
+
+import { compose } from 'utils';
+
+/**
+ * Branches data table.
+ */
+function BranchesDataTable({
+ // #withDialogAction
+ openDialog,
+
+ // #withAlertActions
+ openAlert,
+}) {
+ // Table columns.
+ const columns = useBranchesTableColumns();
+
+ const { branches, isBranchesLoading, isBranchesFetching } =
+ useBranchesContext();
+
+ const handleEditBranch = ({ id }) => {
+ openDialog('branch-form', { branchId: id, action: 'edit' });
+ };
+
+ const handleDeleteBranch = ({ id }) => {
+ openAlert('branch-delete', { branchId: id });
+ };
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions, withAlertActions)(BranchesDataTable);
+
+const BranchesTable = styled(DataTable)``;
diff --git a/src/containers/Preferences/Branches/BranchesProvider.js b/src/containers/Preferences/Branches/BranchesProvider.js
new file mode 100644
index 000000000..eb6f47f3a
--- /dev/null
+++ b/src/containers/Preferences/Branches/BranchesProvider.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import styled from 'styled-components';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import { Card } from 'components';
+import { useBranches } from 'hooks/query';
+import PreferencesPageLoader from '../PreferencesPageLoader';
+
+const BranchesContext = React.createContext();
+
+/**
+ * Branches data provider.
+ */
+function BranchesProvider({ ...props }) {
+ // Fetches the branches list.
+ const {
+ isLoading: isBranchesLoading,
+ isFetching: isBranchesFetching,
+ data: branches,
+ } = useBranches();
+
+ // Provider state.
+ const provider = {
+ branches,
+ isBranchesLoading,
+ isBranchesFetching,
+ };
+
+ return (
+
+
+
+
+
+ );
+}
+
+const useBranchesContext = () => React.useContext(BranchesContext);
+export { BranchesProvider, useBranchesContext };
+
+const BrachesPreferencesCard = styled(Card)`
+ padding: 0;
+`;
diff --git a/src/containers/Preferences/Branches/components.js b/src/containers/Preferences/Branches/components.js
new file mode 100644
index 000000000..771681fba
--- /dev/null
+++ b/src/containers/Preferences/Branches/components.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { Intent, Menu, MenuDivider, MenuItem } from '@blueprintjs/core';
+
+import { safeCallback } from 'utils';
+import { Icon } from 'components';
+
+/**
+ * Context menu of Branches.
+ */
+export function ActionsMenu({
+ payload: { onEdit, onDelete },
+ row: { original },
+}) {
+ return (
+
+ );
+}
+
+/**
+ * Retrieve branches table columns
+ * @returns
+ */
+export function useBranchesTableColumns() {
+ return React.useMemo(
+ () => [
+ {
+ id: 'name',
+ Header: intl.get('branches.column.branch_name'),
+ accessor: 'name',
+ className: 'name',
+ width: '120',
+ disableSortBy: true,
+ textOverview: true,
+ },
+ {
+ id: 'code',
+ Header: intl.get('branches.column.code'),
+ accessor: 'code',
+ className: 'code',
+ width: '100',
+ disableSortBy: true,
+ textOverview: true,
+ },
+ {
+ Header: intl.get('branches.column.address'),
+ accessor: 'address',
+ className: 'address',
+ width: '180',
+ disableSortBy: true,
+ textOverview: true,
+ },
+ {
+ Header: intl.get('branches.column.phone_number'),
+ accessor: 'phone_number',
+ className: 'phone_number',
+ width: '120',
+ disableSortBy: true,
+ },
+ ],
+ [],
+ );
+}
diff --git a/src/containers/Preferences/Branches/index.js b/src/containers/Preferences/Branches/index.js
new file mode 100644
index 000000000..9bd083d91
--- /dev/null
+++ b/src/containers/Preferences/Branches/index.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { BranchesProvider } from './BranchesProvider';
+import Branches from './Branches';
+
+/**
+ * Branches .
+ */
+export default function BranchesPreferences() {
+ return (
+
+
+
+ );
+}
diff --git a/src/hooks/query/branches.js b/src/hooks/query/branches.js
new file mode 100644
index 000000000..b4cbb7daf
--- /dev/null
+++ b/src/hooks/query/branches.js
@@ -0,0 +1,99 @@
+import { useQueryClient, useMutation } from 'react-query';
+import { useRequestQuery } from '../useQueryRequest';
+import useApiRequest from '../useRequest';
+import t from './types';
+
+// Common invalidate queries.
+const commonInvalidateQueries = (queryClient) => {
+ // Invalidate warehouses.
+ queryClient.invalidateQueries(t.BRANCHES);
+ queryClient.invalidateQueries(t.BRANCH);
+};
+
+/**
+ * Create a new branch.
+ */
+export function useCreateBranch(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation((values) => apiRequest.post('branches', values), {
+ onSuccess: (res, values) => {
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ });
+}
+
+/**
+ * Edits the given branch.
+ */
+export function useEditBranch(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ ([id, values]) => apiRequest.post(`branches/${id}`, values),
+ {
+ onSuccess: (res, [id, values]) => {
+ // Invalidate specific branch.
+ queryClient.invalidateQueries([t.BRANCH, id]);
+
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Deletes the given branch.
+ */
+export function useDeleteBranch(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation((id) => apiRequest.delete(`branches/${id}`), {
+ onSuccess: (res, id) => {
+ // Invalidate specific branch.
+ queryClient.invalidateQueries([t.BRANCH, id]);
+
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ });
+}
+
+/**
+ * Retrieve Branches list.
+ */
+export function useBranches(query, props) {
+ return useRequestQuery(
+ [t.BRANCHES, query],
+ { method: 'get', url: 'branches', params: query },
+ {
+ select: (res) => res.data.branches,
+ defaultData: [],
+ ...props,
+ },
+ );
+}
+
+/**
+ * Retrieve the branch details.
+ * @param {number}
+ */
+export function useBranch(id, props, requestProps) {
+ return useRequestQuery(
+ [t.BRANCH, id],
+ { method: 'get', url: `branches/${id}`, ...requestProps },
+ {
+ select: (res) => res.data.branch,
+ defaultData: {},
+ ...props,
+ },
+ );
+}
diff --git a/src/hooks/query/index.js b/src/hooks/query/index.js
index b27ebd2b5..1d4fa2f8d 100644
--- a/src/hooks/query/index.js
+++ b/src/hooks/query/index.js
@@ -34,3 +34,4 @@ export * from './creditNote';
export * from './vendorCredit';
export * from './transactionsLocking';
export * from './warehouses'
+export * from './branches';
diff --git a/src/hooks/query/types.js b/src/hooks/query/types.js
index 58b34a13e..663ea5679 100644
--- a/src/hooks/query/types.js
+++ b/src/hooks/query/types.js
@@ -199,6 +199,10 @@ const WAREHOUSES = {
const WAREHOUSE_TRANSFERS = {
WAREHOUSE_TRANSFER: 'WAREHOUSE_TRANSFER',
WAREHOUSE_TRANSFERS: 'WAREHOUSE_TRANSFERS',
+}
+const BRANCHES = {
+ BRANCHES: 'BRANCHES',
+ BRANCH: 'BRANCH',
};
export default {
@@ -230,4 +234,5 @@ export default {
...TARNSACTIONS_LOCKING,
...WAREHOUSES,
...WAREHOUSE_TRANSFERS,
+ ...BRANCHES,
};
diff --git a/src/lang/en/index.json b/src/lang/en/index.json
index 72c1d35d2..b5d16acc1 100644
--- a/src/lang/en/index.json
+++ b/src/lang/en/index.json
@@ -1802,5 +1802,27 @@
"select_warehouse_transfer":"Select Warehouse Transfer",
"warehouse_transfer.alert.delete_message":"The warehouse transfer transaction has been deleted successfully",
"warehouse_transfer.once_delete_this_warehouse_transfer":"Once you delete this warehouse transfer, you won't be able to restore it later. Are you sure you want to delete this warehouse transfer?",
- "warehouse_transfer.error.could_not_transfer_item_from_source_to_destination":"Could not transfer item from source to destination on the same warehouse"
+ "warehouse_transfer.error.could_not_transfer_item_from_source_to_destination":"Could not transfer item from source to destination on the same warehouse",
+ "branches.label": "Branches",
+ "branches.label.new_branch": "New Branch",
+ "branches.action.edit_branch": "Edit Branch",
+ "branches.action.delete_branch": "Delete Branch",
+ "branches.column.branch_name": "Branch name",
+ "branches.column.address": "Address",
+ "branches.column.phone_number": "Phone number",
+ "branches.column.code": "Code",
+ "branch.dialog.label_new_branch": "New Branch",
+ "branch.dialog.label_edit_branch": "New Branch",
+ "branch.dialog.label.branch_name": "Branch Name",
+ "branch.dialog.label.branch_code": "Code",
+ "branch.dialog.label.branch_address": "Branch Address",
+ "branch.dialog.label.address_1": "Address",
+ "branch.dialog.label.city": "City",
+ "branch.dialog.label.country": "Country",
+ "branch.dialog.label.phone_number": "Phone Number",
+ "branch.dialog.label.email": "Email",
+ "branch.dialog.label.website": "Website",
+ "branch.dialog.success_message": "The branch has been created successfully.",
+ "branch.alert.delete_message":"The branch has been deleted successfully",
+ "branch.once_delete_this_branch":"Once you delete this branch, you won't be able to restore it later. Are you sure you want to delete this branch?"
}
\ No newline at end of file
diff --git a/src/routes/preferences.js b/src/routes/preferences.js
index b4200758e..bf8949a19 100644
--- a/src/routes/preferences.js
+++ b/src/routes/preferences.js
@@ -8,6 +8,7 @@ import Item from 'containers/Preferences/Item';
import SMSIntegration from '../containers/Preferences/SMSIntegration';
import DefaultRoute from '../containers/Preferences/DefaultRoute';
import Warehouses from '../containers/Preferences/Warehouses';
+import Branches from '../containers/Preferences/Branches';
const BASE_URL = '/preferences';
@@ -42,6 +43,11 @@ export default [
component: Warehouses,
exact: true,
},
+ {
+ path: `${BASE_URL}/branches`,
+ component: Branches,
+ exact: true,
+ },
{
path: `${BASE_URL}/accountant`,
component: Accountant,
diff --git a/src/style/pages/Branches/BranchFormDialog.scss b/src/style/pages/Branches/BranchFormDialog.scss
new file mode 100644
index 000000000..9dd06954f
--- /dev/null
+++ b/src/style/pages/Branches/BranchFormDialog.scss
@@ -0,0 +1,40 @@
+.dialog--branch-form {
+ width: 650px;
+
+ .bp3-dialog-body {
+ .bp3-form-group {
+ margin-bottom: 15px;
+ }
+ .bp3-form-group.bp3-inline {
+ .bp3-label {
+ font-size: 13px;
+ margin-bottom: 3px;
+ min-width: 150px;
+ }
+ .bp3-form-content {
+ width: 278px;
+ }
+ .bp3-control-group > * {
+ flex-shrink: unset;
+ padding-right: 5px;
+ padding-left: 5px;
+
+ &:first-child {
+ padding-left: 0;
+ }
+ &:last-child {
+ padding-right: 0;
+ }
+ }
+
+ &.form-group--branch_address {
+ .bp3-form-content {
+ width: 388px;
+ }
+ }
+ }
+ }
+ .bp3-dialog-footer {
+ padding-top: 10px;
+ }
+}