refactor: customers alerts.

This commit is contained in:
elforjani3
2021-01-27 20:01:33 +02:00
parent 29259f460d
commit 25d4b558b6
13 changed files with 311 additions and 213 deletions

View File

@@ -0,0 +1,81 @@
import React, { useCallback, useState } from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { transformErrors } from 'containers/Customers/utils';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import { compose } from 'utils';
/**
* Customer bulk delete alert.
*/
function CustomerBulkDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { customersIds },
// #withCustomersActions
requestDeleteBulkCustomers,
// #withAlertActions
closeAlert,
}) {
const { formatMessage } = useIntl();
const [isLoading, setLoading] = useState(false);
// handle cancel delete alert.
const handleCancelDeleteAlert = () => {
closeAlert(name);
};
console.log(customersIds, 'EE');
// Handle confirm customers bulk delete.
const handleConfirmBulkDelete = useCallback(() => {
setLoading(true);
requestDeleteBulkCustomers(customersIds)
.then(() => {
AppToaster.show({
message: formatMessage({
id: 'the_customers_has_been_deleted_successfully',
}),
intent: Intent.SUCCESS,
});
})
.catch((errors) => {
transformErrors(errors);
})
.finally(() => {
setLoading(false);
closeAlert(name);
});
}, [requestDeleteBulkCustomers, customersIds, formatMessage]);
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelDeleteAlert}
onConfirm={handleConfirmBulkDelete}
loading={isLoading}
>
<p>
<T id={'once_delete_these_customers_you_will_not_able_restore_them'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
withCustomersActions,
)(CustomerBulkDeleteAlert);

View File

@@ -0,0 +1,87 @@
import React, { useCallback, useState } from 'react';
import {
FormattedMessage as T,
FormattedHTMLMessage,
useIntl,
} from 'react-intl';
import { Intent, Alert } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { AppToaster } from 'components';
import { transformErrors } from 'containers/Customers/utils';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import { compose } from 'utils';
/**
* Customer delete alert.
*/
function CustomerDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { customerId },
// #withCustomersActions
requestDeleteCustomer,
// #withAlertActions
closeAlert,
}) {
const { formatMessage } = useIntl();
const [isLoading, setLoading] = useState(false);
// handle cancel delete alert.
const handleCancelDeleteAlert = () => {
closeAlert(name);
};
// handle confirm delete customer.
const handleConfirmDeleteCustomer = useCallback(() => {
setLoading(true);
requestDeleteCustomer(customerId)
.then(() => {
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_deleted_successfully',
}),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('customers-table');
})
.catch((errors) => {
transformErrors(errors);
})
.finally(() => {
setLoading(false);
closeAlert(name);
});
}, [requestDeleteCustomer, customerId, formatMessage]);
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelDeleteAlert}
onConfirm={handleConfirmDeleteCustomer}
loading={isLoading}
>
<p>
<FormattedHTMLMessage
id={'once_delete_this_customer_you_will_able_to_restore_it'}
/>
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
withCustomersActions,
)(CustomerDeleteAlert);

View File

