Skip to content

Instantly share code, notes, and snippets.

@tanvirraj
Created April 29, 2019 06:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tanvirraj/30e45f41e1ade8b96ec367d514036f9c to your computer and use it in GitHub Desktop.
Save tanvirraj/30e45f41e1ade8b96ec367d514036f9c to your computer and use it in GitHub Desktop.
// @flow
import React, { PureComponent } from "react";
import styles from "./MerchantCatalogScreen.module.scss";
import { connect } from "react-redux";
import { List, Modal, message } from "antd";
import { select } from "../../../../store/store";
import { withNamespaces } from "react-i18next";
import BrowserMeta, {
Themes
} from "../../../../components/BrowserMeta/BrowserMeta";
import BreadcrumbNavigation from "../../../../components/BreadcrumbNavigation/BreadcrumbNavigation";
import { ROUTES } from "../../../../screens/Router/Router.config";
import { formatRoute } from "react-router-named-routes";
import { ConfirmationModal } from "../../../../components/Modal/Modal";
import DashboardContainer from "../../../../components/DashboardContainer/DashboardContainer";
import EmptyState from "../../../../components/EmptyState/EmptyState";
import SidePanelContentLayout from "../../../../layouts/SidePanelContentLayout/SidePanelContentLayout";
import CatalogActionButtons from "./components/CatalogActionButtons/CatalogActionButtons";
import CatalogSideMenu from "../../../../components/CatalogSideMenu/CatalogSideMenu";
import CatalogHeader from "../../../../components/CatalogHeader/CatalogHeader";
import CatalogItemCard from "../../../../components/CatalogItemCard/CatalogItemCard";
import ItemFormSidebar from "../../../../components/ItemFormSidebar/ItemFormSidebar";
import ItemCopySidebar, {
type ItemCopySidebarDataType
} from "./components/ItemCopySidebar/ItemCopySidebar";
import ConfirmationMessage from "../../../../components/ConfirmationMessage/ConfirmationMessage";
import MIcon from "@mdi/react";
import { mdiFileDocumentBoxMultiple } from "@mdi/js";
import { PRIVATE_ROUTER } from "../../../Router/Router.config";
import {
MerchantError,
MerchantConstants
} from "../../../../utils/merchantConst";
import { ItemParentType, CatalogStatus } from "../../../../utils/catalogConst";
import { type TranslateType } from "../../../../types/types";
import { type RouterMatchType } from "../../../../types/types";
import { type MerchantType } from "../../../../types/merchantType";
import { type CatalogType } from "../../../../types/catalogType";
import { type CategoryType } from "../../../../types/catalogType";
import { type ItemType } from "../../../../types/catalogType";
type Props = {
/** redux: merchant */
merchant: MerchantType,
/** redux: catalog */
catalog: CatalogType,
/** redux: selected content */
selectedContent: CatalogType | CategoryType,
/** redux: fetch merchant catalog from API */
getCatalog: Function,
/** redux: fetch active catalog for a merchant */
getActiveCatalog: Function,
/** redux: fetch merchant from API */
getMerchantById: Function,
/** redux: Create item */
createItem: Function,
/** redux: Edit item */
editItem: Function,
/** redux: Delete item */
deleteItem: Function,
/** redux: Copy item */
copyItemToCategory: Function,
/** redux: Update item status */
updateItemStatus: Function,
/** redux: loading fetch from API */
loading: boolean,
/** router: url parameter */
match: RouterMatchType,
/** router: current url */
location: any,
/** router: router history */
history: any,
/** redux: catalog error*/
error: string
} & TranslateType;
type State = {
itemDisabledModalVisible: boolean,
itemCopySidebarVisible: boolean,
showItemFormSidebar: boolean,
showConfirmationMessage: boolean,
targetCategoryName: string,
selectedItem: ?ItemType
};
/**
* Merchant Catalog
*/
class MerchantCatalogScreen extends PureComponent<Props, State> {
state = {
itemDisabledModalVisible: false,
itemCopySidebarVisible: false,
showConfirmationMessage: false,
showItemFormSidebar: false,
targetCategoryName: "",
selectedItem: null
};
componentDidMount() {
const { match, getCatalog, getActiveCatalog, getMerchantById } = this.props;
const { params } = match;
// Get the merchant and catalog id from the url id parameter
const merchantId = params.id;
const catalogId = params.catalogId;
// When there is no catalog id, fetch the active catalog for the merchant
if (catalogId == null) {
// TODO: Remove when catalogId available in merchant
getActiveCatalog(merchantId);
} else if (merchantId && catalogId) {
// Fetch merchant's catalog on first load
getCatalog(merchantId, catalogId);
}
if (merchantId) {
getMerchantById(merchantId);
}
}
componentDidUpdate(prevProps) {
const { match, history, catalog, error } = this.props;
const { params } = match;
const merchantId = params.id;
const catalogId = params.catalogId;
if (error && error === MerchantError.NO_CATALOG && !prevProps.error) {
Modal.error({
title: "No active catalog found for the merchant",
onOk: () => {
history.replace(PRIVATE_ROUTER.ACCOUNT_MANAGER_DASHBOARD.path);
}
});
}
// When there is no catalog id in the url, but a catalog id has been fetched
if (
(catalogId == null && catalog.id) ||
(catalog.id && catalog.id !== prevProps.catalog.id)
) {
// TODO: Remove when catalog id always exist for a merchant
// Replace route to add the fetched catalog id to the url
const newRoute = formatRoute(PRIVATE_ROUTER.MERCHANT_CATALOG.path, {
id: merchantId,
catalogId: catalog.id
});
history.replace(newRoute);
}
}
// Open sidebars and set/remove selected item
openSidebar = (stateKey: string, selectedItem?: ItemType) => {
this.setState({
[stateKey]: true,
selectedItem: selectedItem,
showConfirmationMessage: false
});
};
getCurrentCategoryInfo = () => {
const { selectedContent, catalog } = this.props;
let parentInfo = {};
let categoryId = null;
let subCategoryId = null;
if (!selectedContent.id || selectedContent.merchant_id) {
// catalog
parentInfo.parentId = catalog.id;
parentInfo.parentType = ItemParentType.CATALOG;
} else {
// category
parentInfo.parentId = selectedContent.id;
parentInfo.parentType = ItemParentType.CATEGORY;
if (selectedContent.parent_id) {
categoryId = selectedContent.parent_id;
subCategoryId = selectedContent.id;
} else categoryId = selectedContent.id;
}
return { categoryId, subCategoryId };
};
// Submit new or edited item from sidebar
onItemSubmit = (item: ItemType) => {
const { selectedContent, catalog, createItem, editItem } = this.props;
const { selectedItem } = this.state;
let parentInfo = {};
let categoryId = null;
let subCategoryId = null;
if (!selectedContent.id || selectedContent.merchant_id) {
// catalog
parentInfo.parentId = catalog.id;
parentInfo.parentType = ItemParentType.CATALOG;
} else {
// category
parentInfo.parentId = selectedContent.id;
parentInfo.parentType = ItemParentType.CATEGORY;
if (selectedContent.parent_id) {
categoryId = selectedContent.parent_id;
subCategoryId = selectedContent.id;
} else categoryId = selectedContent.id;
}
if (item.id)
editItem(
catalog.id,
{
...item,
parent_item_id: selectedItem && selectedItem.parent_item_id,
global_item_id: item.id //TODO: Add this global_item_id conditionally
},
categoryId,
subCategoryId
);
else {
createItem(
catalog.id,
{ ...item, ...parentInfo },
categoryId,
subCategoryId
);
}
this.resetSidebars();
};
// Item is confirmed to be deleted
onItemDelete = (item: ItemType) => {
const { catalog, deleteItem, selectedContent } = this.props;
let categoryId = null;
let subCategoryId = null;
if (!selectedContent.merchant_id) {
if (selectedContent.parent_id) {
categoryId = selectedContent.parent_id;
subCategoryId = selectedContent.id;
} else categoryId = selectedContent.id;
}
deleteItem(
catalog.id,
item.id,
item.parent_item_id,
categoryId,
subCategoryId
);
this.resetSidebars();
};
// Item is confirmed to be copied
onItemCopy = (data: ItemCopySidebarDataType) => {
const { catalog, copyItemToCategory } = this.props;
const { selectedItem } = this.state;
let targetCategoryName = "";
const foundCategory = catalog.categories.find(
c => c.id === data.categoryId
);
if (!!foundCategory) {
if (data.subCategoryId) {
const foundSubCategory =
foundCategory.sub_categories &&
foundCategory.sub_categories.find(
subcat => subcat.id === data.subCategoryId
);
if (foundSubCategory) targetCategoryName = foundSubCategory.name;
} else targetCategoryName = foundCategory.name;
}
const categoryId = data.subCategoryId || data.categoryId;
if (selectedItem)
copyItemToCategory(
catalog.id,
selectedItem.parent_item_id,
categoryId
).then(success => {
if (success) {
this.setState({
showConfirmationMessage: true,
targetCategoryName
});
} else {
message.error("Copy Item Failed!");
}
});
this.setState({
itemCopySidebarVisible: false
});
};
// Change status of item
onItemChangeStatus = (item: ItemType, status: boolean) => {
// User needs to confirm when disabling item
// show confirmation modal
if (status === false) {
this.openSidebar("itemDisabledModalVisible", item);
} else {
const { updateItemStatus, catalog } = this.props;
const { categoryId, subCategoryId } = this.getCurrentCategoryInfo();
if (item) {
const itemStatusEnabled = 1;
updateItemStatus(
catalog.id,
categoryId,
subCategoryId,
item.parent_item_id,
itemStatusEnabled
);
}
}
};
// Item is confirmed to be disabled
onItemDisabledConfirm = () => {
const { selectedItem } = this.state;
const { updateItemStatus, catalog } = this.props;
const { categoryId, subCategoryId } = this.getCurrentCategoryInfo();
if (selectedItem) {
const itemStatusDisabled = 0;
updateItemStatus(
catalog.id,
categoryId,
subCategoryId,
selectedItem.parent_item_id,
itemStatusDisabled
);
}
this.resetSidebars();
};
// Close all sidebars and reset selected item
resetSidebars = () => {
this.setState({
showItemFormSidebar: false,
itemCopySidebarVisible: false,
itemDisabledModalVisible: false,
showConfirmationMessage: false,
selectedItem: null
});
};
render() {
const { t, selectedContent, merchant, catalog } = this.props;
const {
itemDisabledModalVisible,
itemCopySidebarVisible,
showItemFormSidebar,
showConfirmationMessage,
targetCategoryName,
selectedItem
} = this.state;
return (
<>
<BrowserMeta
title={t("merchantCatalog.title")}
theme={Themes.INTERNAL}
/>
{showConfirmationMessage && selectedItem && (
<ConfirmationMessage
onClose={this.resetSidebars}
type="success"
message={t(
`'${
selectedItem.name_localized
}' was copied to ${targetCategoryName} successfully`
)}
/>
)}
{merchant && (
<BreadcrumbNavigation
showSeparatorAfterLastItem
breadcrumbs={[
{
label: t("default.breadcrumbHome"),
link: ROUTES.ACCOUNT_MANAGER_DASHBOARD.path
},
{
label: merchant.name,
link: formatRoute(ROUTES.MERCHANT_PROFILE.path, {
id: merchant.id
})
}
]}
/>
)}
<DashboardContainer
title={catalog.name}
actions={
merchant &&
catalog && (
<CatalogActionButtons catalog={catalog} merchant={merchant} />
)
}
>
<SidePanelContentLayout
sidePanel={
<CatalogSideMenu
onClickNewItem={() => this.openSidebar("showItemFormSidebar")}
/>
}
header={
<CatalogHeader
title={
selectedContent.merchant_id
? t("merchantCatalog.itemsLabel")
: selectedContent.name_localized
}
onClickNewItem={() => this.openSidebar("showItemFormSidebar")}
/>
}
>
<List
dataSource={selectedContent.items}
renderItem={(item, index) => (
<CatalogItemCard
id={item.parent_item_id}
image={item.image}
name={item.name_localized}
price={item.price}
active={
item.parent_status_id === CatalogStatus.ACTIVE &&
item.status_id === CatalogStatus.ACTIVE
}
itemDisabled={
item.parent_status_id === CatalogStatus.OFFLINE ||
item.parent_status_id === CatalogStatus.INACTIVE
}
currency={item.currency}
onEdit={() => this.openSidebar("showItemFormSidebar", item)}
onDuplicate={() =>
this.openSidebar("itemCopySidebarVisible", item)
}
onChangeStatus={status =>
this.onItemChangeStatus(item, status)
}
/>
)}
locale={{
emptyText: (
<EmptyState
icon={
<MIcon
color="currentColor"
path={mdiFileDocumentBoxMultiple}
size={"100px"}
/>
}
className={styles.emptyItemContainer}
text={t("merchantCatalog.emptyStateText")}
/>
)
}}
/>
</SidePanelContentLayout>
</DashboardContainer>
{/* Disable item for today (or indefinitely) */}
<ConfirmationModal
visible={itemDisabledModalVisible}
title={t("merchantCatalog.confirmationDisableItemTitle")}
body={t("merchantCatalog.confirmationDisableItemText")}
checkboxLabel={t(
"merchantCatalog.confirmationDisableItemCheckboxLabel"
)}
onConfirm={this.onItemDisabledConfirm}
onClose={this.resetSidebars}
/>
{/* Create or Edit Item sidebar */}
{merchant && (
<ItemFormSidebar
visible={showItemFormSidebar}
currency={
merchant.currency
? merchant.currency.code
: MerchantConstants.DEFAULT_CURRENCY.code
}
data={selectedItem}
onSubmit={this.onItemSubmit}
onDelete={this.onItemDelete}
onClose={this.resetSidebars}
/>
)}
{/* Copy Item sidebar */}
<ItemCopySidebar
visible={itemCopySidebarVisible}
categories={catalog.categories}
item={selectedItem}
onSubmit={item => this.onItemCopy(item)}
onClose={this.resetSidebars}
/>
</>
);
}
}
const selection = select(state => ({
merchant: state.merchantModel.merchant
}));
const mapState = (state, ownProps) => ({
catalog: state.catalogModel.catalog,
selectedContent: state.catalogModel.selectedContent,
loading: state.catalogModel.loading,
error: state.catalogModel.error,
...selection(state, ownProps)
});
const mapDispatch = dispatch => ({
getActiveCatalog: merchantId =>
dispatch.catalogModel.getActiveCatalog({ merchantId }),
getCatalog: (merchantId, catalogId) =>
dispatch.catalogModel.getCatalog({ merchantId, catalogId }),
getMerchantById: merchantId =>
dispatch.merchantModel.getMerchantById({ merchantId }),
createItem: (catalogId, item, categoryId, subCategoryId) =>
dispatch.catalogModel.createItem({
catalogId,
item,
categoryId,
subCategoryId
}),
editItem: (catalogId, item, categoryId, subCategoryId) =>
dispatch.catalogModel.editItem({
catalogId,
item,
categoryId,
subCategoryId
}),
deleteItem: (catalogId, itemId, parentItemId, categoryId, subCategoryId) =>
dispatch.catalogModel.deleteItem({
catalogId,
itemId,
parentItemId,
categoryId,
subCategoryId
}),
copyItemToCategory: (catalogId, itemId, categoryId) =>
dispatch.catalogModel.copyItemToCategory({
catalogId,
itemId,
categoryId
}),
updateItemStatus: (
catalogId,
categoryId,
subCategoryId,
itemParentId,
status
) =>
dispatch.catalogModel.updateItemStatus({
catalogId,
categoryId,
subCategoryId,
itemParentId,
status
})
});
export default withNamespaces()(
connect(
mapState,
mapDispatch
)(MerchantCatalogScreen)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment