diff --git a/assets/icons/productcategoryicon.svg b/assets/icons/productcategoryicon.svg new file mode 100644 index 0000000..825fee8 --- /dev/null +++ b/assets/icons/productcategoryicon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/components/Dashboard/Inventory/StockTransfers.jsx b/src/components/Dashboard/Inventory/StockTransfers.jsx index 4d2d9dd..5a6a646 100644 --- a/src/components/Dashboard/Inventory/StockTransfers.jsx +++ b/src/components/Dashboard/Inventory/StockTransfers.jsx @@ -90,7 +90,7 @@ const StockTransfers = () => { open={newOpen} styles={{ content: { paddingBottom: '24px' } }} footer={null} - width={960} + width={740} onCancel={() => { setNewOpen(false) }} diff --git a/src/components/Dashboard/Inventory/StockTransfers/NewStockTransfer.jsx b/src/components/Dashboard/Inventory/StockTransfers/NewStockTransfer.jsx index cbf8f6b..675da7d 100644 --- a/src/components/Dashboard/Inventory/StockTransfers/NewStockTransfer.jsx +++ b/src/components/Dashboard/Inventory/StockTransfers/NewStockTransfer.jsx @@ -1,17 +1,40 @@ import PropTypes from 'prop-types' +import dayjs from 'dayjs' import ObjectInfo from '../../common/ObjectInfo' import NewObjectForm from '../../common/NewObjectForm' import WizardView from '../../common/WizardView' +const defaultTransferName = () => + `Transfer ${dayjs().format('YYYY-MM-DD HH:mm:ss')}` + const NewStockTransfer = ({ onOk, reset }) => { return ( - {({ handleSubmit, submitLoading, objectData }) => { + {({ handleSubmit, submitLoading, objectData, formValid }) => { const steps = [ + { + title: 'Required', + key: 'required', + content: ( + + ) + }, { title: 'Summary', key: 'summary', @@ -20,6 +43,7 @@ const NewStockTransfer = ({ onOk, reset }) => { type='stockTransfer' column={1} bordered={false} + labelWidth={80} visibleProperties={{ _id: false, createdAt: false, @@ -38,7 +62,7 @@ const NewStockTransfer = ({ onOk, reset }) => { { const result = await handleSubmit() diff --git a/src/components/Dashboard/Management/Hosts/HostInfo.jsx b/src/components/Dashboard/Management/Hosts/HostInfo.jsx index aeb5fab..5d74d32 100644 --- a/src/components/Dashboard/Management/Hosts/HostInfo.jsx +++ b/src/components/Dashboard/Management/Hosts/HostInfo.jsx @@ -68,6 +68,10 @@ const HostInfo = () => { finishEdit: () => { objectFormRef?.current.handleUpdate() return true + }, + delete: () => { + objectFormRef?.current?.handleDelete?.() + return true } } diff --git a/src/components/Dashboard/Management/ManagementSidebar.jsx b/src/components/Dashboard/Management/ManagementSidebar.jsx index 96f8609..6881be2 100644 --- a/src/components/Dashboard/Management/ManagementSidebar.jsx +++ b/src/components/Dashboard/Management/ManagementSidebar.jsx @@ -5,6 +5,7 @@ import FilamentSkuIcon from '../../Icons/FilamentSkuIcon' import PartIcon from '../../Icons/PartIcon' import PartSkuIcon from '../../Icons/PartSkuIcon' import ProductIcon from '../../Icons/ProductIcon' +import ProductCategoryIcon from '../../Icons/ProductCategoryIcon' import ProductSkuIcon from '../../Icons/ProductSkuIcon' import VendorIcon from '../../Icons/VendorIcon' import MaterialIcon from '../../Icons/MaterialIcon' @@ -57,6 +58,12 @@ const items = [ label: 'Products', path: '/dashboard/management/products' }, + { + key: 'productCategories', + icon: , + label: 'Product Categories', + path: '/dashboard/management/productcategories' + }, { key: 'productSkus', icon: , @@ -199,6 +206,7 @@ const routeKeyMap = { '/dashboard/management/users': 'users', '/dashboard/management/apppasswords': 'appPasswords', '/dashboard/management/products': 'products', + '/dashboard/management/productcategories': 'productCategories', '/dashboard/management/productskus': 'productSkus', '/dashboard/management/vendors': 'vendors', '/dashboard/management/couriers': 'couriers', diff --git a/src/components/Dashboard/Management/ProductCategories.jsx b/src/components/Dashboard/Management/ProductCategories.jsx new file mode 100644 index 0000000..4a4c8fd --- /dev/null +++ b/src/components/Dashboard/Management/ProductCategories.jsx @@ -0,0 +1,109 @@ +import { useRef, useState } from 'react' +import { Button, Flex, Space, Modal, Dropdown } from 'antd' + +import NewProductCategory from './ProductCategories/NewProductCategory' + +import useColumnVisibility from '../hooks/useColumnVisibility' +import ColumnViewButton from '../common/ColumnViewButton' +import ObjectTable from '../common/ObjectTable' +import PlusIcon from '../../Icons/PlusIcon' +import ReloadIcon from '../../Icons/ReloadIcon' +import ObjectTableViewButton from '../common/ObjectTableViewButton' +import FilterSidebarButton from '../common/FilterSidebarButton' +import useViewMode from '../hooks/useViewMode' +import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility' +import ExportListButton from '../common/ExportListButton' + +const ProductCategories = () => { + const [newProductCategoryOpen, setNewProductCategoryOpen] = useState(false) + const tableRef = useRef() + + const [viewMode, setViewMode] = useViewMode('productCategory') + + const [columnVisibility, setColumnVisibility] = + useColumnVisibility('productCategory') + + const [showFilterSidebar, setShowFilterSidebar] = + useFilterSidebarVisibility('ProductCategories') + + const actionItems = { + items: [ + { + label: 'New Product Category', + key: 'newProductCategory', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + tableRef.current?.reload() + } else if (key === 'newProductCategory') { + setNewProductCategoryOpen(true) + } + } + } + + return ( + <> + + + + + + + + + + + setShowFilterSidebar(!showFilterSidebar)} + /> + + + + + + + { + setNewProductCategoryOpen(false) + }} + > + { + setNewProductCategoryOpen(false) + tableRef.current?.reload() + }} + reset={newProductCategoryOpen} + /> + + + + ) +} + +export default ProductCategories diff --git a/src/components/Dashboard/Management/ProductCategories/NewProductCategory.jsx b/src/components/Dashboard/Management/ProductCategories/NewProductCategory.jsx new file mode 100644 index 0000000..71c2141 --- /dev/null +++ b/src/components/Dashboard/Management/ProductCategories/NewProductCategory.jsx @@ -0,0 +1,68 @@ +import PropTypes from 'prop-types' +import ObjectInfo from '../../common/ObjectInfo' +import NewObjectForm from '../../common/NewObjectForm' +import WizardView from '../../common/WizardView' + +const NewProductCategory = ({ onOk }) => { + return ( + + {({ handleSubmit, submitLoading, objectData, formValid }) => { + const steps = [ + { + title: 'Required', + key: 'required', + content: ( + + ) + }, + { + title: 'Summary', + key: 'summary', + content: ( + + ) + } + ] + return ( + { + const result = await handleSubmit() + if (result) { + onOk() + } + }} + /> + ) + }} + + ) +} + +NewProductCategory.propTypes = { + onOk: PropTypes.func.isRequired, + reset: PropTypes.bool +} + +export default NewProductCategory diff --git a/src/components/Dashboard/Management/ProductCategories/ProductCategoryInfo.jsx b/src/components/Dashboard/Management/ProductCategories/ProductCategoryInfo.jsx new file mode 100644 index 0000000..98841d0 --- /dev/null +++ b/src/components/Dashboard/Management/ProductCategories/ProductCategoryInfo.jsx @@ -0,0 +1,195 @@ +import { useRef, useState } from 'react' +import { useLocation } from 'react-router-dom' +import { Space, Flex, Card } from 'antd' +import useCollapseState from '../../hooks/useCollapseState' +import NotesPanel from '../../common/NotesPanel' +import InfoCollapse from '../../common/InfoCollapse' +import ObjectInfo from '../../common/ObjectInfo' +import ViewButton from '../../common/ViewButton' +import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx' +import NoteIcon from '../../../Icons/NoteIcon.jsx' +import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx' +import ObjectForm from '../../common/ObjectForm' +import EditButtons from '../../common/EditButtons' +import LockIndicator from '../../common/LockIndicator.jsx' +import ActionHandler from '../../common/ActionHandler.jsx' +import ObjectActions from '../../common/ObjectActions.jsx' +import ObjectTable from '../../common/ObjectTable.jsx' +import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' +import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' +import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' +import ScrollBox from '../../common/ScrollBox.jsx' + +const ProductCategoryInfo = () => { + const location = useLocation() + const objectFormRef = useRef(null) + const actionHandlerRef = useRef(null) + const productCategoryId = new URLSearchParams(location.search).get( + 'productCategoryId' + ) + const [collapseState, updateCollapseState] = useCollapseState( + 'ProductCategoryInfo', + { + info: true, + notes: true, + auditLogs: true + } + ) + const [objectFormState, setEditFormState] = useState({ + isEditing: false, + editLoading: false, + formValid: false, + lock: null, + loading: false, + objectData: {} + }) + + const actions = { + edit: () => { + objectFormRef?.current?.startEditing?.() + return false + }, + cancelEdit: () => { + objectFormRef?.current?.cancelEditing?.() + return true + }, + finishEdit: () => { + objectFormRef?.current?.handleUpdate?.() + return true + }, + delete: () => { + objectFormRef?.current?.handleDelete?.() + return true + } + } + + return ( + <> + + + + + + + + + + + + + { + actionHandlerRef.current.callAction('finishEdit') + }} + cancelEditing={() => { + actionHandlerRef.current.callAction('cancelEdit') + }} + startEditing={() => { + actionHandlerRef.current.callAction('edit') + }} + editLoading={objectFormState.editLoading} + formValid={objectFormState.formValid} + disabled={objectFormState.lock?.locked || objectFormState.loading} + loading={objectFormState.editLoading} + /> + + + + + + } + active={collapseState.info} + onToggle={(expanded) => updateCollapseState('info', expanded)} + collapseKey='info' + > + { + setEditFormState((prev) => ({ ...prev, ...state })) + }} + > + {({ loading, isEditing, objectData }) => ( + + )} + + + + } + active={collapseState.notes} + onToggle={(expanded) => updateCollapseState('notes', expanded)} + collapseKey='notes' + > + + + + + } + active={collapseState.auditLogs} + onToggle={(expanded) => + updateCollapseState('auditLogs', expanded) + } + collapseKey='auditLogs' + > + {objectFormState.loading ? ( + + ) : ( + + )} + + + + + + ) +} + +export default ProductCategoryInfo diff --git a/src/components/Dashboard/Management/Users/UserInfo.jsx b/src/components/Dashboard/Management/Users/UserInfo.jsx index 6850247..941de3b 100644 --- a/src/components/Dashboard/Management/Users/UserInfo.jsx +++ b/src/components/Dashboard/Management/Users/UserInfo.jsx @@ -22,6 +22,9 @@ import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import ScrollBox from '../../common/ScrollBox.jsx' import NewAppPassword from '../AppPasswords/NewAppPassword.jsx' +import ObjectProperty from '../../common/ObjectProperty.jsx' +import { getModelProperty } from '../../../../database/ObjectModels.js' +import { useMediaQuery } from 'react-responsive' const UserInfo = () => { const location = useLocation() @@ -44,6 +47,7 @@ const UserInfo = () => { loading: false, objectData: {} }) + const isMobile = useMediaQuery({ maxWidth: 768 }) const actions = { newAppPassword: () => { @@ -147,13 +151,34 @@ const UserInfo = () => { }} > {({ loading, isEditing, objectData }) => ( - } - isEditing={isEditing} - type='user' - objectData={objectData} - /> + +
+ + + +
+ } + isEditing={isEditing} + type='user' + objectData={objectData} + visibleProperties={{ + profileImage: false + }} + /> +
)} diff --git a/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx b/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx index 1776aa9..0b6d71e 100644 --- a/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx +++ b/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx @@ -18,7 +18,7 @@ const NewGCodeFile = ({ onOk, defaultValues }) => { const steps = [ { title: 'Upload', - key: 'uplaod', + key: 'upload', content: ( { isEditing={true} required={true} objectData={objectData} - visibleProperties={{ name: false, filament: false }} + visibleProperties={{ file: true }} showLabels={false} /> ) @@ -57,6 +57,8 @@ const NewGCodeFile = ({ onOk, defaultValues }) => { bordered={false} visibleProperties={{ _id: false, + _reference: false, + parts: false, createdAt: false, updatedAt: false, startedAt: false diff --git a/src/components/Dashboard/common/DashboardBreadcrumb.jsx b/src/components/Dashboard/common/DashboardBreadcrumb.jsx index dd293b9..73e96b5 100644 --- a/src/components/Dashboard/common/DashboardBreadcrumb.jsx +++ b/src/components/Dashboard/common/DashboardBreadcrumb.jsx @@ -12,6 +12,7 @@ const breadcrumbNameMap = { developer: 'Developer', finance: 'Finance', sales: 'Sales', + productcategories: 'Product Categories', overview: 'Overview', info: 'Info', design: 'Design', diff --git a/src/components/Dashboard/common/FileList.jsx b/src/components/Dashboard/common/FileList.jsx index 5bcf97a..a0b2bb8 100644 --- a/src/components/Dashboard/common/FileList.jsx +++ b/src/components/Dashboard/common/FileList.jsx @@ -23,6 +23,7 @@ const FileList = ({ showInfo = true, showDownload = true, defaultPreviewOpen = false, + minimal = false, card = true }) => { const { fetchFileContent, flushFile } = useContext(ApiServerContext) @@ -78,7 +79,7 @@ const FileList = ({ {file.extension} - {showDownload && ( + {showDownload && !minimal && (