@@ -27,6 +27,7 @@ function ItemBulkDeleteAlert({
}) {
const { formatMessage } = useIntl();
const [isLoading, setLoading] = useState(false);
// handle cancel item bulk delete alert.
const handleCancelBulkDelete = () => {
closeAlert(name);
@@ -49,7 +50,6 @@ function ItemBulkDeleteAlert({
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
@@ -61,6 +61,7 @@ function ItemBulkDeleteAlert({
isOpen={isOpen}
onCancel={handleCancelBulkDelete}
onConfirm={handleConfirmBulkDelete}
loading={isLoading}
>
<p>
<T id={'once_delete_these_items_you_will_not_able_restore_them'} />

View File

@@ -14,33 +14,31 @@ import classNames from 'classnames';
import { connect } from 'react-redux';
import { useHistory } from 'react-router-dom';
import Icon from 'components/Icon';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import FilterDropdown from 'components/FilterDropdown';
import { If, DashboardActionViewsList } from 'components';
import { If, Icon, DashboardActionViewsList } from 'components';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withCustomers from 'containers/Customers/withCustomers';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
const CustomerActionsBar = ({
// #withResourceDetail
resourceFields,
// #withCustomers
customersViews,
customersSelectedRows,
//#withCustomersActions
addCustomersTableQueries,
changeCustomerView,
// #withAlertActions
openAlert,
// #ownProps
selectedRows = [],
onFilterChanged,
onBulkDelete,
}) => {
const [filterCount, setFilterCount] = useState(0);
const history = useHistory();
const { formatMessage } = useIntl();
@@ -48,14 +46,10 @@ const CustomerActionsBar = ({
history.push('/customers/new');
}, [history]);
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows,
]);
const handleBulkDelete = useCallback(() => {
onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
}, [onBulkDelete, selectedRows]);
// Handle Customers bulk delete button click.,
const handleBulkDelete = () => {
openAlert('customers-bulk-delete', { customersIds: customersSelectedRows });
};
const handleTabChange = (viewId) => {
changeCustomerView(viewId.id || -1);
@@ -88,18 +82,12 @@ const CustomerActionsBar = ({
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={
filterCount <= 0 ? (
<T id={'filter'} />
) : (
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
)
}
text={`${formatMessage({ id: 'filters_applied' })}`}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<If condition={hasSelectedRows}>
<If condition={customersSelectedRows.length}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
@@ -134,7 +122,9 @@ export default compose(
withResourceDetail(({ resourceFields }) => ({
resourceFields,
})),
withCustomers(({ customersViews }) => ({
withCustomers(({ customersViews, customersSelectedRows }) => ({
customersViews,
customersSelectedRows,
})),
withAlertActions,
)(CustomerActionsBar);

View File

@@ -204,7 +204,6 @@ const CustomerTable = ({
noInitialFetch={true}
columns={columns}
data={customers}
// loading={customersLoading}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import CustomerDeleteAlert from 'containers/Alerts/Customers/CustomerDeleteAlert';
import CustomerBulkDeleteAlert from 'containers/Alerts/Customers/CustomerBulkDeleteAlert';
/**
* Customers alert.
*/
export default function ItemsAlerts() {
return (
<div>
<CustomerDeleteAlert name={'customer-delete'} />
<CustomerBulkDeleteAlert name={'customers-bulk-delete'} />
</div>
);
}

View File

@@ -1,21 +1,13 @@
import React, { useEffect, useCallback, useState, useMemo } from 'react';
import { Route, Switch, useHistory } from 'react-router-dom';
import { Intent, Alert } from '@blueprintjs/core';
import React, { useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import {
FormattedMessage as T,
FormattedHTMLMessage,
useIntl,
} from 'react-intl';
import { FormattedMessage as T, useIntl } from 'react-intl';
import AppToaster from 'components/AppToaster';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import CustomersTable from 'containers/Customers/CustomerTable';
import CustomerActionsBar from 'containers/Customers/CustomerActionsBar';
import CustomersViewsTabs from 'containers/Customers/CustomersViewsTabs';
import CustomersAlerts from 'containers/Customers/CustomersAlerts';
import CustomersViewPage from 'containers/Customers/CustomersViewPage';
import withCustomers from 'containers/Customers/withCustomers';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
@@ -35,26 +27,21 @@ function CustomersList({
// #withResourceActions
requestFetchResourceViews,
requestFetchResourceFields,
// #withCustomers
customersTableQuery,
// #withAlertsActions.
openAlert,
// #withCustomersActions
requestFetchCustomers,
requestDeleteCustomer,
requestDeleteBulkCustomers,
addCustomersTableQueries,
}) {
const [deleteCustomer, setDeleteCustomer] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
const [tableLoading, setTableLoading] = useState(false);
const [bulkDelete, setBulkDelete] = useState(false);
const { formatMessage } = useIntl();
const history = useHistory();
useEffect(() => {
changePageTitle(formatMessage({ id: 'customers_list' }));
}, [changePageTitle, formatMessage]);
@@ -70,178 +57,23 @@ function CustomersList({
(key, query) => requestFetchCustomers({ ...query }),
);
const handleEditCustomer = useCallback(
(customer) => {
history.push(`/customers/${customer.id}/edit`);
},
[history],
);
// Handle click delete customer.
const handleDeleteCustomer = useCallback(
(customer) => {
setDeleteCustomer(customer);
},
[setDeleteCustomer],
);
// Handle cancel delete the customer.
const handleCancelDeleteCustomer = useCallback(() => {
setDeleteCustomer(false);
}, [setDeleteCustomer]);
const transformErrors = (errors) => {
if (errors.some((e) => e.type === 'CUSTOMER.HAS.SALES_INVOICES')) {
AppToaster.show({
message: formatMessage({
id: 'customer_has_sales_invoices',
}),
intent: Intent.DANGER,
});
}
};
// handle confirm delete customer.
const handleConfirmDeleteCustomer = useCallback(() => {
requestDeleteCustomer(deleteCustomer.id)
.then(() => {
setDeleteCustomer(false);
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_deleted_successfully',
}),
intent: Intent.SUCCESS,
});
})
.catch((errors) => {
setDeleteCustomer(false);
transformErrors(errors);
});
}, [requestDeleteCustomer, deleteCustomer, formatMessage]);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback(
(customer) => {
setSelectedRows(customer);
},
[setSelectedRows],
);
useEffect(() => {
if (tableLoading && !fetchCustomers.isFetching) {
setTableLoading(false);
}
}, [tableLoading, fetchCustomers.isFetching]);
// Calculates the data table selected rows count.
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
selectedRows,
]);
// Handle Customers bulk delete button click.,
const handleBulkDelete = useCallback(
(customersIds) => {
setBulkDelete(customersIds);
},
[setBulkDelete],
);
// Handle cancel cusomters bulk delete.
const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false);
}, []);
const transformApiErrors = (errors) => {
if (
errors.find(
(error) => error.type === 'SOME.CUSTOMERS.HAVE.SALES_INVOICES',
)
) {
AppToaster.show({
message: formatMessage({
id: 'some_customers_have_sales_invoices',
}),
intent: Intent.DANGER,
});
}
};
// Handle confirm customers bulk delete.
const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkCustomers(bulkDelete)
.then(() => {
setBulkDelete(false);
AppToaster.show({
message: formatMessage({
id: 'the_customers_has_been_deleted_successfully',
}),
intent: Intent.SUCCESS,
});
})
.catch((errors) => {
transformApiErrors(errors);
setBulkDelete(false);
});
}, [requestDeleteBulkCustomers, bulkDelete, formatMessage]);
return (
<DashboardInsider
loading={fetchResourceViews.isFetching}
name={'customers-list'}
>
<CustomerActionsBar
selectedRows={selectedRows}
onBulkDelete={handleBulkDelete}
/>
<CustomerActionsBar />
<DashboardPageContent>
<Switch>
<Route
exact={true}
path={['/customers/:custom_view_id/custom_view', '/customers']}
>
<CustomersViewsTabs />
<CustomersTable
onDeleteCustomer={handleDeleteCustomer}
onEditCustomer={handleEditCustomer}
onSelectedRowsChange={handleSelectedRowsChange}
/>
</Route>
</Switch>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={deleteCustomer}
onCancel={handleCancelDeleteCustomer}
onConfirm={handleConfirmDeleteCustomer}
>
<p>
<FormattedHTMLMessage
id={'once_delete_this_customer_you_will_able_to_restore_it'}
/>
</p>
</Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={`${formatMessage({
id: 'delete',
})} (${selectedRowsCount})`}
icon="trash"
intent={Intent.DANGER}
isOpen={bulkDelete}
onCancel={handleCancelBulkDelete}
onConfirm={handleConfirmBulkDelete}
>
<p>
<T
id={'once_delete_these_customers_you_will_not_able_restore_them'}
/>
</p>
</Alert>
<CustomersViewPage />
</DashboardPageContent>
<CustomersAlerts />
</DashboardInsider>
);
}

View File

@@ -0,0 +1,61 @@
import React, { useCallback } from 'react';
import { Route, Switch, useHistory } from 'react-router-dom';
import CustomersViewsTabs from 'containers/Customers/CustomersViewsTabs';
import CustomersTable from 'containers/Customers/CustomerTable';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
function CustomersViewPage({
// #withAlertsActions.
openAlert,
// #withCustomersActions
setSelectedRowsCustomers,
}) {
const history = useHistory();
// Handle click delete customer.
const handleDeleteCustomer = useCallback(
({ id }) => {
openAlert('customer-delete', { customerId: id });
},
[openAlert],
);
// Handle select customer rows.
const handleSelectedRowsChange = (selectedRows) => {
const selectedRowsIds = selectedRows.map((r) => r.id);
setSelectedRowsCustomers(selectedRowsIds);
};
const handleEditCustomer = useCallback(
(customer) => {
history.push(`/customers/${customer.id}/edit`);
},
[history],
);
return (
<Switch>
<Route
exact={true}
path={['/customers/:custom_view_id/custom_view', '/customers']}
>
<CustomersViewsTabs />
<CustomersTable
onDeleteCustomer={handleDeleteCustomer}
onEditCustomer={handleEditCustomer}
onSelectedRowsChange={handleSelectedRowsChange}
/>
</Route>
</Switch>
);
}
export default compose(
withAlertsActions,
withCustomersActions,
)(CustomersViewPage);

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { formatMessage } from 'services/intl';
export const transformErrors = (errors) => {
if (errors.some((e) => e.type === 'CUSTOMER.HAS.SALES_INVOICES')) {
AppToaster.show({
message: formatMessage({
id: 'customer_has_sales_invoices',
}),
intent: Intent.DANGER,
});
}
if (
errors.find((error) => error.type === 'SOME.CUSTOMERS.HAVE.SALES_INVOICES')
) {
AppToaster.show({
message: formatMessage({
id: 'some_customers_have_sales_invoices',
}),
intent: Intent.DANGER,
});
}
};

View File

@@ -15,7 +15,6 @@ export default (mapState) => {
const mapStateToProps = (state, props) => {
const query = getCustomerTableQuery(state, props);
const mapped = {
customers: getCustomersList(state, props, query),
customersViews: getResourceViews(state, props, 'customers'),
@@ -24,7 +23,7 @@ export default (mapState) => {
customersLoading: state.customers.loading,
customersItems: state.customers.items,
customersCurrentViewId: getCustomersCurrentViewId(state, props),
// customerErrors: state.customers.errors,
customersSelectedRows: state.customers.selectedRows,
};
return mapState ? mapState(mapped, state, props) : mapped;
};

View File

@@ -27,6 +27,11 @@ export const mapDispatchToProps = (dispatch) => ({
currentViewId: parseInt(id, 10),
});
},
setSelectedRowsCustomers: (selectedRows) =>
dispatch({
type: t.CUSTOMER_SELECTED_ROWS_SET,
payload: { selectedRows },
}),
});
export default connect(null, mapDispatchToProps);

View File

@@ -10,7 +10,7 @@ const initialState = {
views: {},
loading: false,
currentViewId: -1,
selectedRows: [],
// Responsible for data fetch query based on this query.
tableQuery: {
page_size: 12,
@@ -49,7 +49,6 @@ export default createReducer(initialState, {
state.views[viewId] = {
...view,
pages: {
...(state.views?.[viewId]?.pages || {}),
[paginationMeta.page]: {
ids: customers.map((i) => i.id),
@@ -85,11 +84,14 @@ export default createReducer(initialState, {
});
state.items = items;
},
[t.CUSTOMER_SELECTED_ROWS_SET]: (state, action) => {
const { selectedRows } = action.payload;
state.selectedRows = selectedRows;
},
...viewPaginationSetReducer(t.CUSTOMERS_PAGINATION_SET),
...createTableQueryReducers('CUSTOMERS'),
});
export const getCustomerById = (state, id) => {
return state.customers.items[id];
};
};

View File

@@ -8,4 +8,5 @@ export default {
CUSTOMERS_BULK_DELETE: 'CUSTOMERS_BULK_DELETE',
CUSTOMERS_PAGINATION_SET: 'CUSTOMERS_PAGINATION_SET',
CUSTOMERS_SET_CURRENT_VIEW: 'CUSTOMERS_SET_CURRENT_VIEW',
CUSTOMER_SELECTED_ROWS_SET: 'CUSTOMER_SELECTED_ROWS_SET',
};