Add Payment and Fulfillment Policy Components with SVG Icons
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Introduced new components for managing Payment Policies and Fulfillment Policies, enhancing the dashboard's functionality for financial management. - Added corresponding SVG icons for Payment Policy, Fulfillment Policy, and Return Policy to improve visual representation. - Updated App.css with new styles for file gallery previews, ensuring a cohesive layout and user experience across the dashboard. - Implemented new object forms and tables for Payment Policies, allowing for better data handling and user interaction.
This commit is contained in:
parent
6cc6bf9a79
commit
26132d206c
14
assets/icons/fulfillmentpolicyicon.svg
Normal file
14
assets/icons/fulfillmentpolicyicon.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.607884,0,0,0.607884,2,6.470704)">
|
||||
<path d="M50.153,64.047L37.296,64.047C35.825,69.222 31.078,73.031 25.453,73.031C19.816,73.031 15.034,69.222 13.552,64.047L10.547,64.047C3.781,64.047 0,60.266 0,53.516L0,11.688C0,4.938 3.781,1.141 10.547,1.141L57.859,1.141C64.609,1.141 68.391,4.938 68.391,11.688L68.391,18.422L76.766,18.422C79.761,18.422 81.953,19.046 83.846,20.626C83.618,20.616 83.389,20.612 83.159,20.611L83.154,20.611C78.507,20.611 74.687,22.221 70.737,26.132L70.727,26.142L60.359,36.468L60.359,12.328C60.359,10.141 59.281,9.141 57.172,9.141L11.203,9.141C9.078,9.141 8.031,10.141 8.031,12.328L8.031,52.859C8.031,55.062 9.078,56.031 11.203,56.031L13.984,56.031C15.828,51.524 20.28,48.344 25.453,48.344C30.614,48.344 35.037,51.524 36.867,56.031L47.85,56.031C47.915,58.873 48.681,61.577 50.153,64.047ZM25.453,66.422C28.609,66.422 31.156,63.828 31.156,60.656C31.156,57.5 28.609,54.906 25.453,54.906C22.281,54.906 19.688,57.5 19.688,60.656C19.688,63.828 22.281,66.422 25.453,66.422Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.319305,0,0,0.319305,12.718629,15.454198)">
|
||||
<rect x="0" y="0" width="61.719" height="58.219" style="fill-opacity:0;"/>
|
||||
<path d="M24.594,58.219C27.5,58.219 29.781,57.156 31.312,54.875L59.969,12.312C61.062,10.719 61.531,9.062 61.531,7.625C61.531,3.5 58.25,0.375 54,0.375C51.125,0.375 49.281,1.406 47.5,4.156L24.469,40.125L13.062,27C11.562,25.281 9.812,24.469 7.438,24.469C3.156,24.469 0,27.594 0,31.75C0,33.625 0.5,35.125 2.125,36.969L18.188,55.406C19.906,57.344 21.969,58.219 24.594,58.219Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.515011,0,0,0.515011,34.08479,22)">
|
||||
<path d="M0.001,35.371C-0.038,39.202 1.473,42.727 4.441,45.671L16.349,57.61C11.913,60.839 9.844,64.493 9.844,69.059C9.844,76.236 15.431,81.432 22.42,81.549C26.022,81.612 29.343,80.305 31.751,77.938L53.883,55.695C59.804,49.751 59.453,42.361 52.939,35.83L41.879,24.794L44.775,21.89C47.234,19.407 48.675,16.156 48.692,12.936C48.785,5.665 43.16,0.007 35.85,0C31.88,0 28.667,1.489 25.293,4.83L3.649,26.387C1.324,28.753 0.032,31.925 0.001,35.371ZM18.613,24.255L31.665,11.203C33.369,9.474 34.298,9.018 35.85,9.018C37.991,9.018 39.664,10.692 39.664,12.825C39.664,13.85 39.304,14.61 38.379,15.518L24.077,29.836C23.14,27.191 21.326,25.315 18.613,24.255ZM9.135,35.594C9.135,33.425 10.709,31.799 12.756,31.799C14.857,31.799 16.468,33.464 16.468,35.669C16.468,36.68 16.444,37.157 16.216,37.941C15.03,43.335 21.775,44.881 24.421,42.268L35.475,31.174L46.542,42.227C49.615,45.227 49.817,47.01 47.511,49.299L33.566,63.268C32.715,61.558 31.327,59.892 29.126,57.643L11.994,40.417C9.701,38.148 9.135,37.157 9.135,35.594ZM18.887,69.149C18.887,67.295 20.04,65.793 22.846,64.06L23.303,64.516C25.504,66.773 26.164,67.845 26.172,69.173C26.22,71.078 24.636,72.476 22.444,72.476C20.445,72.476 18.887,71.13 18.887,69.149Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
10
assets/icons/paymentpolicyicon.svg
Normal file
10
assets/icons/paymentpolicyicon.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.81149,0,0,0.81149,2,10.044134)">
|
||||
<path d="M42.914,54.093L10.251,54.093C3.506,54.093 0,50.638 0,43.969L0,10.169C0,3.501 3.506,0.02 10.251,0.02L63.687,0.02C70.463,0.02 73.938,3.501 73.938,10.169L73.938,20.595C73.272,16.95 70.992,13.955 67.816,12.338L67.816,10.709C67.816,7.667 66.186,6.142 63.332,6.142L10.606,6.142C7.701,6.142 6.122,7.667 6.122,10.709L6.122,12.998L55.627,12.998C54.744,13.579 53.871,14.299 52.989,15.172L52.981,15.179L47.835,20.305L6.122,20.305L6.122,43.429C6.122,46.471 7.701,47.97 10.606,47.97L41.376,47.97L44.616,51.219C43.884,52.13 43.321,53.086 42.914,54.093ZM14.864,42.322C13.02,42.322 11.801,41.078 11.801,39.328L11.801,33.547C11.801,31.766 13.02,30.547 14.864,30.547L22.52,30.547C24.364,30.547 25.582,31.766 25.582,33.547L25.582,39.328C25.582,41.078 24.364,42.322 22.52,42.322L14.864,42.322Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.515011,0,0,0.515011,34.08479,22)">
|
||||
<path d="M0.001,35.371C-0.038,39.202 1.473,42.727 4.441,45.671L16.349,57.61C11.913,60.839 9.844,64.493 9.844,69.059C9.844,76.236 15.431,81.432 22.42,81.549C26.022,81.612 29.343,80.305 31.751,77.938L53.883,55.695C59.804,49.751 59.453,42.361 52.939,35.83L41.879,24.794L44.775,21.89C47.234,19.407 48.675,16.156 48.692,12.936C48.785,5.665 43.16,0.007 35.85,0C31.88,0 28.667,1.489 25.293,4.83L3.649,26.387C1.324,28.753 0.032,31.925 0.001,35.371ZM18.613,24.255L31.665,11.203C33.369,9.474 34.298,9.018 35.85,9.018C37.991,9.018 39.664,10.692 39.664,12.825C39.664,13.85 39.304,14.61 38.379,15.518L24.077,29.836C23.14,27.191 21.326,25.315 18.613,24.255ZM9.135,35.594C9.135,33.425 10.709,31.799 12.756,31.799C14.857,31.799 16.468,33.464 16.468,35.669C16.468,36.68 16.444,37.157 16.216,37.941C15.03,43.335 21.775,44.881 24.421,42.268L35.475,31.174L46.542,42.227C49.615,45.227 49.817,47.01 47.511,49.299L33.566,63.268C32.715,61.558 31.327,59.892 29.126,57.643L11.994,40.417C9.701,38.148 9.135,37.157 9.135,35.594ZM18.887,69.149C18.887,67.295 20.04,65.793 22.846,64.06L23.303,64.516C25.504,66.773 26.164,67.845 26.172,69.173C26.22,71.078 24.636,72.476 22.444,72.476C20.445,72.476 18.887,71.13 18.887,69.149Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
16
assets/icons/returnpolicyicon.svg
Normal file
16
assets/icons/returnpolicyicon.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.607884,0,0,0.607884,2,6.470704)">
|
||||
<path d="M50.153,64.047L37.296,64.047C35.825,69.222 31.078,73.031 25.453,73.031C19.816,73.031 15.034,69.222 13.552,64.047L10.547,64.047C3.781,64.047 0,60.266 0,53.516L0,11.688C0,4.938 3.781,1.141 10.547,1.141L57.859,1.141C64.609,1.141 68.391,4.938 68.391,11.688L68.391,18.422L76.766,18.422C79.761,18.422 81.953,19.046 83.846,20.626C83.618,20.616 83.389,20.612 83.159,20.611L83.154,20.611C78.507,20.611 74.687,22.221 70.737,26.132L70.727,26.142L60.359,36.468L60.359,12.328C60.359,10.141 59.281,9.141 57.172,9.141L11.203,9.141C9.078,9.141 8.031,10.141 8.031,12.328L8.031,52.859C8.031,55.062 9.078,56.031 11.203,56.031L13.984,56.031C15.828,51.524 20.28,48.344 25.453,48.344C30.614,48.344 35.037,51.524 36.867,56.031L47.85,56.031C47.915,58.873 48.681,61.577 50.153,64.047ZM25.453,66.422C28.609,66.422 31.156,63.828 31.156,60.656C31.156,57.5 28.609,54.906 25.453,54.906C22.281,54.906 19.688,57.5 19.688,60.656C19.688,63.828 22.281,66.422 25.453,66.422Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.33037,0,0,0.33037,12.639581,15.454198)">
|
||||
<path d="M53.25,58.219C54.656,59.562 56.219,60.219 58.125,60.219C62.062,60.219 65.312,56.969 65.312,53.125C65.312,51.156 64.469,49.312 62.938,47.812L43.562,29.156L43.562,30.906L62.938,12.375C64.5,10.844 65.312,9.031 65.312,7.094C65.312,3.219 62.062,0 58.125,0C56.188,0 54.594,0.625 53.25,2L30.719,24C28.781,25.969 27.969,27.75 27.906,30.094C27.906,32.406 28.75,34.219 30.719,36.188L53.25,58.219Z" style="fill-rule:nonzero;"/>
|
||||
<g transform="matrix(1,0,0,1,-4.437961,0)">
|
||||
<path d="M22.23,55.204L2.781,36.187C0.844,34.219 0,32.406 0,30.094C0.031,27.75 0.875,25.969 2.781,24L25.313,2C26.688,0.625 28.25,0 30.188,0C34.125,0 37.406,3.219 37.406,7.094C37.406,9.031 36.594,10.844 35,12.375L16.537,30.034L35,47.812C36.562,49.313 37.406,51.156 37.406,53.125C37.406,55.669 35.969,57.953 33.855,59.207C30.383,57.109 26.443,55.71 22.23,55.204Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(0.515011,0,0,0.515011,34.08479,22)">
|
||||
<path d="M0.001,35.371C-0.038,39.202 1.473,42.727 4.441,45.671L16.349,57.61C11.913,60.839 9.844,64.493 9.844,69.059C9.844,76.236 15.431,81.432 22.42,81.549C26.022,81.612 29.343,80.305 31.751,77.938L53.883,55.695C59.804,49.751 59.453,42.361 52.939,35.83L41.879,24.794L44.775,21.89C47.234,19.407 48.675,16.156 48.692,12.936C48.785,5.665 43.16,0.007 35.85,0C31.88,0 28.667,1.489 25.293,4.83L3.649,26.387C1.324,28.753 0.032,31.925 0.001,35.371ZM18.613,24.255L31.665,11.203C33.369,9.474 34.298,9.018 35.85,9.018C37.991,9.018 39.664,10.692 39.664,12.825C39.664,13.85 39.304,14.61 38.379,15.518L24.077,29.836C23.14,27.191 21.326,25.315 18.613,24.255ZM9.135,35.594C9.135,33.425 10.709,31.799 12.756,31.799C14.857,31.799 16.468,33.464 16.468,35.669C16.468,36.68 16.444,37.157 16.216,37.941C15.03,43.335 21.775,44.881 24.421,42.268L35.475,31.174L46.542,42.227C49.615,45.227 49.817,47.01 47.511,49.299L33.566,63.268C32.715,61.558 31.327,59.892 29.126,57.643L11.994,40.417C9.701,38.148 9.135,37.157 9.135,35.594ZM18.887,69.149C18.887,67.295 20.04,65.793 22.846,64.06L23.303,64.516C25.504,66.773 26.164,67.845 26.172,69.173C26.22,71.078 24.636,72.476 22.444,72.476C20.445,72.476 18.887,71.13 18.887,69.149Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@ -2143,3 +2143,160 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
||||
border-radius: 1px;
|
||||
background: color-mix(in srgb, var(--color-text) 50%, transparent);
|
||||
}
|
||||
|
||||
.file-gallery-list-preview {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.file-gallery-list-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-gallery-list-preview .simplebar-track.simplebar-vertical {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-gallery-list-preview .simplebar-track.simplebar-horizontal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-gallery-list-preview .simplebar-content-wrapper {
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-padding-inline: 0;
|
||||
}
|
||||
|
||||
.file-gallery-list-preview:not(.is-mobile) .simplebar-content-wrapper {
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent 0,
|
||||
#000 var(--file-gallery-side-padding, 66px),
|
||||
#000 calc(100% - var(--file-gallery-side-padding, 66px)),
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent 0,
|
||||
#000 var(--file-gallery-side-padding, 66px),
|
||||
#000 calc(100% - var(--file-gallery-side-padding, 66px)),
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
.file-gallery-list-track {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.file-gallery-list-slide {
|
||||
box-sizing: border-box;
|
||||
scroll-snap-align: center;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.file-gallery-list-preview-frame {
|
||||
flex: 0 0 auto;
|
||||
max-width: 100%;
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.file-gallery-list-nav {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
z-index: 2;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.file-gallery-list-nav-left {
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.file-gallery-list-nav-right {
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.file-gallery-footer {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.file-gallery-list-thumb-container {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
}
|
||||
.file-gallery-list-thumb {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.file-gallery-list-thumb.is-active {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
.file-gallery-list-thumb-delete.ant-card {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(30%, -60%);
|
||||
z-index: 4;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.file-gallery-list-thumb-delete.ant-card .ant-card-body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.file-gallery-list-thumb-delete .ant-btn {
|
||||
height: auto;
|
||||
width: auto;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.file-gallery-list-upload {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.file-gallery-list-dropzone.ant-upload.ant-upload-drag {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
padding: 0;
|
||||
border-radius: 5px;
|
||||
border: 1px dashed color-mix(in srgb, var(--color-primary) 50%, transparent);
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
color-mix(in srgb, var(--color-primary) 12%, transparent),
|
||||
color-mix(in srgb, var(--color-primary) 12%, transparent) 6px,
|
||||
transparent 6px,
|
||||
transparent 12px
|
||||
);
|
||||
}
|
||||
|
||||
.file-gallery-list-dropzone.ant-upload.ant-upload-drag
|
||||
.ant-upload-drag-container {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.file-gallery-list-dropzone.ant-upload.ant-upload-drag:hover,
|
||||
.file-gallery-list-dropzone.ant-upload.ant-upload-drag.ant-upload-drag-hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.file-gallery-list-dropzone .anticon {
|
||||
font-size: 18px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
79
src/components/Dashboard/Finance/PaymentPolicies.jsx
Normal file
79
src/components/Dashboard/Finance/PaymentPolicies.jsx
Normal file
@ -0,0 +1,79 @@
|
||||
import { useRef } from 'react'
|
||||
import { Flex, Space } from 'antd'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import ObjectActions from '../common/ObjectActions'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import ObjectTableViewButton from '../common/ObjectTableViewButton'
|
||||
import FilterSidebarButton from '../common/FilterSidebarButton'
|
||||
import SortSidebarButton from '../common/SortSidebarButton'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
|
||||
import useSortSidebarVisibility from '../hooks/useSortSidebarVisibility'
|
||||
import ColumnViewButton from '../common/ColumnViewButton'
|
||||
import ExportListButton from '../common/ExportListButton'
|
||||
|
||||
const PaymentPolicies = () => {
|
||||
const tableRef = useRef()
|
||||
const [viewMode, setViewMode] = useViewMode('paymentPolicy')
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
useColumnVisibility('paymentPolicy')
|
||||
const [showFilterSidebar, setShowFilterSidebar] =
|
||||
useFilterSidebarVisibility('PaymentPolicies')
|
||||
const [showSortSidebar, setShowSortSidebar] =
|
||||
useSortSidebarVisibility('PaymentPolicies')
|
||||
|
||||
return (
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='paymentPolicy'
|
||||
pageName='list'
|
||||
onReload={() => tableRef.current?.reload()}
|
||||
/>
|
||||
<ColumnViewButton
|
||||
type='paymentPolicy'
|
||||
loading={false}
|
||||
visibleState={columnVisibility}
|
||||
updateVisibleState={setColumnVisibility}
|
||||
/>
|
||||
<ExportListButton objectType='paymentPolicy' />
|
||||
</Space>
|
||||
<Space>
|
||||
<SortSidebarButton
|
||||
active={showSortSidebar}
|
||||
onClick={() => setShowSortSidebar(!showSortSidebar)}
|
||||
/>
|
||||
<FilterSidebarButton
|
||||
active={showFilterSidebar}
|
||||
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
|
||||
/>
|
||||
<ObjectTableViewButton
|
||||
viewMode={viewMode}
|
||||
setViewMode={setViewMode}
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
visibleColumns={columnVisibility}
|
||||
type='paymentPolicy'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
showSortSidebar={showSortSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaymentPolicies
|
||||
@ -0,0 +1,90 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import NewObjectForm from '../../common/NewObjectForm'
|
||||
import WizardView from '../../common/WizardView'
|
||||
|
||||
const NewPaymentPolicy = ({ onOk, defaultValues }) => {
|
||||
return (
|
||||
<NewObjectForm
|
||||
type={'paymentPolicy'}
|
||||
defaultValues={{ immediatePay: true, ...defaultValues }}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='paymentPolicy'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={140}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='paymentPolicy'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={140}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='paymentPolicy'
|
||||
column={1}
|
||||
bordered={false}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
_reference: false,
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
labelWidth={140}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
return (
|
||||
<WizardView
|
||||
steps={steps}
|
||||
loading={submitLoading}
|
||||
formValid={formValid}
|
||||
title='New Payment Policy'
|
||||
onSubmit={async () => {
|
||||
const result = await handleSubmit()
|
||||
if (result) {
|
||||
onOk()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NewObjectForm>
|
||||
)
|
||||
}
|
||||
|
||||
NewPaymentPolicy.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
reset: PropTypes.bool,
|
||||
defaultValues: PropTypes.object
|
||||
}
|
||||
|
||||
export default NewPaymentPolicy
|
||||
@ -0,0 +1,264 @@
|
||||
import { useRef, useState, useMemo } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space, Spin } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import MarketplaceIcon from '../../../Icons/MarketplaceIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
|
||||
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 {
|
||||
getModelProperty,
|
||||
getModelByName
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const log = loglevel.getLogger('PaymentPolicyInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
|
||||
const PaymentPolicyInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const paymentPolicyId = new URLSearchParams(location.search).get(
|
||||
'paymentPolicyId'
|
||||
)
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'PaymentPolicyInfo',
|
||||
{
|
||||
info: true,
|
||||
marketplaces: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
lock: null,
|
||||
loading: false,
|
||||
editDisabled: false,
|
||||
objectData: {}
|
||||
})
|
||||
|
||||
const currentObjectTools = useMemo(
|
||||
() => (
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={paymentPolicyId}
|
||||
objectType='paymentPolicy'
|
||||
showEndingDivider={false}
|
||||
/>
|
||||
),
|
||||
[objectFormState.loading, objectFormState.isEditing, paymentPolicyId]
|
||||
)
|
||||
useDashboardObjectTools(currentObjectTools)
|
||||
|
||||
const actions = {
|
||||
edit: () => {
|
||||
objectFormRef?.current?.startEditing?.()
|
||||
return false
|
||||
},
|
||||
cancelEdit: () => {
|
||||
objectFormRef?.current?.cancelEditing?.()
|
||||
return true
|
||||
},
|
||||
finishEdit: () => {
|
||||
objectFormRef?.current?.handleUpdate?.()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='paymentPolicy'
|
||||
pageName='info'
|
||||
id={paymentPolicyId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Payment Policy Information' },
|
||||
{ key: 'marketplaces', label: 'Marketplaces' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<UserNotifierToggle
|
||||
type='paymentPolicy'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='paymentPolicy'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
isEditing={objectFormState.isEditing}
|
||||
handleUpdate={() => {
|
||||
actionHandlerRef.current.callAction('finishEdit')
|
||||
}}
|
||||
cancelEditing={() => {
|
||||
actionHandlerRef.current.callAction('cancelEdit')
|
||||
}}
|
||||
startEditing={() => {
|
||||
actionHandlerRef.current.callAction('edit')
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther ||
|
||||
objectFormState.loading ||
|
||||
objectFormState.editDisabled
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
<Flex vertical gap={'large'}>
|
||||
<ActionHandler
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<ObjectForm
|
||||
id={paymentPolicyId}
|
||||
type='paymentPolicy'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<Flex vertical gap={'large'}>
|
||||
<InfoCollapse
|
||||
title='Payment Policy Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('info', expanded)
|
||||
}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='paymentPolicy'
|
||||
objectData={objectData}
|
||||
labelWidth={210}
|
||||
visibleProperties={{
|
||||
marketplaces: false
|
||||
}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Marketplaces'
|
||||
icon={<MarketplaceIcon />}
|
||||
active={collapseState.marketplaces}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('marketplaces', expanded)
|
||||
}
|
||||
collapseKey='marketplaces'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('paymentPolicy', 'marketplaces')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Spin
|
||||
spinning={objectFormState.loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={paymentPolicyId} type='paymentPolicy' />
|
||||
</Card>
|
||||
</Spin>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Audit Logs'
|
||||
icon={<AuditLogIcon />}
|
||||
active={collapseState.auditLogs}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('auditLogs', expanded)
|
||||
}
|
||||
collapseKey='auditLogs'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{
|
||||
parent:
|
||||
getModelByName('paymentPolicy').prefix +
|
||||
':' +
|
||||
paymentPolicyId
|
||||
}}
|
||||
visibleColumns={{ _id: false, parent: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaymentPolicyInfo
|
||||
@ -8,7 +8,7 @@ const NewPartStock = ({ onOk, reset, defaultValues }) => {
|
||||
<NewObjectForm
|
||||
type={'partStock'}
|
||||
reset={reset}
|
||||
defaultValues={{ state: { type: 'new' }, ...defaultValues }}
|
||||
defaultValues={{ state: { type: 'draft' }, ...defaultValues }}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
@ -38,7 +38,8 @@ const NewPartStock = ({ onOk, reset, defaultValues }) => {
|
||||
_id: false,
|
||||
_reference: false,
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
updatedAt: false,
|
||||
postedAt: false
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import { useState, useContext } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ApiServerContext } from '../../context/ApiServerContext'
|
||||
import { message } from 'antd'
|
||||
import MessageDialogView from '../../common/MessageDialogView.jsx'
|
||||
|
||||
const PostPartStock = ({ onOk, objectData }) => {
|
||||
const [postLoading, setPostLoading] = useState(false)
|
||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
||||
|
||||
const handlePost = async () => {
|
||||
setPostLoading(true)
|
||||
try {
|
||||
const result = await sendObjectFunction(objectData._id, 'PartStock', 'post')
|
||||
if (result) {
|
||||
message.success('Part stock posted successfully')
|
||||
onOk(result)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error posting part stock:', error)
|
||||
} finally {
|
||||
setPostLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MessageDialogView
|
||||
title={'Are you sure you want to post this part stock?'}
|
||||
description={`Posting part stock ${objectData?.name || objectData?._reference || objectData?._id} will finalize it and make it read-only.`}
|
||||
onOk={handlePost}
|
||||
okText='Post'
|
||||
okLoading={postLoading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
PostPartStock.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default PostPartStock
|
||||
@ -3,9 +3,9 @@ import ObjectInfo from '../../common/ObjectInfo'
|
||||
import NewObjectForm from '../../common/NewObjectForm'
|
||||
import WizardView from '../../common/WizardView'
|
||||
|
||||
const NewProductCategory = ({ onOk }) => {
|
||||
const NewProductCategory = ({ onOk, defaultValues }) => {
|
||||
return (
|
||||
<NewObjectForm type={'productCategory'}>
|
||||
<NewObjectForm type={'productCategory'} defaultValues={defaultValues}>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
@ -23,6 +23,21 @@ const NewProductCategory = ({ onOk }) => {
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='productCategory'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
labelWidth={120}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
@ -39,7 +54,7 @@ const NewProductCategory = ({ onOk }) => {
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
labelWidth={70}
|
||||
labelWidth={120}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -65,7 +80,8 @@ const NewProductCategory = ({ onOk }) => {
|
||||
|
||||
NewProductCategory.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
reset: PropTypes.bool
|
||||
reset: PropTypes.bool,
|
||||
defaultValues: PropTypes.object
|
||||
}
|
||||
|
||||
export default NewProductCategory
|
||||
|
||||
@ -6,10 +6,12 @@ import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import MarketplaceIcon from '../../../Icons/MarketplaceIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
@ -21,7 +23,7 @@ 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 { getModelByName } from '../../../../database/ObjectModels.js'
|
||||
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const ProductCategoryInfo = () => {
|
||||
@ -35,6 +37,7 @@ const ProductCategoryInfo = () => {
|
||||
'ProductCategoryInfo',
|
||||
{
|
||||
info: true,
|
||||
marketplaces: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
}
|
||||
@ -98,6 +101,7 @@ const ProductCategoryInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Product Category Information' },
|
||||
{ key: 'marketplaces', label: 'Marketplaces' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
@ -150,33 +154,56 @@ const ProductCategoryInfo = () => {
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Product Category Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
<ObjectForm
|
||||
id={productCategoryId}
|
||||
type='productCategory'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
<ObjectForm
|
||||
id={productCategoryId}
|
||||
type='productCategory'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='productCategory'
|
||||
objectData={objectData}
|
||||
/>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<Flex vertical gap={'large'}>
|
||||
<InfoCollapse
|
||||
title='Product Category Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('info', expanded)
|
||||
}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='productCategory'
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
marketplaces: false
|
||||
}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Marketplaces'
|
||||
icon={<MarketplaceIcon />}
|
||||
active={collapseState.marketplaces}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('marketplaces', expanded)
|
||||
}
|
||||
collapseKey='marketplaces'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('productCategory', 'marketplaces')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
|
||||
@ -8,10 +8,12 @@ import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import MarketplaceIcon from '../../../Icons/MarketplaceIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
@ -23,7 +25,7 @@ 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 { getModelByName } from '../../../../database/ObjectModels.js'
|
||||
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const log = loglevel.getLogger('TaxRateInfo')
|
||||
@ -36,6 +38,7 @@ const TaxRateInfo = () => {
|
||||
const taxRateId = new URLSearchParams(location.search).get('taxRateId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('TaxRateInfo', {
|
||||
info: true,
|
||||
marketplaces: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
})
|
||||
@ -98,6 +101,7 @@ const TaxRateInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Tax Rate Information' },
|
||||
{ key: 'marketplaces', label: 'Marketplaces' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
@ -150,33 +154,56 @@ const TaxRateInfo = () => {
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Tax Rate Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
<ObjectForm
|
||||
id={taxRateId}
|
||||
type='taxRate'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
<ObjectForm
|
||||
id={taxRateId}
|
||||
type='taxRate'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='taxRate'
|
||||
objectData={objectData}
|
||||
/>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<Flex vertical gap={'large'}>
|
||||
<InfoCollapse
|
||||
title='Tax Rate Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('info', expanded)
|
||||
}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='taxRate'
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
marketplaces: false
|
||||
}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Marketplaces'
|
||||
icon={<MarketplaceIcon />}
|
||||
active={collapseState.marketplaces}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('marketplaces', expanded)
|
||||
}
|
||||
collapseKey='marketplaces'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('taxRate', 'marketplaces')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
|
||||
79
src/components/Dashboard/Sales/FulfillmentPolicies.jsx
Normal file
79
src/components/Dashboard/Sales/FulfillmentPolicies.jsx
Normal file
@ -0,0 +1,79 @@
|
||||
import { useRef } from 'react'
|
||||
import { Flex, Space } from 'antd'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import ObjectActions from '../common/ObjectActions'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import ObjectTableViewButton from '../common/ObjectTableViewButton'
|
||||
import FilterSidebarButton from '../common/FilterSidebarButton'
|
||||
import SortSidebarButton from '../common/SortSidebarButton'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
|
||||
import useSortSidebarVisibility from '../hooks/useSortSidebarVisibility'
|
||||
import ColumnViewButton from '../common/ColumnViewButton'
|
||||
import ExportListButton from '../common/ExportListButton'
|
||||
|
||||
const FulfillmentPolicies = () => {
|
||||
const tableRef = useRef()
|
||||
const [viewMode, setViewMode] = useViewMode('fulfillmentPolicy')
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
useColumnVisibility('fulfillmentPolicy')
|
||||
const [showFilterSidebar, setShowFilterSidebar] =
|
||||
useFilterSidebarVisibility('FulfillmentPolicies')
|
||||
const [showSortSidebar, setShowSortSidebar] =
|
||||
useSortSidebarVisibility('FulfillmentPolicies')
|
||||
|
||||
return (
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='fulfillmentPolicy'
|
||||
pageName='list'
|
||||
onReload={() => tableRef.current?.reload()}
|
||||
/>
|
||||
<ColumnViewButton
|
||||
type='fulfillmentPolicy'
|
||||
loading={false}
|
||||
visibleState={columnVisibility}
|
||||
updateVisibleState={setColumnVisibility}
|
||||
/>
|
||||
<ExportListButton objectType='fulfillmentPolicy' />
|
||||
</Space>
|
||||
<Space>
|
||||
<SortSidebarButton
|
||||
active={showSortSidebar}
|
||||
onClick={() => setShowSortSidebar(!showSortSidebar)}
|
||||
/>
|
||||
<FilterSidebarButton
|
||||
active={showFilterSidebar}
|
||||
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
|
||||
/>
|
||||
<ObjectTableViewButton
|
||||
viewMode={viewMode}
|
||||
setViewMode={setViewMode}
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
visibleColumns={columnVisibility}
|
||||
type='fulfillmentPolicy'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
showSortSidebar={showSortSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default FulfillmentPolicies
|
||||
@ -0,0 +1,270 @@
|
||||
import { useRef, useState, useMemo } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space, Spin } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import MarketplaceIcon from '../../../Icons/MarketplaceIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
|
||||
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 {
|
||||
getModelProperty,
|
||||
getModelByName
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const log = loglevel.getLogger('FulfillmentPolicyInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
|
||||
const FulfillmentPolicyInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const fulfillmentPolicyId = new URLSearchParams(location.search).get(
|
||||
'fulfillmentPolicyId'
|
||||
)
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'FulfillmentPolicyInfo',
|
||||
{
|
||||
info: true,
|
||||
marketplaces: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
lock: null,
|
||||
loading: false,
|
||||
editDisabled: false,
|
||||
objectData: {}
|
||||
})
|
||||
|
||||
const currentObjectTools = useMemo(
|
||||
() => (
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={fulfillmentPolicyId}
|
||||
objectType='fulfillmentPolicy'
|
||||
showEndingDivider={false}
|
||||
/>
|
||||
),
|
||||
[objectFormState.loading, objectFormState.isEditing, fulfillmentPolicyId]
|
||||
)
|
||||
useDashboardObjectTools(currentObjectTools)
|
||||
|
||||
const actions = {
|
||||
edit: () => {
|
||||
objectFormRef?.current?.startEditing?.()
|
||||
return false
|
||||
},
|
||||
cancelEdit: () => {
|
||||
objectFormRef?.current?.cancelEditing?.()
|
||||
return true
|
||||
},
|
||||
finishEdit: () => {
|
||||
objectFormRef?.current?.handleUpdate?.()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='fulfillmentPolicy'
|
||||
pageName='info'
|
||||
id={fulfillmentPolicyId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Fulfillment Policy Information' },
|
||||
{ key: 'marketplaces', label: 'Marketplaces' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<UserNotifierToggle
|
||||
type='fulfillmentPolicy'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='fulfillmentPolicy'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
isEditing={objectFormState.isEditing}
|
||||
handleUpdate={() => {
|
||||
actionHandlerRef.current.callAction('finishEdit')
|
||||
}}
|
||||
cancelEditing={() => {
|
||||
actionHandlerRef.current.callAction('cancelEdit')
|
||||
}}
|
||||
startEditing={() => {
|
||||
actionHandlerRef.current.callAction('edit')
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther ||
|
||||
objectFormState.loading ||
|
||||
objectFormState.editDisabled
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
<Flex vertical gap={'large'}>
|
||||
<ActionHandler
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<ObjectForm
|
||||
id={fulfillmentPolicyId}
|
||||
type='fulfillmentPolicy'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<Flex vertical gap={'large'}>
|
||||
<InfoCollapse
|
||||
title='Fulfillment Policy Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('info', expanded)
|
||||
}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
labelWidth={175}
|
||||
type='fulfillmentPolicy'
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
marketplaces: false
|
||||
}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Marketplaces'
|
||||
icon={<MarketplaceIcon />}
|
||||
active={collapseState.marketplaces}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('marketplaces', expanded)
|
||||
}
|
||||
collapseKey='marketplaces'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty(
|
||||
'fulfillmentPolicy',
|
||||
'marketplaces'
|
||||
)}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Spin
|
||||
spinning={objectFormState.loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel
|
||||
_id={fulfillmentPolicyId}
|
||||
type='fulfillmentPolicy'
|
||||
/>
|
||||
</Card>
|
||||
</Spin>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Audit Logs'
|
||||
icon={<AuditLogIcon />}
|
||||
active={collapseState.auditLogs}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('auditLogs', expanded)
|
||||
}
|
||||
collapseKey='auditLogs'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{
|
||||
parent:
|
||||
getModelByName('fulfillmentPolicy').prefix +
|
||||
':' +
|
||||
fulfillmentPolicyId
|
||||
}}
|
||||
visibleColumns={{ _id: false, parent: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FulfillmentPolicyInfo
|
||||
@ -0,0 +1,90 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import NewObjectForm from '../../common/NewObjectForm'
|
||||
import WizardView from '../../common/WizardView'
|
||||
|
||||
const NewFulfillmentPolicy = ({ onOk, defaultValues }) => {
|
||||
return (
|
||||
<NewObjectForm
|
||||
type={'fulfillmentPolicy'}
|
||||
defaultValues={{ handlingTime: 1, ...defaultValues }}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='fulfillmentPolicy'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={130}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='fulfillmentPolicy'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={130}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='fulfillmentPolicy'
|
||||
column={1}
|
||||
bordered={false}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
_reference: false,
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
labelWidth={130}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
return (
|
||||
<WizardView
|
||||
steps={steps}
|
||||
loading={submitLoading}
|
||||
formValid={formValid}
|
||||
title='New Fulfillment Policy'
|
||||
onSubmit={async () => {
|
||||
const result = await handleSubmit()
|
||||
if (result) {
|
||||
onOk()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NewObjectForm>
|
||||
)
|
||||
}
|
||||
|
||||
NewFulfillmentPolicy.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
reset: PropTypes.bool,
|
||||
defaultValues: PropTypes.object
|
||||
}
|
||||
|
||||
export default NewFulfillmentPolicy
|
||||
86
src/components/Dashboard/Sales/ListingVarients.jsx
Normal file
86
src/components/Dashboard/Sales/ListingVarients.jsx
Normal file
@ -0,0 +1,86 @@
|
||||
import { useRef } from 'react'
|
||||
import { Flex, Space } from 'antd'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import ObjectActions from '../common/ObjectActions'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import ObjectTableViewButton from '../common/ObjectTableViewButton'
|
||||
import FilterSidebarButton from '../common/FilterSidebarButton'
|
||||
import SortSidebarButton from '../common/SortSidebarButton'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
|
||||
import useSortSidebarVisibility from '../hooks/useSortSidebarVisibility'
|
||||
import ColumnViewButton from '../common/ColumnViewButton'
|
||||
import ExportListButton from '../common/ExportListButton'
|
||||
|
||||
const ListingVarients = () => {
|
||||
const tableRef = useRef()
|
||||
|
||||
const [viewMode, setViewMode] = useViewMode('listingVarients')
|
||||
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
useColumnVisibility('listingVarients')
|
||||
|
||||
const [showFilterSidebar, setShowFilterSidebar] =
|
||||
useFilterSidebarVisibility('ListingVarients')
|
||||
|
||||
const [showSortSidebar, setShowSortSidebar] =
|
||||
useSortSidebarVisibility('ListingVarients')
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='listingVarient'
|
||||
pageName='list'
|
||||
onReload={() => tableRef.current?.reload()}
|
||||
/>
|
||||
<ColumnViewButton
|
||||
type='listingVarient'
|
||||
loading={false}
|
||||
visibleState={columnVisibility}
|
||||
updateVisibleState={setColumnVisibility}
|
||||
/>
|
||||
<ExportListButton objectType='listingVarient' />
|
||||
</Space>
|
||||
<Space>
|
||||
<SortSidebarButton
|
||||
active={showSortSidebar}
|
||||
onClick={() => setShowSortSidebar(!showSortSidebar)}
|
||||
/>
|
||||
<FilterSidebarButton
|
||||
active={showFilterSidebar}
|
||||
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
|
||||
/>
|
||||
<ObjectTableViewButton
|
||||
viewMode={viewMode}
|
||||
setViewMode={setViewMode}
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
visibleColumns={columnVisibility}
|
||||
type='listingVarient'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
showSortSidebar={showSortSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListingVarients
|
||||
@ -8,8 +8,10 @@ import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import PictureIcon from '../../../Icons/PictureIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
@ -25,7 +27,10 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import { getModelByName } from '../../../../database/ObjectModels.js'
|
||||
import {
|
||||
getModelByName,
|
||||
getModelProperty
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
|
||||
const log = loglevel.getLogger('ListingVarientInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -49,6 +54,7 @@ const ListingVarientInfo = () => {
|
||||
'ListingVarientInfo',
|
||||
{
|
||||
info: true,
|
||||
listingImages: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
}
|
||||
@ -111,6 +117,7 @@ const ListingVarientInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Listing Varient Information' },
|
||||
{ key: 'listingImages', label: 'Listing Images' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
@ -189,6 +196,25 @@ const ListingVarientInfo = () => {
|
||||
isEditing={isEditing}
|
||||
type='listingVarient'
|
||||
objectData={objectData}
|
||||
visibleProperties={{ listingImages: false }}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Listing Images'
|
||||
icon={<PictureIcon />}
|
||||
active={collapseState.listingImages}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('listingImages', expanded)
|
||||
}
|
||||
collapseKey='listingImages'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('listingVarient', 'listingImages')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
size='medium'
|
||||
showCard={false}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
|
||||
@ -37,6 +37,7 @@ const NewListingVarient = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
visibleProperties={{ listingImages: false }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -54,7 +55,8 @@ const NewListingVarient = ({ onOk, defaultValues }) => {
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
lastSyncedAt: false,
|
||||
stockQuantity: false
|
||||
stockQuantity: false,
|
||||
listingImages: false
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
|
||||
@ -17,7 +17,7 @@ const PublishListingVarient = ({ onOk, objectData }) => {
|
||||
'publish'
|
||||
)
|
||||
if (result) {
|
||||
message.success('Published successfully')
|
||||
message.success('Publishing started...')
|
||||
onOk(result)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -17,7 +17,7 @@ const UnpublishListingVarient = ({ onOk, objectData }) => {
|
||||
'unpublish'
|
||||
)
|
||||
if (result) {
|
||||
message.success('Unpublished successfully')
|
||||
message.success('Unpublishing started...')
|
||||
onOk(result)
|
||||
}
|
||||
} catch (error) {
|
||||
@ -35,7 +35,7 @@ const UnpublishListingVarient = ({ onOk, objectData }) => {
|
||||
|
||||
return (
|
||||
<MessageDialogView
|
||||
title="Withdraw this variant from the marketplace?"
|
||||
title='Withdraw this variant from the marketplace?'
|
||||
description={`Ends the live marketplace listing for this SKU where applicable (eBay).${
|
||||
ref ? ` (variant: ${ref})` : ''
|
||||
}`}
|
||||
|
||||
@ -12,6 +12,7 @@ import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import TextIcon from '../../../Icons/TextIcon.jsx'
|
||||
import PictureIcon from '../../../Icons/PictureIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ListingVarientIcon from '../../../Icons/ListingVarientIcon.jsx'
|
||||
@ -27,7 +28,10 @@ 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 { getModelByName, getModelProperty } from '../../../../database/ObjectModels.js'
|
||||
import {
|
||||
getModelByName,
|
||||
getModelProperty
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const log = loglevel.getLogger('ListingInfo')
|
||||
@ -49,6 +53,7 @@ const ListingInfo = () => {
|
||||
const listingId = new URLSearchParams(location.search).get('listingId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('ListingInfo', {
|
||||
info: true,
|
||||
listingImages: true,
|
||||
description: true,
|
||||
listingVarients: true,
|
||||
notes: true,
|
||||
@ -89,7 +94,7 @@ const ListingInfo = () => {
|
||||
finishEdit: () => {
|
||||
objectFormRef?.current?.handleUpdate?.()
|
||||
return true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@ -112,6 +117,7 @@ const ListingInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Listing Information' },
|
||||
{ key: 'listingImages', label: 'Listing Images' },
|
||||
{ key: 'description', label: 'Listing Description' },
|
||||
{ key: 'listingVarients', label: 'Listing Varients' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
@ -191,11 +197,35 @@ const ListingInfo = () => {
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='listing'
|
||||
labelWidth={165}
|
||||
labelWidth={180}
|
||||
objectData={objectData}
|
||||
visibleProperties={{ description: false }}
|
||||
visibleProperties={{
|
||||
description: false,
|
||||
listingImages: false
|
||||
}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Listing Images'
|
||||
icon={<PictureIcon />}
|
||||
active={collapseState.listingImages}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('listingImages', expanded)
|
||||
}
|
||||
collapseKey='listingImages'
|
||||
>
|
||||
<Spin spinning={loading} indicator={<LoadingOutlined />}>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('listing', 'listingImages')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
size='medium'
|
||||
showCard={false}
|
||||
/>
|
||||
</Spin>
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Listing Description'
|
||||
icon={<TextIcon />}
|
||||
@ -205,16 +235,18 @@ const ListingInfo = () => {
|
||||
}
|
||||
collapseKey='description'
|
||||
>
|
||||
<Card>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('listing', 'description')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
size='medium'
|
||||
showCard={false}
|
||||
/>
|
||||
</Card>
|
||||
<Spin spinning={loading} indicator={<LoadingOutlined />}>
|
||||
<Card>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('listing', 'description')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
size='medium'
|
||||
showCard={false}
|
||||
/>
|
||||
</Card>
|
||||
</Spin>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Listing Varients'
|
||||
@ -228,7 +260,8 @@ const ListingInfo = () => {
|
||||
<ObjectTable
|
||||
type='listingVarient'
|
||||
masterFilter={{
|
||||
listing: getModelByName('listing').prefix + ':' + listingId
|
||||
listing:
|
||||
getModelByName('listing').prefix + ':' + listingId
|
||||
}}
|
||||
visibleColumns={{ listing: false }}
|
||||
ref={listingVarientsTableRef}
|
||||
@ -250,7 +283,7 @@ const ListingInfo = () => {
|
||||
indicator={<LoadingOutlined />}
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={listingId} type='listing' />
|
||||
<NotesPanel _id={listingId} type='listing' />
|
||||
</Card>
|
||||
</Spin>
|
||||
</InfoCollapse>
|
||||
@ -267,10 +300,7 @@ const ListingInfo = () => {
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{
|
||||
parent:
|
||||
getModelByName('listing').prefix +
|
||||
':' +
|
||||
listingId
|
||||
parent: getModelByName('listing').prefix + ':' + listingId
|
||||
}}
|
||||
visibleColumns={{ _id: false, parent: false }}
|
||||
/>
|
||||
|
||||
@ -37,6 +37,7 @@ const NewListing = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
visibleProperties={{ listingImages: false }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -54,7 +55,8 @@ const NewListing = ({ onOk, defaultValues }) => {
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
lastSyncedAt: false,
|
||||
stockQuantity: false
|
||||
stockQuantity: false,
|
||||
listingImages: false
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
|
||||
@ -11,9 +11,13 @@ const PublishListing = ({ onOk, objectData }) => {
|
||||
const handlePublish = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await sendObjectFunction(objectData._id, 'Listing', 'publish')
|
||||
const result = await sendObjectFunction(
|
||||
objectData._id,
|
||||
'Listing',
|
||||
'publish'
|
||||
)
|
||||
if (result) {
|
||||
message.success('Published successfully')
|
||||
message.success('Publishing started...')
|
||||
onOk(result)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -11,9 +11,13 @@ const UnpublishListing = ({ onOk, objectData }) => {
|
||||
const handleUnpublish = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await sendObjectFunction(objectData._id, 'Listing', 'unpublish')
|
||||
const result = await sendObjectFunction(
|
||||
objectData._id,
|
||||
'Listing',
|
||||
'unpublish'
|
||||
)
|
||||
if (result) {
|
||||
message.success('Unpublished successfully')
|
||||
message.success('Unpublishing started...')
|
||||
onOk(result)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -253,6 +253,10 @@ const MarketplaceInfo = () => {
|
||||
isEditing={isEditing}
|
||||
type='marketplace'
|
||||
labelWidth={215}
|
||||
visibleProperties={{
|
||||
'eBay.categoryReferences': false,
|
||||
'eBay.availableShippingServices': false
|
||||
}}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)}
|
||||
@ -271,7 +275,7 @@ const MarketplaceInfo = () => {
|
||||
indicator={<LoadingOutlined />}
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={marketplaceId} type='marketplace' />
|
||||
<NotesPanel _id={marketplaceId} type='marketplace' />
|
||||
</Card>
|
||||
</Spin>
|
||||
</InfoCollapse>
|
||||
@ -288,10 +292,8 @@ const MarketplaceInfo = () => {
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{
|
||||
parent:
|
||||
getModelByName('marketplace').prefix +
|
||||
':' +
|
||||
marketplaceId
|
||||
parent:
|
||||
getModelByName('marketplace').prefix + ':' + marketplaceId
|
||||
}}
|
||||
visibleColumns={{ _id: false, parent: false }}
|
||||
/>
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
import { useState, useContext } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ApiServerContext } from '../../context/ApiServerContext'
|
||||
import { message } from 'antd'
|
||||
import MessageDialogView from '../../common/MessageDialogView.jsx'
|
||||
|
||||
const SyncAccountPolicies = ({
|
||||
onOk,
|
||||
objectData,
|
||||
functionName,
|
||||
title,
|
||||
description,
|
||||
successMessage
|
||||
}) => {
|
||||
const [syncLoading, setSyncLoading] = useState(false)
|
||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
||||
|
||||
const handleSync = async () => {
|
||||
setSyncLoading(true)
|
||||
try {
|
||||
const result = await sendObjectFunction(
|
||||
objectData._id,
|
||||
'Marketplace',
|
||||
functionName
|
||||
)
|
||||
if (result) {
|
||||
message.success(successMessage)
|
||||
onOk(result)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error syncing ${functionName}:`, error)
|
||||
} finally {
|
||||
setSyncLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MessageDialogView
|
||||
title={title}
|
||||
description={description}
|
||||
onOk={handleSync}
|
||||
okText='Sync'
|
||||
okLoading={syncLoading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
SyncAccountPolicies.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object,
|
||||
functionName: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
successMessage: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default SyncAccountPolicies
|
||||
@ -0,0 +1,20 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import SyncAccountPolicies from './SyncAccountPolicies.jsx'
|
||||
|
||||
const SyncFulfillmentPolicies = ({ onOk, objectData }) => (
|
||||
<SyncAccountPolicies
|
||||
onOk={onOk}
|
||||
objectData={objectData}
|
||||
functionName='sync/fulfillmentPolicies'
|
||||
title='Sync fulfillment policies from marketplace?'
|
||||
description={`This will import fulfillment policies from ${objectData?.name || objectData?._reference || objectData?._id}.`}
|
||||
successMessage='Fulfillment policies synced successfully'
|
||||
/>
|
||||
)
|
||||
|
||||
SyncFulfillmentPolicies.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default SyncFulfillmentPolicies
|
||||
@ -30,7 +30,7 @@ const SyncMarketplace = ({ onOk, objectData }) => {
|
||||
return (
|
||||
<MessageDialogView
|
||||
title='Sync marketplace?'
|
||||
description={`This will refresh marketplace metadata from ${objectData?.name || objectData?._reference || objectData?._id}, including available shipping services.`}
|
||||
description={`This will refresh marketplace metadata from ${objectData?.name || objectData?._reference || objectData?._id}, including shipping services, categories, business policies, and tax tables.`}
|
||||
onOk={handleSync}
|
||||
okText='Sync'
|
||||
okLoading={syncLoading}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import SyncAccountPolicies from './SyncAccountPolicies.jsx'
|
||||
|
||||
const SyncPaymentPolicies = ({ onOk, objectData }) => (
|
||||
<SyncAccountPolicies
|
||||
onOk={onOk}
|
||||
objectData={objectData}
|
||||
functionName='sync/paymentPolicies'
|
||||
title='Sync payment policies from marketplace?'
|
||||
description={`This will import payment policies from ${objectData?.name || objectData?._reference || objectData?._id}.`}
|
||||
successMessage='Payment policies synced successfully'
|
||||
/>
|
||||
)
|
||||
|
||||
SyncPaymentPolicies.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default SyncPaymentPolicies
|
||||
@ -0,0 +1,20 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import SyncAccountPolicies from './SyncAccountPolicies.jsx'
|
||||
|
||||
const SyncReturnPolicies = ({ onOk, objectData }) => (
|
||||
<SyncAccountPolicies
|
||||
onOk={onOk}
|
||||
objectData={objectData}
|
||||
functionName='sync/returnPolicies'
|
||||
title='Sync return policies from marketplace?'
|
||||
description={`This will import return policies from ${objectData?.name || objectData?._reference || objectData?._id}.`}
|
||||
successMessage='Return policies synced successfully'
|
||||
/>
|
||||
)
|
||||
|
||||
SyncReturnPolicies.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default SyncReturnPolicies
|
||||
20
src/components/Dashboard/Sales/Marketplaces/SyncTaxRates.jsx
Normal file
20
src/components/Dashboard/Sales/Marketplaces/SyncTaxRates.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import SyncAccountPolicies from './SyncAccountPolicies.jsx'
|
||||
|
||||
const SyncTaxRates = ({ onOk, objectData }) => (
|
||||
<SyncAccountPolicies
|
||||
onOk={onOk}
|
||||
objectData={objectData}
|
||||
functionName='sync/taxRates'
|
||||
title='Sync tax rates from marketplace?'
|
||||
description={`This will import tax tables from ${objectData?.name || objectData?._reference || objectData?._id} and push matching local tax rates.`}
|
||||
successMessage='Tax rates synced successfully'
|
||||
/>
|
||||
)
|
||||
|
||||
SyncTaxRates.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default SyncTaxRates
|
||||
78
src/components/Dashboard/Sales/ReturnPolicies.jsx
Normal file
78
src/components/Dashboard/Sales/ReturnPolicies.jsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { useRef } from 'react'
|
||||
import { Flex, Space } from 'antd'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import ObjectActions from '../common/ObjectActions'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import ObjectTableViewButton from '../common/ObjectTableViewButton'
|
||||
import FilterSidebarButton from '../common/FilterSidebarButton'
|
||||
import SortSidebarButton from '../common/SortSidebarButton'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
|
||||
import useSortSidebarVisibility from '../hooks/useSortSidebarVisibility'
|
||||
import ColumnViewButton from '../common/ColumnViewButton'
|
||||
import ExportListButton from '../common/ExportListButton'
|
||||
|
||||
const ReturnPolicies = () => {
|
||||
const tableRef = useRef()
|
||||
const [viewMode, setViewMode] = useViewMode('returnPolicy')
|
||||
const [columnVisibility, setColumnVisibility] = useColumnVisibility('returnPolicy')
|
||||
const [showFilterSidebar, setShowFilterSidebar] =
|
||||
useFilterSidebarVisibility('ReturnPolicies')
|
||||
const [showSortSidebar, setShowSortSidebar] =
|
||||
useSortSidebarVisibility('ReturnPolicies')
|
||||
|
||||
return (
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='returnPolicy'
|
||||
pageName='list'
|
||||
onReload={() => tableRef.current?.reload()}
|
||||
/>
|
||||
<ColumnViewButton
|
||||
type='returnPolicy'
|
||||
loading={false}
|
||||
visibleState={columnVisibility}
|
||||
updateVisibleState={setColumnVisibility}
|
||||
/>
|
||||
<ExportListButton objectType='returnPolicy' />
|
||||
</Space>
|
||||
<Space>
|
||||
<SortSidebarButton
|
||||
active={showSortSidebar}
|
||||
onClick={() => setShowSortSidebar(!showSortSidebar)}
|
||||
/>
|
||||
<FilterSidebarButton
|
||||
active={showFilterSidebar}
|
||||
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
|
||||
/>
|
||||
<ObjectTableViewButton
|
||||
viewMode={viewMode}
|
||||
setViewMode={setViewMode}
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
visibleColumns={columnVisibility}
|
||||
type='returnPolicy'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
showSortSidebar={showSortSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReturnPolicies
|
||||
@ -0,0 +1,96 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import NewObjectForm from '../../common/NewObjectForm'
|
||||
import WizardView from '../../common/WizardView'
|
||||
|
||||
const NewReturnPolicy = ({ onOk, defaultValues }) => {
|
||||
return (
|
||||
<NewObjectForm
|
||||
type={'returnPolicy'}
|
||||
defaultValues={{
|
||||
returnsAccepted: true,
|
||||
returnPeriodDays: 30,
|
||||
returnShippingCostPayer: 'buyer',
|
||||
refundMethod: 'moneyBack',
|
||||
...defaultValues
|
||||
}}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='returnPolicy'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={160}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='returnPolicy'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={160}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='returnPolicy'
|
||||
column={1}
|
||||
bordered={false}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
_reference: false,
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
labelWidth={160}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
return (
|
||||
<WizardView
|
||||
steps={steps}
|
||||
loading={submitLoading}
|
||||
formValid={formValid}
|
||||
title='New Return Policy'
|
||||
onSubmit={async () => {
|
||||
const result = await handleSubmit()
|
||||
if (result) {
|
||||
onOk()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NewObjectForm>
|
||||
)
|
||||
}
|
||||
|
||||
NewReturnPolicy.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
reset: PropTypes.bool,
|
||||
defaultValues: PropTypes.object
|
||||
}
|
||||
|
||||
export default NewReturnPolicy
|
||||
@ -0,0 +1,264 @@
|
||||
import { useRef, useState, useMemo } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space, Spin } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import MarketplaceIcon from '../../../Icons/MarketplaceIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
|
||||
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 {
|
||||
getModelProperty,
|
||||
getModelByName
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const log = loglevel.getLogger('ReturnPolicyInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
|
||||
const ReturnPolicyInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const returnPolicyId = new URLSearchParams(location.search).get(
|
||||
'returnPolicyId'
|
||||
)
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'ReturnPolicyInfo',
|
||||
{
|
||||
info: true,
|
||||
marketplaces: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
lock: null,
|
||||
loading: false,
|
||||
editDisabled: false,
|
||||
objectData: {}
|
||||
})
|
||||
|
||||
const currentObjectTools = useMemo(
|
||||
() => (
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={returnPolicyId}
|
||||
objectType='returnPolicy'
|
||||
showEndingDivider={false}
|
||||
/>
|
||||
),
|
||||
[objectFormState.loading, objectFormState.isEditing, returnPolicyId]
|
||||
)
|
||||
useDashboardObjectTools(currentObjectTools)
|
||||
|
||||
const actions = {
|
||||
edit: () => {
|
||||
objectFormRef?.current?.startEditing?.()
|
||||
return false
|
||||
},
|
||||
cancelEdit: () => {
|
||||
objectFormRef?.current?.cancelEditing?.()
|
||||
return true
|
||||
},
|
||||
finishEdit: () => {
|
||||
objectFormRef?.current?.handleUpdate?.()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='returnPolicy'
|
||||
pageName='info'
|
||||
id={returnPolicyId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Return Policy Information' },
|
||||
{ key: 'marketplaces', label: 'Marketplaces' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<UserNotifierToggle
|
||||
type='returnPolicy'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='returnPolicy'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
isEditing={objectFormState.isEditing}
|
||||
handleUpdate={() => {
|
||||
actionHandlerRef.current.callAction('finishEdit')
|
||||
}}
|
||||
cancelEditing={() => {
|
||||
actionHandlerRef.current.callAction('cancelEdit')
|
||||
}}
|
||||
startEditing={() => {
|
||||
actionHandlerRef.current.callAction('edit')
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther ||
|
||||
objectFormState.loading ||
|
||||
objectFormState.editDisabled
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
<Flex vertical gap={'large'}>
|
||||
<ActionHandler
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<ObjectForm
|
||||
id={returnPolicyId}
|
||||
type='returnPolicy'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<Flex vertical gap={'large'}>
|
||||
<InfoCollapse
|
||||
title='Return Policy Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('info', expanded)
|
||||
}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='returnPolicy'
|
||||
objectData={objectData}
|
||||
labelWidth={225}
|
||||
visibleProperties={{
|
||||
marketplaces: false
|
||||
}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Marketplaces'
|
||||
icon={<MarketplaceIcon />}
|
||||
active={collapseState.marketplaces}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('marketplaces', expanded)
|
||||
}
|
||||
collapseKey='marketplaces'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('returnPolicy', 'marketplaces')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Spin
|
||||
spinning={objectFormState.loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={returnPolicyId} type='returnPolicy' />
|
||||
</Card>
|
||||
</Spin>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Audit Logs'
|
||||
icon={<AuditLogIcon />}
|
||||
active={collapseState.auditLogs}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('auditLogs', expanded)
|
||||
}
|
||||
collapseKey='auditLogs'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{
|
||||
parent:
|
||||
getModelByName('returnPolicy').prefix +
|
||||
':' +
|
||||
returnPolicyId
|
||||
}}
|
||||
visibleColumns={{ _id: false, parent: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReturnPolicyInfo
|
||||
74
src/components/Dashboard/common/CustomTreeSelect.jsx
Normal file
74
src/components/Dashboard/common/CustomTreeSelect.jsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { TreeSelect } from 'antd'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export const findTreeNode = (nodes, value) => {
|
||||
if (!Array.isArray(nodes) || value == null || value === '') return null
|
||||
const match = String(value)
|
||||
for (const node of nodes) {
|
||||
if (node?.value != null && String(node.value) === match) {
|
||||
return node
|
||||
}
|
||||
const child = findTreeNode(node?.children, value)
|
||||
if (child) return child
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const findTreeLabel = (nodes, value) => {
|
||||
const node = findTreeNode(nodes, value)
|
||||
return node?.title || node?.label || null
|
||||
}
|
||||
|
||||
const CustomTreeSelect = ({
|
||||
placeholder,
|
||||
disabled,
|
||||
options = [],
|
||||
value,
|
||||
onChange,
|
||||
...rest
|
||||
}) => {
|
||||
const prevOptionsRef = useRef(options)
|
||||
const treeData = Array.isArray(options) ? options : []
|
||||
|
||||
useEffect(() => {
|
||||
if (prevOptionsRef.current === options) return
|
||||
|
||||
if (value !== undefined && value !== null) {
|
||||
const valueExists = findTreeNode(options, value)
|
||||
if (!valueExists && onChange) {
|
||||
onChange(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
prevOptionsRef.current = options
|
||||
}, [options, value, onChange])
|
||||
|
||||
return (
|
||||
<TreeSelect
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
treeData={treeData}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
showSearch
|
||||
allowClear
|
||||
treeNodeFilterProp='title'
|
||||
treeLine
|
||||
listHeight={400}
|
||||
popupMatchSelectWidth={false}
|
||||
style={{ width: '100%' }}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
CustomTreeSelect.propTypes = {
|
||||
placeholder: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
options: PropTypes.array,
|
||||
value: PropTypes.any,
|
||||
onChange: PropTypes.func
|
||||
}
|
||||
|
||||
export default CustomTreeSelect
|
||||
@ -4,7 +4,7 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import ArrowLeftIcon from '../../Icons/ArrowLeftIcon'
|
||||
import ArrowRightIcon from '../../Icons/ArrowRightIcon'
|
||||
import FilterIcon from '../../Icons/FilterIcon'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
import { getModelByPluralName } from '../../../database/ObjectModels'
|
||||
import { useTableState } from '../context/TableStateContext'
|
||||
|
||||
const breadcrumbNameMap = {
|
||||
@ -19,6 +19,7 @@ const breadcrumbNameMap = {
|
||||
design: 'Design',
|
||||
control: 'Control',
|
||||
preview: 'Preview',
|
||||
settings: 'Settings',
|
||||
about: 'About'
|
||||
}
|
||||
|
||||
@ -32,13 +33,8 @@ const DashboardBreadcrumb = () => {
|
||||
|
||||
function segmentToModel(segment) {
|
||||
if (segment) {
|
||||
// If segment ends with 's', remove it and get the model
|
||||
if (segment.endsWith('s')) {
|
||||
const singularSegment = segment.slice(0, -1)
|
||||
return getModelByName(singularSegment, true)
|
||||
}
|
||||
// Otherwise, get the model as is
|
||||
return getModelByName(segment, true)
|
||||
return getModelByPluralName(segment, true)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@ -48,7 +44,7 @@ const DashboardBreadcrumb = () => {
|
||||
if (segment !== 'dashboard') {
|
||||
const isMainSection = mainSections.includes(segment)
|
||||
const model = segmentToModel(segment)
|
||||
const modelLabelPlural = model?.label ? `${model.label}s` : null
|
||||
const modelLabelPlural = model?.labelPlural ? model.labelPlural : null
|
||||
const name = breadcrumbNameMap[segment] || modelLabelPlural || segment
|
||||
const showFilterIcon =
|
||||
hasPageFilter(url) ||
|
||||
|
||||
362
src/components/Dashboard/common/FileGalleryList.jsx
Normal file
362
src/components/Dashboard/common/FileGalleryList.jsx
Normal file
@ -0,0 +1,362 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Button, Card, Flex, Divider } from 'antd'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useMediaQuery } from 'react-responsive'
|
||||
import FilePreview from './FilePreview'
|
||||
import ScrollBox from './ScrollBox'
|
||||
import Thumbnail from './Thumbnail'
|
||||
import ChevronLeftIcon from '../../Icons/ChevronLeftIcon'
|
||||
import ChevronRightIcon from '../../Icons/ChevronRightIcon'
|
||||
import BinIcon from '../../Icons/BinIcon'
|
||||
|
||||
const GALLERY_PADDING = 24
|
||||
const MIN_SIDE_PADDING = 66
|
||||
const MAX_SLIDE_HEIGHT = 600
|
||||
|
||||
const getFileKey = (file, index) => file?._id || file?.id || index
|
||||
|
||||
const getSlideLayout = (containerWidth, naturalWidth, naturalHeight) => {
|
||||
const availableWidth = Math.max(0, containerWidth - MIN_SIDE_PADDING * 2)
|
||||
if (!containerWidth || availableWidth <= 0) {
|
||||
return {
|
||||
padding: MIN_SIDE_PADDING,
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
|
||||
const aspect =
|
||||
naturalWidth > 0 && naturalHeight > 0 ? naturalWidth / naturalHeight : 1
|
||||
|
||||
let width = availableWidth
|
||||
let height = width / aspect
|
||||
|
||||
if (height > MAX_SLIDE_HEIGHT) {
|
||||
height = MAX_SLIDE_HEIGHT
|
||||
width = height * aspect
|
||||
}
|
||||
|
||||
return {
|
||||
padding: Math.max(MIN_SIDE_PADDING, (containerWidth - width) / 2),
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
const FileGalleryList = ({
|
||||
files = [],
|
||||
editing = false,
|
||||
onRemove,
|
||||
uploadSlot = null
|
||||
}) => {
|
||||
const filesToRender = useMemo(
|
||||
() => (Array.isArray(files) ? files : files ? [files] : []),
|
||||
[files]
|
||||
)
|
||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
const previewRef = useRef(null)
|
||||
const scrollElRef = useRef(null)
|
||||
const slideRefs = useRef([])
|
||||
const [containerWidth, setContainerWidth] = useState(0)
|
||||
const [imageSizes, setImageSizes] = useState({})
|
||||
const [activeIndex, setActiveIndex] = useState(0)
|
||||
|
||||
const slideLayouts = useMemo(
|
||||
() =>
|
||||
filesToRender.map((file, index) => {
|
||||
const size = imageSizes[getFileKey(file, index)]
|
||||
return getSlideLayout(containerWidth, size?.width, size?.height)
|
||||
}),
|
||||
[containerWidth, filesToRender, imageSizes]
|
||||
)
|
||||
|
||||
const slideHeight = useMemo(() => {
|
||||
const heights = slideLayouts.map((layout) => layout.height)
|
||||
return Math.max(0, ...heights)
|
||||
}, [slideLayouts])
|
||||
|
||||
useEffect(() => {
|
||||
const el = previewRef.current
|
||||
if (!el) return undefined
|
||||
|
||||
const updateWidth = () => {
|
||||
setContainerWidth(el.clientWidth)
|
||||
}
|
||||
|
||||
updateWidth()
|
||||
const observer = new ResizeObserver(updateWidth)
|
||||
observer.observe(el)
|
||||
return () => observer.disconnect()
|
||||
}, [filesToRender.length])
|
||||
|
||||
useEffect(() => {
|
||||
const root = previewRef.current
|
||||
if (!root) return undefined
|
||||
|
||||
const collectSizes = () => {
|
||||
const next = {}
|
||||
filesToRender.forEach((file, index) => {
|
||||
const img = slideRefs.current[index]?.querySelector('img')
|
||||
if (img?.naturalWidth && img.naturalHeight) {
|
||||
next[getFileKey(file, index)] = {
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight
|
||||
}
|
||||
}
|
||||
})
|
||||
setImageSizes((prev) => {
|
||||
const nextKeys = Object.keys(next)
|
||||
if (
|
||||
nextKeys.length === Object.keys(prev).length &&
|
||||
nextKeys.every(
|
||||
(key) =>
|
||||
prev[key]?.width === next[key].width &&
|
||||
prev[key]?.height === next[key].height
|
||||
)
|
||||
) {
|
||||
return prev
|
||||
}
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const onLoadCapture = (event) => {
|
||||
if (event.target?.tagName === 'IMG') {
|
||||
collectSizes()
|
||||
}
|
||||
}
|
||||
|
||||
root.addEventListener('load', onLoadCapture, true)
|
||||
collectSizes()
|
||||
const mutationObserver = new MutationObserver(collectSizes)
|
||||
mutationObserver.observe(root, { childList: true, subtree: true })
|
||||
|
||||
return () => {
|
||||
root.removeEventListener('load', onLoadCapture, true)
|
||||
mutationObserver.disconnect()
|
||||
}
|
||||
}, [filesToRender])
|
||||
|
||||
useEffect(() => {
|
||||
if (activeIndex >= filesToRender.length) {
|
||||
setActiveIndex(Math.max(0, filesToRender.length - 1))
|
||||
}
|
||||
}, [activeIndex, filesToRender.length])
|
||||
|
||||
useEffect(() => {
|
||||
const root = scrollElRef.current
|
||||
if (!root || filesToRender.length === 0) return undefined
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const visible = entries
|
||||
.filter((entry) => entry.isIntersecting)
|
||||
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0]
|
||||
if (!visible) return
|
||||
const index = slideRefs.current.findIndex(
|
||||
(node) => node === visible.target
|
||||
)
|
||||
if (index >= 0) {
|
||||
setActiveIndex(index)
|
||||
}
|
||||
},
|
||||
{ root, threshold: 0.6 }
|
||||
)
|
||||
|
||||
slideRefs.current.forEach((node) => {
|
||||
if (node) observer.observe(node)
|
||||
})
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [filesToRender.length, containerWidth, slideHeight])
|
||||
|
||||
const scrollToIndex = useCallback((index) => {
|
||||
const nextIndex = Math.max(0, Math.min(index, slideRefs.current.length - 1))
|
||||
const node = slideRefs.current[nextIndex]
|
||||
if (!node) return
|
||||
node.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
inline: 'center',
|
||||
block: 'nearest'
|
||||
})
|
||||
setActiveIndex(nextIndex)
|
||||
}, [])
|
||||
|
||||
const handleRemove = (fileToRemove) => {
|
||||
onRemove?.(fileToRemove)
|
||||
}
|
||||
|
||||
const activePadding = slideLayouts[activeIndex]?.padding || MIN_SIDE_PADDING
|
||||
|
||||
return (
|
||||
<Card
|
||||
className='file-gallery-list'
|
||||
styles={{
|
||||
body: {
|
||||
paddingTop: GALLERY_PADDING,
|
||||
paddingBottom: GALLERY_PADDING,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0
|
||||
}
|
||||
}}
|
||||
>
|
||||
{filesToRender.length > 0 ? (
|
||||
<div
|
||||
className={`file-gallery-list-preview${isMobile ? ' is-mobile' : ''}`}
|
||||
ref={previewRef}
|
||||
style={
|
||||
slideHeight
|
||||
? {
|
||||
height: slideHeight,
|
||||
'--file-gallery-side-padding': `${activePadding}px`
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className='file-gallery-list-nav file-gallery-list-nav-left'
|
||||
icon={<ChevronLeftIcon />}
|
||||
disabled={activeIndex <= 0}
|
||||
onClick={() => scrollToIndex(activeIndex - 1)}
|
||||
aria-label='Previous image'
|
||||
/>
|
||||
<ScrollBox
|
||||
className='file-gallery-list-scroll'
|
||||
horizontalBottomPadding={0}
|
||||
verticalRightPadding={0}
|
||||
scrollableNodeProps={{
|
||||
ref: (node) => {
|
||||
scrollElRef.current = node
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='file-gallery-list-track'
|
||||
style={slideHeight ? { height: slideHeight } : undefined}
|
||||
>
|
||||
{filesToRender.map((file, index) => {
|
||||
const layout = slideLayouts[index] || {
|
||||
padding: MIN_SIDE_PADDING,
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={file._id || file.id || index}
|
||||
className='file-gallery-list-slide'
|
||||
ref={(node) => {
|
||||
slideRefs.current[index] = node
|
||||
}}
|
||||
style={{
|
||||
flex: `0 0 ${containerWidth || 0}px`,
|
||||
width: containerWidth || undefined,
|
||||
paddingLeft: layout.padding,
|
||||
paddingRight: layout.padding
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='file-gallery-list-preview-frame'
|
||||
style={
|
||||
layout.width && layout.height
|
||||
? {
|
||||
width: layout.width,
|
||||
height: layout.height
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<FilePreview
|
||||
file={file}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'contain',
|
||||
borderRadius: 8
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</ScrollBox>
|
||||
<Button
|
||||
className='file-gallery-list-nav file-gallery-list-nav-right'
|
||||
icon={<ChevronRightIcon />}
|
||||
disabled={activeIndex >= filesToRender.length - 1}
|
||||
onClick={() => scrollToIndex(activeIndex + 1)}
|
||||
aria-label='Next image'
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className='file-gallery-footer'>
|
||||
{filesToRender.length > 0 ? (
|
||||
<Divider style={{ margin: '24px 0' }} />
|
||||
) : null}
|
||||
<Flex
|
||||
className='file-gallery-list-thumbs'
|
||||
gap={GALLERY_PADDING}
|
||||
align='center'
|
||||
justify='center'
|
||||
wrap='nowrap'
|
||||
>
|
||||
{filesToRender.map((file, index) => (
|
||||
<div
|
||||
key={file._id || file.id || index}
|
||||
className={`file-gallery-list-thumb${
|
||||
index === activeIndex ? ' is-active' : ''
|
||||
}`}
|
||||
onClick={() => scrollToIndex(index)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault()
|
||||
scrollToIndex(index)
|
||||
}
|
||||
}}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
aria-label={`Show image ${index + 1}`}
|
||||
aria-current={index === activeIndex ? 'true' : undefined}
|
||||
>
|
||||
<Thumbnail
|
||||
file={file}
|
||||
size={64}
|
||||
alt={file.name || file.filename || ''}
|
||||
/>
|
||||
{editing ? (
|
||||
<Card
|
||||
size='small'
|
||||
className='file-gallery-list-thumb-delete'
|
||||
styles={{ body: { padding: 2 } }}
|
||||
>
|
||||
<Button
|
||||
type='text'
|
||||
size='small'
|
||||
icon={<BinIcon />}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
handleRemove(file)
|
||||
}}
|
||||
aria-label='Remove image'
|
||||
/>
|
||||
</Card>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
{editing && uploadSlot ? (
|
||||
<div className='file-gallery-list-upload'>{uploadSlot}</div>
|
||||
) : null}
|
||||
</Flex>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
FileGalleryList.propTypes = {
|
||||
files: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
editing: PropTypes.bool,
|
||||
onRemove: PropTypes.func,
|
||||
uploadSlot: PropTypes.node
|
||||
}
|
||||
|
||||
export default FileGalleryList
|
||||
@ -7,11 +7,13 @@ import DownloadIcon from '../../Icons/DownloadIcon'
|
||||
import { useContext, useState } from 'react'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
import FilePreview from './FilePreview'
|
||||
import FileGalleryList from './FileGalleryList'
|
||||
import EyeSlashIcon from '../../Icons/EyeSlashIcon'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import ElipsisText from './ElipsisText'
|
||||
import MissingPlaceholder from './MissingPlaceholder'
|
||||
|
||||
const FileList = ({
|
||||
files,
|
||||
@ -24,6 +26,8 @@ const FileList = ({
|
||||
defaultPreviewOpen = false,
|
||||
minimal = false,
|
||||
card = true,
|
||||
gallery = false,
|
||||
uploadSlot = null,
|
||||
maxWidth = '100%'
|
||||
}) => {
|
||||
const { fetchFileContent, flushFile } = useContext(ApiServerContext)
|
||||
@ -37,10 +41,6 @@ const FileList = ({
|
||||
? !files || !Array.isArray(files) || files.length === 0
|
||||
: !files
|
||||
|
||||
if (hasNoItems) {
|
||||
return null
|
||||
}
|
||||
|
||||
const handleRemove = (fileToRemove) => {
|
||||
flushFile(fileToRemove._id)
|
||||
if (multiple) {
|
||||
@ -60,7 +60,41 @@ const FileList = ({
|
||||
await fetchFileContent(file, true)
|
||||
}
|
||||
|
||||
const filesToRender = multiple ? files : [files]
|
||||
const filesToRender = multiple
|
||||
? Array.isArray(files)
|
||||
? files
|
||||
: []
|
||||
: files
|
||||
? [files]
|
||||
: []
|
||||
|
||||
const rootStyle = {
|
||||
minWidth: 0,
|
||||
maxWidth,
|
||||
...(card
|
||||
? { width: '100%' }
|
||||
: { width: 'fit-content', alignSelf: 'flex-start' })
|
||||
}
|
||||
|
||||
if (gallery && !minimal) {
|
||||
if (hasNoItems && !(gallery && (editing || uploadSlot))) {
|
||||
return <MissingPlaceholder message='No listing images.' />
|
||||
}
|
||||
return (
|
||||
<div style={rootStyle}>
|
||||
<FileGalleryList
|
||||
files={filesToRender}
|
||||
editing={editing}
|
||||
uploadSlot={uploadSlot}
|
||||
onRemove={handleRemove}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (hasNoItems && !(gallery && (editing || uploadSlot))) {
|
||||
return null
|
||||
}
|
||||
|
||||
const renderFileContent = (file) => (
|
||||
<Flex
|
||||
@ -151,14 +185,6 @@ const FileList = ({
|
||||
</Flex>
|
||||
)
|
||||
|
||||
const rootStyle = {
|
||||
minWidth: 0,
|
||||
maxWidth,
|
||||
...(card
|
||||
? { width: '100%' }
|
||||
: { width: 'fit-content', alignSelf: 'flex-start' })
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={rootStyle}>
|
||||
{filesToRender.map((file, index) => {
|
||||
@ -205,6 +231,8 @@ FileList.propTypes = {
|
||||
showDownload: PropTypes.bool,
|
||||
defaultPreviewOpen: PropTypes.bool,
|
||||
card: PropTypes.bool,
|
||||
gallery: PropTypes.bool,
|
||||
uploadSlot: PropTypes.node,
|
||||
minimal: PropTypes.bool,
|
||||
maxWidth: PropTypes.string
|
||||
}
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
import { useCallback, useContext, useEffect, useState, memo } from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
memo,
|
||||
useRef
|
||||
} from 'react'
|
||||
import LoadingPlaceholder from './LoadingPlaceholder'
|
||||
import GCodePreview from './GCodePreview'
|
||||
import ThreeDPreview from './ThreeDPreview'
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
|
||||
const FilePreview = ({ file, style = {} }) => {
|
||||
useEffect(() => {
|
||||
console.log('FILEPREVIEWFILE', file)
|
||||
}, [file])
|
||||
const { token } = useContext(AuthContext)
|
||||
const { fetchFileContent } = useContext(ApiServerContext)
|
||||
|
||||
@ -14,6 +24,8 @@ const FilePreview = ({ file, style = {} }) => {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
const currentId = useRef(null)
|
||||
|
||||
const fetchPreview = useCallback(async () => {
|
||||
if (error != null) {
|
||||
return
|
||||
@ -31,7 +43,8 @@ const FilePreview = ({ file, style = {} }) => {
|
||||
}, [file, fetchFileContent, error])
|
||||
|
||||
useEffect(() => {
|
||||
if (file?.type && token != null) {
|
||||
if (file?.type && token != null && file._id !== currentId.current) {
|
||||
currentId.current = file._id
|
||||
fetchPreview()
|
||||
}
|
||||
}, [file._id, file?.type, fetchPreview, token])
|
||||
|
||||
@ -28,6 +28,62 @@ const getFileIdentity = (value, multiple) => {
|
||||
return value?._id || value || ''
|
||||
}
|
||||
|
||||
const getMasterFilterAccept = (masterFilter) =>
|
||||
Array.isArray(masterFilter) && masterFilter.length > 0
|
||||
? masterFilter.join(',')
|
||||
: undefined
|
||||
|
||||
const FileUploadDropzone = ({ onUploaded, multiple = true, masterFilter = [] }) => {
|
||||
const { uploadFile } = useContext(ApiServerContext)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [uploadProgress, setUploadProgress] = useState(0)
|
||||
|
||||
const handleFileUpload = async (file) => {
|
||||
try {
|
||||
setUploading(true)
|
||||
const uploadedFile = await uploadFile(file, {}, (progress) => {
|
||||
setUploadProgress(progress)
|
||||
})
|
||||
if (uploadedFile) {
|
||||
onUploaded?.(uploadedFile)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('File upload failed:', error)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
setUploadProgress(0)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
<Upload.Dragger
|
||||
beforeUpload={handleFileUpload}
|
||||
showUploadList={false}
|
||||
multiple={multiple}
|
||||
accept={getMasterFilterAccept(masterFilter)}
|
||||
className='file-gallery-list-dropzone'
|
||||
openFileDialogOnClick
|
||||
>
|
||||
{uploading ? (
|
||||
uploadProgress > 0 && uploadProgress < 100 ? (
|
||||
<Text style={{ fontSize: 11 }}>{uploadProgress}%</Text>
|
||||
) : (
|
||||
<LoadingOutlined />
|
||||
)
|
||||
) : (
|
||||
<PlusIcon />
|
||||
)}
|
||||
</Upload.Dragger>
|
||||
)
|
||||
}
|
||||
|
||||
FileUploadDropzone.propTypes = {
|
||||
onUploaded: PropTypes.func,
|
||||
multiple: PropTypes.bool,
|
||||
masterFilter: PropTypes.array
|
||||
}
|
||||
|
||||
const FileUpload = ({
|
||||
value,
|
||||
onChange,
|
||||
@ -36,6 +92,7 @@ const FileUpload = ({
|
||||
defaultPreviewOpen = false,
|
||||
showPreview = true,
|
||||
showInfo,
|
||||
gallery = false,
|
||||
masterFilter = []
|
||||
}) => {
|
||||
const { uploadFile } = useContext(ApiServerContext)
|
||||
@ -135,6 +192,37 @@ const FileUpload = ({
|
||||
)
|
||||
}
|
||||
|
||||
if (gallery) {
|
||||
return (
|
||||
<FileList
|
||||
files={currentFiles}
|
||||
multiple={multiple}
|
||||
editing={true}
|
||||
gallery={true}
|
||||
showInfo={showInfo || false}
|
||||
showPreview={showPreview}
|
||||
defaultPreviewOpen={defaultPreviewOpen}
|
||||
uploadSlot={
|
||||
<FileUploadDropzone
|
||||
multiple={multiple}
|
||||
masterFilter={masterFilter}
|
||||
onUploaded={(uploadedFile) => {
|
||||
if (multiple) {
|
||||
const existing = Array.isArray(currentFiles) ? currentFiles : []
|
||||
updateCurrentFiles([...existing, uploadedFile])
|
||||
} else {
|
||||
updateCurrentFiles(uploadedFile)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
onChange={(updatedFiles) => {
|
||||
updateCurrentFiles(updatedFiles)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex gap={'small'} vertical>
|
||||
{hasNoItems && uploading == false ? (
|
||||
@ -220,6 +308,7 @@ FileUpload.propTypes = {
|
||||
showInfo: PropTypes.bool,
|
||||
defaultPreviewOpen: PropTypes.bool,
|
||||
minimal: PropTypes.bool,
|
||||
gallery: PropTypes.bool,
|
||||
masterFilter: PropTypes.array
|
||||
}
|
||||
|
||||
|
||||
@ -783,19 +783,16 @@ const ObjectForm = forwardRef(
|
||||
isEditingRef.current = false
|
||||
onStateChangeRef.current({ isEditing: isEditingRef.current })
|
||||
|
||||
serverObjectData.current = updatedObject
|
||||
const computedEntries = calculateComputedValues(
|
||||
updatedObject,
|
||||
model,
|
||||
{
|
||||
skipObjectChildrenValue: true
|
||||
}
|
||||
)
|
||||
const mergedObject = mergeFormData(updatedObject, currentFormData)
|
||||
serverObjectData.current = mergedObject
|
||||
const computedEntries = calculateComputedValues(mergedObject, model, {
|
||||
skipObjectChildrenValue: true
|
||||
})
|
||||
const nextObjectData = applyComputedEntries(
|
||||
updatedObject,
|
||||
mergedObject,
|
||||
computedEntries
|
||||
)
|
||||
copyObjectChildren(nextObjectData, updatedObject, model)
|
||||
copyObjectChildren(nextObjectData, mergedObject, model)
|
||||
setObjectData({
|
||||
...nextObjectData,
|
||||
_isEditing: false
|
||||
|
||||
@ -6,7 +6,8 @@ import {
|
||||
InputNumber,
|
||||
Form,
|
||||
Select,
|
||||
Switch
|
||||
Switch,
|
||||
Tree
|
||||
} from 'antd'
|
||||
import IdDisplay from './IdDisplay'
|
||||
import TimeDisplay from './TimeDisplay'
|
||||
@ -41,6 +42,7 @@ import ObjectTypeSelect from './ObjectTypeSelect'
|
||||
import ObjectTypeDisplay from './ObjectTypeDisplay'
|
||||
import CodeBlockEditor from './CodeBlockEditor'
|
||||
import CustomSelect from './CustomSelect'
|
||||
import CustomTreeSelect, { findTreeLabel } from './CustomTreeSelect'
|
||||
import StateDisplay from './StateDisplay'
|
||||
import AlertsDisplay from './AlertsDisplay'
|
||||
import FileUpload from './FileUpload'
|
||||
@ -136,6 +138,7 @@ const ObjectProperty = ({
|
||||
hiddenPropertyWidth = '100px',
|
||||
inherit = true,
|
||||
scrollHeight,
|
||||
gallery = false,
|
||||
...rest
|
||||
}) => {
|
||||
if (typeof value === 'function') {
|
||||
@ -270,6 +273,28 @@ const ObjectProperty = ({
|
||||
)
|
||||
}
|
||||
}
|
||||
case 'treeSelect': {
|
||||
if (Array.isArray(value) && value.length) {
|
||||
return (
|
||||
<Tree
|
||||
treeData={value}
|
||||
fieldNames={{ title: 'title', key: 'value', children: 'children' }}
|
||||
height={320}
|
||||
virtual
|
||||
defaultExpandAll={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const treeLabel = findTreeLabel(options, value)
|
||||
if (treeLabel || (value != null && value !== '')) {
|
||||
return <Text {...textParams}>{treeLabel || String(value)}</Text>
|
||||
}
|
||||
return (
|
||||
<Text type='secondary' {...textParams}>
|
||||
n/a
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
case 'priceMode':
|
||||
switch (value) {
|
||||
case 'margin':
|
||||
@ -655,6 +680,8 @@ const ObjectProperty = ({
|
||||
showPreview={showPreview}
|
||||
showInfo={showHyperlink}
|
||||
showDownload={showDownload}
|
||||
gallery={gallery}
|
||||
editing={false}
|
||||
masterFilter={masterFilter}
|
||||
/>
|
||||
)
|
||||
@ -670,6 +697,8 @@ const ObjectProperty = ({
|
||||
defaultPreviewOpen={previewOpen}
|
||||
showPreview={showPreview}
|
||||
showInfo={showHyperlink}
|
||||
gallery={gallery}
|
||||
editing={false}
|
||||
masterFilter={masterFilter}
|
||||
/>
|
||||
)
|
||||
@ -838,6 +867,14 @@ const ObjectProperty = ({
|
||||
{...inputProps}
|
||||
/>
|
||||
)
|
||||
case 'treeSelect':
|
||||
return (
|
||||
<CustomTreeSelect
|
||||
placeholder={'Select a ' + label.toLowerCase() + '...'}
|
||||
options={Array.isArray(options) ? options : []}
|
||||
{...inputProps}
|
||||
/>
|
||||
)
|
||||
case 'state':
|
||||
if (options?.length && !readOnly) {
|
||||
return (
|
||||
@ -1002,6 +1039,7 @@ const ObjectProperty = ({
|
||||
defaultPreviewOpen={previewOpen}
|
||||
showPreview={showPreview}
|
||||
showInfo={showHyperlink}
|
||||
gallery={gallery}
|
||||
masterFilter={masterFilter}
|
||||
{...inputProps}
|
||||
/>
|
||||
@ -1014,6 +1052,7 @@ const ObjectProperty = ({
|
||||
defaultPreviewOpen={previewOpen}
|
||||
showPreview={showPreview}
|
||||
showInfo={showHyperlink}
|
||||
gallery={gallery}
|
||||
masterFilter={masterFilter}
|
||||
{...inputProps}
|
||||
/>
|
||||
@ -1073,7 +1112,7 @@ const ObjectProperty = ({
|
||||
}
|
||||
|
||||
ObjectProperty.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||
modelType: PropTypes.string,
|
||||
value: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
|
||||
isEditing: PropTypes.bool,
|
||||
@ -1101,7 +1140,7 @@ ObjectProperty.propTypes = {
|
||||
showPreview: PropTypes.bool,
|
||||
useFormItem: PropTypes.bool,
|
||||
showHyperlink: PropTypes.bool,
|
||||
options: PropTypes.array,
|
||||
options: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
|
||||
showSince: PropTypes.bool,
|
||||
inTable: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
@ -1112,7 +1151,8 @@ ObjectProperty.propTypes = {
|
||||
style: PropTypes.object,
|
||||
hiddenPropertyWidth: PropTypes.string,
|
||||
inherit: PropTypes.bool,
|
||||
scrollHeight: PropTypes.string
|
||||
scrollHeight: PropTypes.string,
|
||||
gallery: PropTypes.bool
|
||||
}
|
||||
|
||||
export default ObjectProperty
|
||||
|
||||
@ -20,10 +20,19 @@ const StateDisplay = ({
|
||||
'queued',
|
||||
'printing',
|
||||
'used',
|
||||
'deploying'
|
||||
'deploying',
|
||||
'syncing',
|
||||
'publishing',
|
||||
'unpublishing'
|
||||
]
|
||||
const orangeProgressTypes = ['used', 'deploying', 'queued']
|
||||
const activeProgressTypes = ['printing', 'deploying']
|
||||
const activeProgressTypes = [
|
||||
'printing',
|
||||
'deploying',
|
||||
'publishing',
|
||||
'unpublishing',
|
||||
'syncing'
|
||||
]
|
||||
|
||||
const currentState = state || {
|
||||
type: 'unknown',
|
||||
|
||||
@ -100,6 +100,14 @@ const StateTag = ({ state, showBadge = true, showTag = true, style = {} }) => {
|
||||
status = 'processing'
|
||||
text = 'Syncing'
|
||||
break
|
||||
case 'publishing':
|
||||
status = 'processing'
|
||||
text = 'Publishing'
|
||||
break
|
||||
case 'unpublishing':
|
||||
status = 'processing'
|
||||
text = 'Unpublishing'
|
||||
break
|
||||
case 'disconnected':
|
||||
status = 'default'
|
||||
text = 'Disconnected'
|
||||
@ -112,6 +120,10 @@ const StateTag = ({ state, showBadge = true, showTag = true, style = {} }) => {
|
||||
status = 'warning'
|
||||
text = 'Queued'
|
||||
break
|
||||
case 'pending':
|
||||
status = 'default'
|
||||
text = 'Pending'
|
||||
break
|
||||
case 'used':
|
||||
status = 'warning'
|
||||
text = 'Used'
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import get from 'lodash/get'
|
||||
import mergeWith from 'lodash/mergeWith'
|
||||
import set from 'lodash/set'
|
||||
|
||||
const NESTED_OBJECT_KEYS = ['_id', '_reference', 'name', 'state']
|
||||
|
||||
export function capitalizeFirstLetter(string) {
|
||||
try {
|
||||
@ -71,3 +75,72 @@ export function mergeFormData(...sources) {
|
||||
mergeFormCustomizer
|
||||
)
|
||||
}
|
||||
|
||||
const pickNestedObjectFields = (value) => {
|
||||
if (value == null || typeof value !== 'object') {
|
||||
return value
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(pickNestedObjectFields)
|
||||
}
|
||||
|
||||
const picked = {}
|
||||
NESTED_OBJECT_KEYS.forEach((key) => {
|
||||
if (value[key] !== undefined) {
|
||||
picked[key] = value[key]
|
||||
}
|
||||
})
|
||||
return picked
|
||||
}
|
||||
|
||||
const stripProperties = (data, properties) => {
|
||||
if (data == null || typeof data !== 'object' || !Array.isArray(properties)) {
|
||||
return data
|
||||
}
|
||||
|
||||
const next = Array.isArray(data) ? [...data] : { ...data }
|
||||
|
||||
properties.forEach((property) => {
|
||||
if (!property?.name) return
|
||||
|
||||
const current = get(next, property.name)
|
||||
|
||||
if (property.type === 'object') {
|
||||
if (current === undefined) return
|
||||
set(next, property.name, pickNestedObjectFields(current))
|
||||
return
|
||||
}
|
||||
|
||||
if (property.type === 'objectList') {
|
||||
if (!Array.isArray(current)) return
|
||||
set(next, property.name, current.map(pickNestedObjectFields))
|
||||
return
|
||||
}
|
||||
|
||||
if (!Array.isArray(property.properties) || property.properties.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (property.type === 'objectChildren' || Array.isArray(current)) {
|
||||
if (!Array.isArray(current)) return
|
||||
set(
|
||||
next,
|
||||
property.name,
|
||||
current.map((child) => stripProperties(child, property.properties))
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (current != null && typeof current === 'object') {
|
||||
set(next, property.name, stripProperties(current, property.properties))
|
||||
}
|
||||
})
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
// Before create/update, keep nested object / objectList refs as stubs so we
|
||||
// do not post populated fields (e.g. marketplace category trees).
|
||||
export function stripNestedObjectProperties(data, modelDefinition) {
|
||||
return stripProperties(data, modelDefinition?.properties)
|
||||
}
|
||||
|
||||
6
src/components/Icons/FulfillmentPolicyIcon.jsx
Normal file
6
src/components/Icons/FulfillmentPolicyIcon.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
import Icon from '@ant-design/icons'
|
||||
import CustomIconSvg from '../../../assets/icons/fulfillmentpolicyicon.svg?react'
|
||||
|
||||
const FulfillmentPolicyIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
||||
|
||||
export default FulfillmentPolicyIcon
|
||||
6
src/components/Icons/PaymentPolicyIcon.jsx
Normal file
6
src/components/Icons/PaymentPolicyIcon.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
import Icon from '@ant-design/icons'
|
||||
import CustomIconSvg from '../../../assets/icons/paymentpolicyicon.svg?react'
|
||||
|
||||
const PaymentPolicyIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
||||
|
||||
export default PaymentPolicyIcon
|
||||
6
src/components/Icons/ReturnPolicyIcon.jsx
Normal file
6
src/components/Icons/ReturnPolicyIcon.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
import Icon from '@ant-design/icons'
|
||||
import CustomIconSvg from '../../../assets/icons/returnpolicyicon.svg?react'
|
||||
|
||||
const ReturnPolicyIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
||||
|
||||
export default ReturnPolicyIcon
|
||||
@ -21,6 +21,7 @@ import ClientIcon from './ClientIcon'
|
||||
import SalesOrderIcon from './SalesOrderIcon'
|
||||
import MarketplaceIcon from './MarketplaceIcon'
|
||||
import ListingIcon from './ListingIcon'
|
||||
import ListingVarientIcon from './ListingVarientIcon'
|
||||
import FinanceIcon from './FinanceIcon'
|
||||
import InvoiceIcon from './InvoiceIcon'
|
||||
import PaymentIcon from './PaymentIcon'
|
||||
@ -51,6 +52,9 @@ import CourierIcon from './CourierIcon'
|
||||
import CourierServiceIcon from './CourierServiceIcon'
|
||||
import TaxRateIcon from './TaxRateIcon'
|
||||
import TaxRecordIcon from './TaxRecordIcon'
|
||||
import FulfillmentPolicyIcon from './FulfillmentPolicyIcon'
|
||||
import ReturnPolicyIcon from './ReturnPolicyIcon'
|
||||
import PaymentPolicyIcon from './PaymentPolicyIcon'
|
||||
import AppPasswordIcon from './AppPasswordIcon'
|
||||
import InfoCircleIcon from './InfoCircleIcon'
|
||||
|
||||
@ -80,6 +84,10 @@ const sidebarIconMap = {
|
||||
salesOrder: <SalesOrderIcon />,
|
||||
marketplace: <MarketplaceIcon />,
|
||||
listing: <ListingIcon />,
|
||||
listingVarient: <ListingVarientIcon />,
|
||||
fulfillmentPolicy: <FulfillmentPolicyIcon />,
|
||||
returnPolicy: <ReturnPolicyIcon />,
|
||||
paymentPolicy: <PaymentPolicyIcon />,
|
||||
finance: <FinanceIcon />,
|
||||
invoice: <InvoiceIcon />,
|
||||
payment: <PaymentIcon />,
|
||||
|
||||
@ -49,6 +49,9 @@ import { SalesOrder } from './models/SalesOrder.js'
|
||||
import { Marketplace } from './models/Marketplace.js'
|
||||
import { Listing } from './models/Listing.js'
|
||||
import { ListingVarient } from './models/ListingVarient.js'
|
||||
import { FulfillmentPolicy } from './models/FulfillmentPolicy.js'
|
||||
import { ReturnPolicy } from './models/ReturnPolicy.js'
|
||||
import { PaymentPolicy } from './models/PaymentPolicy.js'
|
||||
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
|
||||
|
||||
export const objectModels = [
|
||||
@ -102,7 +105,10 @@ export const objectModels = [
|
||||
SalesOrder,
|
||||
Marketplace,
|
||||
Listing,
|
||||
ListingVarient
|
||||
ListingVarient,
|
||||
FulfillmentPolicy,
|
||||
ReturnPolicy,
|
||||
PaymentPolicy
|
||||
]
|
||||
|
||||
// Re-export individual models for direct access
|
||||
@ -157,7 +163,10 @@ export {
|
||||
SalesOrder,
|
||||
Marketplace,
|
||||
Listing,
|
||||
ListingVarient
|
||||
ListingVarient,
|
||||
FulfillmentPolicy,
|
||||
ReturnPolicy,
|
||||
PaymentPolicy
|
||||
}
|
||||
|
||||
export function getModelByName(name, ignoreCase = false) {
|
||||
@ -179,6 +188,27 @@ export function getModelByName(name, ignoreCase = false) {
|
||||
)
|
||||
}
|
||||
|
||||
export function getModelByPluralName(pluralName, ignoreCase = false) {
|
||||
function formatName(formattedName) {
|
||||
if (ignoreCase == true) {
|
||||
formattedName = formattedName.toUpperCase().replace(' ', '')
|
||||
}
|
||||
return formattedName
|
||||
}
|
||||
return (
|
||||
objectModels.find(
|
||||
(meta) => formatName(meta.labelPlural) === formatName(pluralName)
|
||||
) || {
|
||||
name: 'unknown',
|
||||
label: 'Unknown',
|
||||
prefix: 'UNK',
|
||||
icon: QuestionCircleIcon,
|
||||
url: () => '#',
|
||||
properties: {}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function getModelProperty(name, property) {
|
||||
const model = getModelByName(name)
|
||||
|
||||
|
||||
@ -24,10 +24,7 @@ const SIDEBARS = {
|
||||
key: 'sales',
|
||||
label: 'Sales',
|
||||
iconKey: 'sales',
|
||||
items: salesSidebarItems,
|
||||
routeAliases: {
|
||||
'/dashboard/sales/listingvarients': 'listings'
|
||||
}
|
||||
items: salesSidebarItems
|
||||
},
|
||||
finance: {
|
||||
key: 'finance',
|
||||
|
||||
@ -30,6 +30,15 @@ export const File = {
|
||||
label: 'List',
|
||||
icon: ListIcon
|
||||
},
|
||||
{
|
||||
name: 'new',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
label: 'New File',
|
||||
visible: () => {
|
||||
return false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'info',
|
||||
type: 'page',
|
||||
@ -117,14 +126,7 @@ export const File = {
|
||||
'updatedAt',
|
||||
'_reference'
|
||||
],
|
||||
sorters: [
|
||||
'name',
|
||||
'type',
|
||||
'size',
|
||||
'createdAt',
|
||||
'temp',
|
||||
'updatedAt'
|
||||
],
|
||||
sorters: ['name', 'type', 'size', 'createdAt', 'temp', 'updatedAt'],
|
||||
group: ['type'],
|
||||
properties: [
|
||||
{
|
||||
|
||||
231
src/database/models/FulfillmentPolicy.js
Normal file
231
src/database/models/FulfillmentPolicy.js
Normal file
@ -0,0 +1,231 @@
|
||||
import { createElement, lazy } from 'react'
|
||||
|
||||
const FulfillmentPolicyInfo = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Sales/FulfillmentPolicies/FulfillmentPolicyInfo')
|
||||
)
|
||||
const NewFulfillmentPolicy = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Sales/FulfillmentPolicies/NewFulfillmentPolicy')
|
||||
)
|
||||
const DeleteObject = lazy(
|
||||
() => import('../../components/Dashboard/common/DeleteObject')
|
||||
)
|
||||
import FulfillmentPolicyIcon from '../../components/Icons/FulfillmentPolicyIcon'
|
||||
import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import BinIcon from '../../components/Icons/BinIcon'
|
||||
import ListIcon from '../../components/Icons/ListIcon'
|
||||
import { marketplaceSyncMappingsProperty } from './marketplaceMappings'
|
||||
|
||||
export const FulfillmentPolicy = {
|
||||
name: 'fulfillmentPolicy',
|
||||
label: 'Fulfillment Policy',
|
||||
labelPlural: 'Fulfillment Policies',
|
||||
url: '/dashboard/sales/fulfillmentpolicies',
|
||||
prefix: 'FPL',
|
||||
endpoint: 'fulfillmentpolicies',
|
||||
icon: FulfillmentPolicyIcon,
|
||||
actions: [
|
||||
{
|
||||
name: 'new',
|
||||
type: 'modal',
|
||||
pageName: 'list',
|
||||
modalWidth: 700,
|
||||
label: 'New Fulfillment Policy',
|
||||
icon: PlusIcon,
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(NewFulfillmentPolicy, {
|
||||
defaultValues: objectData,
|
||||
onOk,
|
||||
reset: true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'list',
|
||||
type: 'page',
|
||||
pageName: 'list',
|
||||
label: 'List',
|
||||
icon: ListIcon
|
||||
},
|
||||
{
|
||||
name: 'info',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
label: 'Info',
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
icon: XMarkIcon,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
icon: CheckIcon,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'delete',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
modalCentered: true,
|
||||
label: 'Delete',
|
||||
icon: BinIcon,
|
||||
danger: true,
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(DeleteObject, { objectData, onOk })
|
||||
}
|
||||
}
|
||||
],
|
||||
pages: [
|
||||
{
|
||||
name: 'info',
|
||||
content: () => createElement(FulfillmentPolicyInfo)
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
'_reference',
|
||||
'name',
|
||||
'handlingTime',
|
||||
'courierServices',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
filters: [
|
||||
'name',
|
||||
'handlingTime',
|
||||
'courierServices',
|
||||
'localPickup',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'_reference'
|
||||
],
|
||||
sorters: ['name', 'handlingTime', 'createdAt', '_id', 'updatedAt'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
label: 'ID',
|
||||
columnFixed: 'left',
|
||||
type: 'id',
|
||||
objectType: 'fulfillmentPolicy',
|
||||
showCopy: true,
|
||||
columnWidth: 140
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: '_reference',
|
||||
label: 'Reference',
|
||||
type: 'reference',
|
||||
columnFixed: 'left',
|
||||
objectType: 'fulfillmentPolicy',
|
||||
showCopy: true,
|
||||
readOnly: true,
|
||||
columnWidth: 180
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
columnFixed: 'left',
|
||||
required: true,
|
||||
type: 'text',
|
||||
columnWidth: 220
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
required: false,
|
||||
span: 2,
|
||||
columnWidth: 280
|
||||
},
|
||||
{
|
||||
name: 'handlingTime',
|
||||
label: 'Handling Time',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
required: false,
|
||||
suffix: 'days',
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'courierServices',
|
||||
label: 'Courier Services',
|
||||
type: 'objectList',
|
||||
objectType: 'courierService',
|
||||
showHyperlink: true,
|
||||
required: false,
|
||||
columnWidth: 250
|
||||
},
|
||||
{
|
||||
name: 'localPickup',
|
||||
label: 'Local Pickup',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
columnWidth: 140
|
||||
},
|
||||
{
|
||||
name: 'globalShipping',
|
||||
label: 'Global Shipping',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
columnWidth: 150
|
||||
},
|
||||
{
|
||||
name: 'freightShipping',
|
||||
label: 'Freight Shipping',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
columnWidth: 160
|
||||
},
|
||||
{
|
||||
name: 'pickupDropOff',
|
||||
label: 'Pickup Drop Off',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
columnWidth: 160
|
||||
},
|
||||
marketplaceSyncMappingsProperty('fulfillmentPolicy')
|
||||
]
|
||||
}
|
||||
@ -112,7 +112,10 @@ export const Listing = {
|
||||
visible: (objectData) =>
|
||||
objectData?.state?.type === 'draft' ||
|
||||
objectData?.state?.type === 'inactive',
|
||||
disabled: (objectData) => objectData?.state?.type === 'syncing',
|
||||
disabled: (objectData) =>
|
||||
['syncing', 'publishing', 'unpublishing'].includes(
|
||||
objectData?.state?.type
|
||||
),
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(PublishListing, { objectData, onOk })
|
||||
}
|
||||
@ -125,7 +128,10 @@ export const Listing = {
|
||||
icon: XMarkIcon,
|
||||
danger: true,
|
||||
visible: (objectData) => objectData?.state?.type === 'active',
|
||||
disabled: (objectData) => objectData?.state?.type === 'syncing',
|
||||
disabled: (objectData) =>
|
||||
['syncing', 'publishing', 'unpublishing'].includes(
|
||||
objectData?.state?.type
|
||||
),
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(UnpublishListing, { objectData, onOk })
|
||||
}
|
||||
@ -158,6 +164,9 @@ export const Listing = {
|
||||
'stockLocation',
|
||||
'stockQuantity',
|
||||
'marketplace',
|
||||
'fulfillmentPolicy',
|
||||
'paymentPolicy',
|
||||
'returnPolicy',
|
||||
'courierServices',
|
||||
'state',
|
||||
'condition',
|
||||
@ -173,6 +182,9 @@ export const Listing = {
|
||||
'stockLocation',
|
||||
'stockQuantity',
|
||||
'marketplace',
|
||||
'fulfillmentPolicy',
|
||||
'paymentPolicy',
|
||||
'returnPolicy',
|
||||
'courierServices',
|
||||
'state',
|
||||
'state.type',
|
||||
@ -252,6 +264,14 @@ export const Listing = {
|
||||
extra:
|
||||
'Used as the eBay listing description. If empty, the title is sent instead.'
|
||||
},
|
||||
{
|
||||
name: 'listingImages',
|
||||
label: 'Listing Images',
|
||||
type: 'fileList',
|
||||
gallery: true,
|
||||
required: false,
|
||||
masterFilter: ['.jpg', '.jpeg', '.png', '.gif', '.webp']
|
||||
},
|
||||
{
|
||||
name: 'lastSyncedAt',
|
||||
label: 'Last Synced',
|
||||
@ -314,6 +334,58 @@ export const Listing = {
|
||||
required: true,
|
||||
columnWidth: 200
|
||||
},
|
||||
{
|
||||
name: 'fulfillmentPolicy',
|
||||
label: 'Fulfillment Policy',
|
||||
type: 'object',
|
||||
objectType: 'fulfillmentPolicy',
|
||||
showHyperlink: true,
|
||||
readOnly: false,
|
||||
required: false,
|
||||
extra:
|
||||
'Defaults from the marketplace. Courier services are used as a fallback when this is empty.',
|
||||
value: (objectData) => {
|
||||
if (objectData?.fulfillmentPolicy == undefined) {
|
||||
return objectData?.marketplace?.defaultFulfillmentPolicy
|
||||
}
|
||||
return objectData?.fulfillmentPolicy
|
||||
},
|
||||
columnWidth: 220
|
||||
},
|
||||
{
|
||||
name: 'paymentPolicy',
|
||||
label: 'Payment Policy',
|
||||
type: 'object',
|
||||
objectType: 'paymentPolicy',
|
||||
showHyperlink: true,
|
||||
readOnly: false,
|
||||
required: false,
|
||||
extra: 'Defaults from the marketplace. Required when publishing to eBay.',
|
||||
value: (objectData) => {
|
||||
if (objectData?.paymentPolicy == undefined) {
|
||||
return objectData?.marketplace?.defaultPaymentPolicy
|
||||
}
|
||||
return objectData?.paymentPolicy
|
||||
},
|
||||
columnWidth: 200
|
||||
},
|
||||
{
|
||||
name: 'returnPolicy',
|
||||
label: 'Return Policy',
|
||||
type: 'object',
|
||||
objectType: 'returnPolicy',
|
||||
showHyperlink: true,
|
||||
readOnly: false,
|
||||
required: false,
|
||||
extra: 'Defaults from the marketplace. Required when publishing to eBay.',
|
||||
value: (objectData) => {
|
||||
if (objectData?.returnPolicy == undefined) {
|
||||
return objectData?.marketplace?.defaultReturnPolicy
|
||||
}
|
||||
return objectData?.returnPolicy
|
||||
},
|
||||
columnWidth: 200
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
@ -378,6 +450,17 @@ export const Listing = {
|
||||
required: false,
|
||||
columnWidth: 250
|
||||
},
|
||||
{
|
||||
name: 'externalReference',
|
||||
label: 'External Reference',
|
||||
type: 'miscId',
|
||||
readOnly: true,
|
||||
required: false,
|
||||
extra:
|
||||
'eBay item ID for this listing. Publish and unpublish reuse this ID when set.',
|
||||
showCopy: true,
|
||||
columnWidth: 180
|
||||
},
|
||||
{
|
||||
name: 'courierServices',
|
||||
label: 'Courier Services',
|
||||
@ -386,6 +469,8 @@ export const Listing = {
|
||||
objectType: 'courierService',
|
||||
readOnly: false,
|
||||
required: true,
|
||||
extra:
|
||||
'Used for order shipping and as the eBay fulfillment fallback when no fulfillment policy is set.',
|
||||
columnWidth: 250
|
||||
}
|
||||
],
|
||||
@ -414,6 +499,18 @@ export const Listing = {
|
||||
type: 'number',
|
||||
color: 'processing'
|
||||
},
|
||||
{
|
||||
name: 'publishing.count',
|
||||
label: 'Publishing',
|
||||
type: 'number',
|
||||
color: 'processing'
|
||||
},
|
||||
{
|
||||
name: 'unpublishing.count',
|
||||
label: 'Unpublishing',
|
||||
type: 'number',
|
||||
color: 'processing'
|
||||
},
|
||||
{
|
||||
name: 'suspended.count',
|
||||
label: 'Suspended',
|
||||
|
||||
@ -108,7 +108,10 @@ export const ListingVarient = {
|
||||
visible: (objectData) =>
|
||||
objectData?.state?.type === 'draft' ||
|
||||
objectData?.state?.type === 'inactive',
|
||||
disabled: (objectData) => objectData?.state?.type === 'syncing',
|
||||
disabled: (objectData) =>
|
||||
['syncing', 'publishing', 'unpublishing'].includes(
|
||||
objectData?.state?.type
|
||||
),
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(PublishListingVarient, { objectData, onOk })
|
||||
}
|
||||
@ -121,7 +124,10 @@ export const ListingVarient = {
|
||||
icon: XMarkIcon,
|
||||
danger: true,
|
||||
visible: (objectData) => objectData?.state?.type === 'active',
|
||||
disabled: (objectData) => objectData?.state?.type === 'syncing',
|
||||
disabled: (objectData) =>
|
||||
['syncing', 'publishing', 'unpublishing'].includes(
|
||||
objectData?.state?.type
|
||||
),
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(UnpublishListingVarient, { objectData, onOk })
|
||||
}
|
||||
@ -312,6 +318,42 @@ export const ListingVarient = {
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'listingImages',
|
||||
label: 'Listing Images',
|
||||
type: 'fileList',
|
||||
gallery: true,
|
||||
required: false,
|
||||
masterFilter: ['.jpg', '.jpeg', '.png', '.gif', '.webp']
|
||||
},
|
||||
{
|
||||
name: 'aspects',
|
||||
label: 'Aspects',
|
||||
type: 'objectChildren',
|
||||
required: false,
|
||||
canAddRemove: true,
|
||||
size: 'medium',
|
||||
span: 2,
|
||||
extra:
|
||||
'Required for multi-variation eBay listings. Use the same aspect names on every varient in a listing (e.g. Color, Size).',
|
||||
columns: ['name', 'value'],
|
||||
properties: [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
columnWidth: 180
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
label: 'Value',
|
||||
type: 'text',
|
||||
required: true,
|
||||
columnWidth: 180
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -15,6 +15,21 @@ const SyncListings = lazy(
|
||||
const SyncOrders = lazy(
|
||||
() => import('../../components/Dashboard/Sales/Marketplaces/SyncOrders')
|
||||
)
|
||||
const SyncFulfillmentPolicies = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Sales/Marketplaces/SyncFulfillmentPolicies')
|
||||
)
|
||||
const SyncPaymentPolicies = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Sales/Marketplaces/SyncPaymentPolicies')
|
||||
)
|
||||
const SyncReturnPolicies = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Sales/Marketplaces/SyncReturnPolicies')
|
||||
)
|
||||
const SyncTaxRates = lazy(
|
||||
() => import('../../components/Dashboard/Sales/Marketplaces/SyncTaxRates')
|
||||
)
|
||||
const DeleteObject = lazy(
|
||||
() => import('../../components/Dashboard/common/DeleteObject')
|
||||
)
|
||||
@ -134,19 +149,6 @@ export const Marketplace = {
|
||||
return createElement(SyncListings, { objectData, onOk })
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'syncMarketplace',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
label: 'Sync Marketplace',
|
||||
icon: ReloadIcon,
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type != 'ready'
|
||||
},
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(SyncMarketplace, { objectData, onOk })
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'syncOrders',
|
||||
type: 'modal',
|
||||
@ -159,6 +161,58 @@ export const Marketplace = {
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(SyncOrders, { objectData, onOk })
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'syncFulfillmentPolicies',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
label: 'Sync Fulfillment Policies',
|
||||
icon: ReloadIcon,
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type != 'ready'
|
||||
},
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(SyncFulfillmentPolicies, { objectData, onOk })
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'syncReturnPolicies',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
label: 'Sync Return Policies',
|
||||
icon: ReloadIcon,
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type != 'ready'
|
||||
},
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(SyncReturnPolicies, { objectData, onOk })
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'syncPaymentPolicies',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
label: 'Sync Payment Policies',
|
||||
icon: ReloadIcon,
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type != 'ready'
|
||||
},
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(SyncPaymentPolicies, { objectData, onOk })
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'syncTaxRates',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
label: 'Sync Tax Rates',
|
||||
icon: ReloadIcon,
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type != 'ready'
|
||||
},
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(SyncTaxRates, { objectData, onOk })
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -313,17 +367,7 @@ export const Marketplace = {
|
||||
],
|
||||
columnWidth: 150
|
||||
},
|
||||
{
|
||||
name: 'eBay.availableShippingServices',
|
||||
label: 'Available Shipping Services',
|
||||
type: 'stringList',
|
||||
readOnly: true,
|
||||
required: false,
|
||||
visible: (objectData) => objectData?.provider === 'ebay',
|
||||
extra: 'Loaded from eBay GeteBayDetails when this marketplace is synced.',
|
||||
span: 2,
|
||||
columnWidth: 280
|
||||
},
|
||||
|
||||
{
|
||||
name: 'config.accessTokenExpiresAt',
|
||||
label: 'Access Token Expires At',
|
||||
@ -331,7 +375,6 @@ export const Marketplace = {
|
||||
readOnly: true,
|
||||
columnWidth: 215
|
||||
},
|
||||
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
@ -340,21 +383,7 @@ export const Marketplace = {
|
||||
readOnly: true,
|
||||
columnWidth: 260
|
||||
},
|
||||
{
|
||||
name: 'active',
|
||||
label: 'Active',
|
||||
type: 'bool',
|
||||
readOnly: false,
|
||||
required: true,
|
||||
columnWidth: 125
|
||||
},
|
||||
{
|
||||
name: 'connected',
|
||||
label: 'Connected',
|
||||
type: 'bool',
|
||||
readOnly: true,
|
||||
columnWidth: 125
|
||||
},
|
||||
|
||||
{
|
||||
name: 'config.refreshTokenExpiresAt',
|
||||
label: 'Refresh Token Expires At',
|
||||
@ -364,15 +393,40 @@ export const Marketplace = {
|
||||
columnWidth: 200
|
||||
},
|
||||
{
|
||||
name: 'config.clientId',
|
||||
label: 'Client ID',
|
||||
type: 'secret',
|
||||
readOnly: false,
|
||||
name: 'eBay.availableShippingServices',
|
||||
label: 'Available Shipping Services',
|
||||
type: 'number',
|
||||
readOnly: true,
|
||||
required: false,
|
||||
columnWidth: 200,
|
||||
visible: (objectData) => objectData?.provider === 'ebay'
|
||||
visible: (objectData) => objectData?.provider === 'ebay',
|
||||
extra: 'Loaded from eBay GeteBayDetails when this marketplace is synced.',
|
||||
span: 2,
|
||||
columnWidth: 280,
|
||||
value: (objectData) => {
|
||||
return objectData?.eBay?.availableShippingServices?.length
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'eBay.categoryReferences',
|
||||
label: 'Category References',
|
||||
type: 'treeSelect',
|
||||
readOnly: true,
|
||||
required: false,
|
||||
visible: false,
|
||||
extra:
|
||||
'Loaded from the eBay taxonomy category tree when this marketplace is synced.',
|
||||
span: 2,
|
||||
columnWidth: 280
|
||||
},
|
||||
|
||||
{
|
||||
name: 'active',
|
||||
label: 'Active',
|
||||
type: 'bool',
|
||||
readOnly: false,
|
||||
required: true,
|
||||
columnWidth: 125
|
||||
},
|
||||
{
|
||||
name: 'config.lastTokenRefreshAt',
|
||||
label: 'Last Token Refresh At',
|
||||
@ -381,6 +435,23 @@ export const Marketplace = {
|
||||
showSince: true,
|
||||
columnWidth: 200
|
||||
},
|
||||
{
|
||||
name: 'connected',
|
||||
label: 'Connected',
|
||||
type: 'bool',
|
||||
readOnly: true,
|
||||
columnWidth: 125
|
||||
},
|
||||
|
||||
{
|
||||
name: 'config.clientId',
|
||||
label: 'Client ID',
|
||||
type: 'secret',
|
||||
readOnly: false,
|
||||
required: false,
|
||||
columnWidth: 200,
|
||||
visible: (objectData) => objectData?.provider === 'ebay'
|
||||
},
|
||||
|
||||
{
|
||||
name: 'config.clientSecret',
|
||||
@ -555,6 +626,37 @@ export const Marketplace = {
|
||||
}
|
||||
return `${window.location.origin}/auth/marketplace/callback`
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'defaultFulfillmentPolicy',
|
||||
label: 'Default Fulfillment Policy',
|
||||
type: 'object',
|
||||
objectType: 'fulfillmentPolicy',
|
||||
showHyperlink: true,
|
||||
required: false,
|
||||
extra:
|
||||
'Used on new listings and when a listing has no fulfillment policy.',
|
||||
columnWidth: 240
|
||||
},
|
||||
{
|
||||
name: 'defaultPaymentPolicy',
|
||||
label: 'Default Payment Policy',
|
||||
type: 'object',
|
||||
objectType: 'paymentPolicy',
|
||||
showHyperlink: true,
|
||||
required: false,
|
||||
extra: 'Used on new listings and when a listing has no payment policy.',
|
||||
columnWidth: 220
|
||||
},
|
||||
{
|
||||
name: 'defaultReturnPolicy',
|
||||
label: 'Default Return Policy',
|
||||
type: 'object',
|
||||
objectType: 'returnPolicy',
|
||||
showHyperlink: true,
|
||||
required: false,
|
||||
extra: 'Used on new listings and when a listing has no return policy.',
|
||||
columnWidth: 220
|
||||
}
|
||||
],
|
||||
stats: [
|
||||
|
||||
@ -6,9 +6,20 @@ const PartStockInfo = lazy(
|
||||
const NewPartStock = lazy(
|
||||
() => import('../../components/Dashboard/Inventory/PartStocks/NewPartStock')
|
||||
)
|
||||
const PostPartStock = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Inventory/PartStocks/PostPartStock.jsx')
|
||||
)
|
||||
const DeleteObject = lazy(
|
||||
() => import('../../components/Dashboard/common/DeleteObject')
|
||||
)
|
||||
import PartStockIcon from '../../components/Icons/PartStockIcon'
|
||||
import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import BinIcon from '../../components/Icons/BinIcon'
|
||||
import ListIcon from '../../components/Icons/ListIcon'
|
||||
|
||||
export const PartStock = {
|
||||
@ -17,7 +28,6 @@ export const PartStock = {
|
||||
labelPlural: 'Part Stocks',
|
||||
url: '/dashboard/inventory/partstocks',
|
||||
prefix: 'PTS',
|
||||
readOnly: true,
|
||||
icon: PartStockIcon,
|
||||
actions: [
|
||||
{
|
||||
@ -50,6 +60,75 @@ export const PartStock = {
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
},
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type != 'draft'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
icon: XMarkIcon,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
icon: CheckIcon,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
modalCentered: true,
|
||||
label: 'Delete',
|
||||
icon: BinIcon,
|
||||
danger: true,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
},
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type != 'draft'
|
||||
},
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(DeleteObject, { objectData, onOk })
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'post',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
label: 'Post',
|
||||
icon: CheckIcon,
|
||||
disabled: (objectData) => {
|
||||
return objectData?._isEditing == true
|
||||
},
|
||||
visible: (objectData) => {
|
||||
return objectData?.state?.type == 'draft'
|
||||
},
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(PostPartStock, { objectData, onOk })
|
||||
}
|
||||
}
|
||||
],
|
||||
pages: [
|
||||
@ -62,7 +141,6 @@ export const PartStock = {
|
||||
'part',
|
||||
'partSku',
|
||||
'state',
|
||||
'startingQuantity',
|
||||
'currentQuantity',
|
||||
'stockLocation',
|
||||
'createdAt',
|
||||
@ -72,7 +150,6 @@ export const PartStock = {
|
||||
sorters: [
|
||||
'part',
|
||||
'partSku',
|
||||
'startingQuantity',
|
||||
'currentQuantity',
|
||||
'state',
|
||||
'createdAt',
|
||||
@ -81,7 +158,6 @@ export const PartStock = {
|
||||
columns: [
|
||||
'_reference',
|
||||
'state',
|
||||
'startingQuantity',
|
||||
'currentQuantity',
|
||||
'part',
|
||||
'partSku',
|
||||
@ -116,13 +192,6 @@ export const PartStock = {
|
||||
readOnly: true,
|
||||
columnWidth: 180
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
type: 'state',
|
||||
readOnly: true,
|
||||
columnWidth: 260
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
@ -131,13 +200,19 @@ export const PartStock = {
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'sourceType',
|
||||
label: 'Source Type',
|
||||
type: 'objectType',
|
||||
readOnly: false,
|
||||
columnWidth: 200,
|
||||
required: true,
|
||||
masterFilter: ['subJob', 'stockTransfer']
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
type: 'state',
|
||||
readOnly: true,
|
||||
columnWidth: 260
|
||||
},
|
||||
|
||||
{
|
||||
name: 'postedAt',
|
||||
label: 'Posted At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'part',
|
||||
@ -169,36 +244,22 @@ export const PartStock = {
|
||||
showHyperlink: true,
|
||||
columnWidth: 200
|
||||
},
|
||||
|
||||
{
|
||||
name: 'source',
|
||||
label: 'Source',
|
||||
type: 'object',
|
||||
readOnly: false,
|
||||
required: true,
|
||||
columnWidth: 200,
|
||||
objectType: (objectData) => {
|
||||
return objectData?.sourceType
|
||||
},
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'currentQuantity',
|
||||
label: 'Current Quantity',
|
||||
type: 'number',
|
||||
readOnly: true,
|
||||
columnWidth: 200,
|
||||
required: true,
|
||||
value: (objectData) => {
|
||||
if (objectData?.state?.type === 'new') {
|
||||
return objectData?.startingQuantity
|
||||
} else {
|
||||
return objectData.currentQuantity
|
||||
}
|
||||
}
|
||||
required: true
|
||||
}
|
||||
],
|
||||
stats: [
|
||||
{
|
||||
name: 'draft.count',
|
||||
label: 'Draft',
|
||||
type: 'number',
|
||||
color: 'default',
|
||||
history: false
|
||||
},
|
||||
{
|
||||
name: 'new.count',
|
||||
label: 'New',
|
||||
|
||||
187
src/database/models/PaymentPolicy.js
Normal file
187
src/database/models/PaymentPolicy.js
Normal file
@ -0,0 +1,187 @@
|
||||
import { createElement, lazy } from 'react'
|
||||
|
||||
const PaymentPolicyInfo = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Finance/PaymentPolicies/PaymentPolicyInfo')
|
||||
)
|
||||
const NewPaymentPolicy = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Finance/PaymentPolicies/NewPaymentPolicy')
|
||||
)
|
||||
const DeleteObject = lazy(
|
||||
() => import('../../components/Dashboard/common/DeleteObject')
|
||||
)
|
||||
import PaymentPolicyIcon from '../../components/Icons/PaymentPolicyIcon'
|
||||
import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import BinIcon from '../../components/Icons/BinIcon'
|
||||
import ListIcon from '../../components/Icons/ListIcon'
|
||||
import { marketplaceSyncMappingsProperty } from './marketplaceMappings'
|
||||
|
||||
export const PaymentPolicy = {
|
||||
name: 'paymentPolicy',
|
||||
label: 'Payment Policy',
|
||||
labelPlural: 'Payment Policies',
|
||||
url: '/dashboard/finance/paymentpolicies',
|
||||
prefix: 'PPL',
|
||||
endpoint: 'paymentpolicies',
|
||||
icon: PaymentPolicyIcon,
|
||||
actions: [
|
||||
{
|
||||
name: 'new',
|
||||
type: 'modal',
|
||||
pageName: 'list',
|
||||
modalWidth: 700,
|
||||
label: 'New Payment Policy',
|
||||
icon: PlusIcon,
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(NewPaymentPolicy, {
|
||||
defaultValues: objectData,
|
||||
onOk,
|
||||
reset: true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'list',
|
||||
type: 'page',
|
||||
pageName: 'list',
|
||||
label: 'List',
|
||||
icon: ListIcon
|
||||
},
|
||||
{
|
||||
name: 'info',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
label: 'Info',
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
icon: XMarkIcon,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
icon: CheckIcon,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'delete',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
modalCentered: true,
|
||||
label: 'Delete',
|
||||
icon: BinIcon,
|
||||
danger: true,
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(DeleteObject, { objectData, onOk })
|
||||
}
|
||||
}
|
||||
],
|
||||
pages: [
|
||||
{
|
||||
name: 'info',
|
||||
content: () => createElement(PaymentPolicyInfo)
|
||||
}
|
||||
],
|
||||
columns: ['_reference', 'name', 'immediatePay', 'createdAt', 'updatedAt'],
|
||||
filters: ['name', 'immediatePay', 'createdAt', 'updatedAt', '_reference'],
|
||||
sorters: ['name', 'immediatePay', 'createdAt', '_id', 'updatedAt'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
label: 'ID',
|
||||
columnFixed: 'left',
|
||||
type: 'id',
|
||||
objectType: 'paymentPolicy',
|
||||
showCopy: true,
|
||||
columnWidth: 140
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: '_reference',
|
||||
label: 'Reference',
|
||||
type: 'reference',
|
||||
columnFixed: 'left',
|
||||
objectType: 'paymentPolicy',
|
||||
showCopy: true,
|
||||
readOnly: true,
|
||||
columnWidth: 180
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
columnFixed: 'left',
|
||||
required: true,
|
||||
type: 'text',
|
||||
columnWidth: 220
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
required: false,
|
||||
span: 2,
|
||||
columnWidth: 280
|
||||
},
|
||||
{
|
||||
name: 'immediatePay',
|
||||
label: 'Immediate Pay',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
default: true,
|
||||
extra: 'Required for eBay managed payments.',
|
||||
columnWidth: 150
|
||||
},
|
||||
{
|
||||
name: 'paymentInstructions',
|
||||
label: 'Payment Instructions',
|
||||
type: 'text',
|
||||
required: false,
|
||||
span: 2,
|
||||
columnWidth: 280
|
||||
},
|
||||
marketplaceSyncMappingsProperty('paymentPolicy')
|
||||
]
|
||||
}
|
||||
@ -1,14 +1,17 @@
|
||||
import { createElement, lazy } from 'react'
|
||||
|
||||
const ProductCategoryInfo = lazy(
|
||||
() => import('../../components/Dashboard/Management/ProductCategories/ProductCategoryInfo')
|
||||
() =>
|
||||
import('../../components/Dashboard/Management/ProductCategories/ProductCategoryInfo')
|
||||
)
|
||||
const NewProductCategory = lazy(
|
||||
() => import('../../components/Dashboard/Management/ProductCategories/NewProductCategory')
|
||||
() =>
|
||||
import('../../components/Dashboard/Management/ProductCategories/NewProductCategory')
|
||||
)
|
||||
const DeleteObject = lazy(
|
||||
() => import('../../components/Dashboard/common/DeleteObject')
|
||||
)
|
||||
import { findTreeNode } from '../../components/Dashboard/common/CustomTreeSelect'
|
||||
import ProductCategoryIcon from '../../components/Icons/ProductCategoryIcon'
|
||||
import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
@ -35,7 +38,11 @@ export const ProductCategory = {
|
||||
label: 'New Product Category',
|
||||
icon: PlusIcon,
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(NewProductCategory, { defaultValues: objectData, onOk, reset: true })
|
||||
return createElement(NewProductCategory, {
|
||||
defaultValues: objectData,
|
||||
onOk,
|
||||
reset: true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -106,7 +113,14 @@ export const ProductCategory = {
|
||||
}
|
||||
],
|
||||
columns: ['_reference', 'name', 'createdAt', 'updatedAt'],
|
||||
filters: ['_id','name','createdAt','updatedAt','_reference'],
|
||||
filters: [
|
||||
'_id',
|
||||
'name',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'_reference',
|
||||
'marketplaces.marketplace'
|
||||
],
|
||||
sorters: ['name', 'createdAt', 'updatedAt', '_id'],
|
||||
properties: [
|
||||
{
|
||||
@ -136,6 +150,14 @@ export const ProductCategory = {
|
||||
readOnly: true,
|
||||
columnWidth: 180
|
||||
},
|
||||
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
@ -145,11 +167,47 @@ export const ProductCategory = {
|
||||
columnWidth: 200
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
name: 'marketplaces',
|
||||
label: 'Marketplaces',
|
||||
type: 'objectChildren',
|
||||
required: false,
|
||||
canAddRemove: true,
|
||||
size: 'medium',
|
||||
span: 2,
|
||||
columns: ['marketplace', 'externalReference'],
|
||||
properties: [
|
||||
{
|
||||
name: 'marketplace',
|
||||
label: 'Marketplace',
|
||||
type: 'object',
|
||||
objectType: 'marketplace',
|
||||
required: true,
|
||||
showHyperlink: true,
|
||||
columnWidth: 220
|
||||
},
|
||||
{
|
||||
name: 'externalReference',
|
||||
label: 'External Reference',
|
||||
type: (objectData) =>
|
||||
objectData?.marketplace?.provider === 'ebay'
|
||||
? 'treeSelect'
|
||||
: 'text',
|
||||
options: (objectData) => {
|
||||
const tree = objectData?.marketplace?.eBay?.categoryReferences || []
|
||||
const current = objectData?.externalReference
|
||||
if (!current || findTreeNode(tree, current)) return tree
|
||||
return [
|
||||
{ title: current, value: current, selectable: true },
|
||||
...tree
|
||||
]
|
||||
},
|
||||
extra:
|
||||
'For eBay listings, choose a category from the selected marketplace. Sync the marketplace if the tree is empty.',
|
||||
showCopy: true,
|
||||
required: true,
|
||||
columnWidth: 260
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -371,10 +371,11 @@ export const ProductStock = {
|
||||
required: false,
|
||||
showHyperlink: true,
|
||||
columnWidth: 260,
|
||||
masterFilter: (objectData) => {
|
||||
masterFilter: (objectData, parentData) => {
|
||||
return {
|
||||
part: objectData?.part?._id,
|
||||
partSku: objectData?.partSku?._id,
|
||||
stockLocation: parentData?.stockLocation?._id,
|
||||
$or: [{ 'state.type': 'new' }, { 'state.type': 'used' }]
|
||||
}
|
||||
}
|
||||
|
||||
294
src/database/models/ReturnPolicy.js
Normal file
294
src/database/models/ReturnPolicy.js
Normal file
@ -0,0 +1,294 @@
|
||||
import { createElement, lazy } from 'react'
|
||||
|
||||
const ReturnPolicyInfo = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Sales/ReturnPolicies/ReturnPolicyInfo')
|
||||
)
|
||||
const NewReturnPolicy = lazy(
|
||||
() =>
|
||||
import('../../components/Dashboard/Sales/ReturnPolicies/NewReturnPolicy')
|
||||
)
|
||||
const DeleteObject = lazy(
|
||||
() => import('../../components/Dashboard/common/DeleteObject')
|
||||
)
|
||||
import ReturnPolicyIcon from '../../components/Icons/ReturnPolicyIcon'
|
||||
import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import BinIcon from '../../components/Icons/BinIcon'
|
||||
import ListIcon from '../../components/Icons/ListIcon'
|
||||
import { marketplaceSyncMappingsProperty } from './marketplaceMappings'
|
||||
|
||||
export const ReturnPolicy = {
|
||||
name: 'returnPolicy',
|
||||
label: 'Return Policy',
|
||||
labelPlural: 'Return Policies',
|
||||
url: '/dashboard/sales/returnpolicies',
|
||||
prefix: 'RPL',
|
||||
endpoint: 'returnpolicies',
|
||||
icon: ReturnPolicyIcon,
|
||||
actions: [
|
||||
{
|
||||
name: 'new',
|
||||
type: 'modal',
|
||||
pageName: 'list',
|
||||
modalWidth: 700,
|
||||
label: 'New Return Policy',
|
||||
icon: PlusIcon,
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(NewReturnPolicy, {
|
||||
defaultValues: objectData,
|
||||
onOk,
|
||||
reset: true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'list',
|
||||
type: 'page',
|
||||
pageName: 'list',
|
||||
label: 'List',
|
||||
icon: ListIcon
|
||||
},
|
||||
{
|
||||
name: 'info',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
label: 'Info',
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
icon: XMarkIcon,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
icon: CheckIcon,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'delete',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
modalCentered: true,
|
||||
label: 'Delete',
|
||||
icon: BinIcon,
|
||||
danger: true,
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(DeleteObject, { objectData, onOk })
|
||||
}
|
||||
}
|
||||
],
|
||||
pages: [
|
||||
{
|
||||
name: 'info',
|
||||
content: () => createElement(ReturnPolicyInfo)
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
'_reference',
|
||||
'name',
|
||||
'returnsAccepted',
|
||||
'returnPeriodDays',
|
||||
'returnShippingCostPayer',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
filters: [
|
||||
'name',
|
||||
'returnsAccepted',
|
||||
'returnPeriodDays',
|
||||
'returnShippingCostPayer',
|
||||
'refundMethod',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'_reference'
|
||||
],
|
||||
sorters: [
|
||||
'name',
|
||||
'returnsAccepted',
|
||||
'returnPeriodDays',
|
||||
'createdAt',
|
||||
'_id',
|
||||
'updatedAt'
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
label: 'ID',
|
||||
columnFixed: 'left',
|
||||
type: 'id',
|
||||
objectType: 'returnPolicy',
|
||||
showCopy: true,
|
||||
columnWidth: 140
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: '_reference',
|
||||
label: 'Reference',
|
||||
type: 'reference',
|
||||
columnFixed: 'left',
|
||||
objectType: 'returnPolicy',
|
||||
showCopy: true,
|
||||
readOnly: true,
|
||||
columnWidth: 180
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
columnFixed: 'left',
|
||||
required: true,
|
||||
type: 'text',
|
||||
columnWidth: 220
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
required: false,
|
||||
span: 2,
|
||||
columnWidth: 280
|
||||
},
|
||||
{
|
||||
name: 'returnsAccepted',
|
||||
label: 'Returns Accepted',
|
||||
type: 'bool',
|
||||
required: true,
|
||||
default: true,
|
||||
columnWidth: 200
|
||||
},
|
||||
{
|
||||
name: 'returnPeriodDays',
|
||||
label: 'Return Period',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '14 days', value: 14 },
|
||||
{ label: '30 days', value: 30 },
|
||||
{ label: '60 days', value: 60 }
|
||||
],
|
||||
default: 30,
|
||||
required: false,
|
||||
extra: 'eBay only accepts 14, 30, or 60 days.',
|
||||
visible: (objectData) => objectData?.returnsAccepted !== false,
|
||||
columnWidth: 170
|
||||
},
|
||||
{
|
||||
name: 'returnShippingCostPayer',
|
||||
label: 'Return Shipping Paid By',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Buyer', value: 'buyer' },
|
||||
{ label: 'Seller', value: 'seller' }
|
||||
],
|
||||
required: false,
|
||||
visible: (objectData) => objectData?.returnsAccepted !== false,
|
||||
columnWidth: 220
|
||||
},
|
||||
{
|
||||
name: 'refundMethod',
|
||||
label: 'Refund Method',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Money Back', value: 'moneyBack' },
|
||||
{ label: 'Merchandise Credit', value: 'merchandiseCredit' }
|
||||
],
|
||||
required: false,
|
||||
visible: (objectData) => objectData?.returnsAccepted !== false,
|
||||
columnWidth: 180
|
||||
},
|
||||
{
|
||||
name: 'restockingFeePercentage',
|
||||
label: 'Restocking Fee',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
suffix: '%',
|
||||
required: false,
|
||||
extra: 'Stored locally. eBay no longer accepts restocking fees.',
|
||||
columnWidth: 160
|
||||
},
|
||||
{
|
||||
name: 'returnInstructions',
|
||||
label: 'Return Instructions',
|
||||
type: 'text',
|
||||
required: false,
|
||||
span: 2,
|
||||
columnWidth: 280
|
||||
},
|
||||
{
|
||||
name: 'internationalReturnsAccepted',
|
||||
label: 'International Returns',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
columnWidth: 180
|
||||
},
|
||||
{
|
||||
name: 'internationalReturnPeriodDays',
|
||||
label: 'International Return Period',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '14 days', value: 14 },
|
||||
{ label: '30 days', value: 30 },
|
||||
{ label: '60 days', value: 60 }
|
||||
],
|
||||
required: false,
|
||||
extra: 'eBay only accepts 14, 30, or 60 days.',
|
||||
visible: (objectData) =>
|
||||
objectData?.internationalReturnsAccepted === true,
|
||||
columnWidth: 220
|
||||
},
|
||||
{
|
||||
name: 'internationalReturnShippingCostPayer',
|
||||
label: 'International Return Shipping Paid By',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Buyer', value: 'buyer' },
|
||||
{ label: 'Seller', value: 'seller' }
|
||||
],
|
||||
required: false,
|
||||
visible: (objectData) =>
|
||||
objectData?.internationalReturnsAccepted === true,
|
||||
columnWidth: 260
|
||||
},
|
||||
marketplaceSyncMappingsProperty('returnPolicy')
|
||||
]
|
||||
}
|
||||
@ -17,6 +17,7 @@ import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import BinIcon from '../../components/Icons/BinIcon'
|
||||
import ListIcon from '../../components/Icons/ListIcon'
|
||||
import { marketplaceSyncMappingsProperty } from './marketplaceMappings'
|
||||
|
||||
export const TaxRate = {
|
||||
name: 'taxRate',
|
||||
@ -111,6 +112,7 @@ export const TaxRate = {
|
||||
'rateType',
|
||||
'active',
|
||||
'country',
|
||||
'jurisdiction',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
@ -120,6 +122,7 @@ export const TaxRate = {
|
||||
'rateType',
|
||||
'active',
|
||||
'country',
|
||||
'jurisdiction',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'_reference'
|
||||
@ -130,6 +133,7 @@ export const TaxRate = {
|
||||
'rateType',
|
||||
'active',
|
||||
'country',
|
||||
'jurisdiction',
|
||||
'createdAt',
|
||||
'_id',
|
||||
'updatedAt'
|
||||
@ -237,6 +241,23 @@ export const TaxRate = {
|
||||
required: false,
|
||||
columnWidth: 150
|
||||
},
|
||||
{
|
||||
name: 'jurisdiction',
|
||||
label: 'Jurisdiction',
|
||||
type: 'text',
|
||||
readOnly: false,
|
||||
required: false,
|
||||
extra:
|
||||
'Marketplace tax table region, for example GU or ON. eBay only uses US territories and Canada.',
|
||||
columnWidth: 140
|
||||
},
|
||||
{
|
||||
name: 'shippingAndHandlingTaxed',
|
||||
label: 'Tax Shipping',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
columnWidth: 140
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
@ -244,6 +265,10 @@ export const TaxRate = {
|
||||
readOnly: false,
|
||||
required: false,
|
||||
columnWidth: 200
|
||||
}
|
||||
},
|
||||
marketplaceSyncMappingsProperty(
|
||||
'taxRate',
|
||||
'eBay tax table key as COUNTRY:JURISDICTION, for example US:GU. Filled when synced.'
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
49
src/database/models/marketplaceMappings.js
Normal file
49
src/database/models/marketplaceMappings.js
Normal file
@ -0,0 +1,49 @@
|
||||
export function marketplaceSyncMappingsProperty(objectType, extra) {
|
||||
return {
|
||||
name: 'marketplaces',
|
||||
label: 'Marketplaces',
|
||||
type: 'objectChildren',
|
||||
required: false,
|
||||
canAddRemove: true,
|
||||
size: 'medium',
|
||||
span: 2,
|
||||
columns: ['marketplace', 'state', 'externalReference'],
|
||||
properties: [
|
||||
{
|
||||
name: 'marketplace',
|
||||
label: 'Marketplace',
|
||||
type: 'object',
|
||||
objectType: 'marketplace',
|
||||
required: true,
|
||||
showHyperlink: true,
|
||||
columnWidth: 220
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
type: 'state',
|
||||
objectType,
|
||||
readOnly: true,
|
||||
columnWidth: 160
|
||||
},
|
||||
{
|
||||
name: 'externalReference',
|
||||
label: 'External Reference',
|
||||
type: (objectData) => {
|
||||
return objectData?.marketplace?.provider === 'ebay'
|
||||
? 'miscId'
|
||||
: 'text'
|
||||
},
|
||||
readOnly: (objectData) => {
|
||||
return objectData?.marketplace?.provider === 'ebay'
|
||||
},
|
||||
extra:
|
||||
extra ||
|
||||
'Marketplace policy ID. Filled when the policy is synced; you can paste an existing ID.',
|
||||
showCopy: true,
|
||||
required: false,
|
||||
columnWidth: 240
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,12 @@ const financeSidebarItems = [
|
||||
iconKey: 'payment',
|
||||
path: '/dashboard/finance/payments'
|
||||
},
|
||||
{
|
||||
key: 'paymentpolicies',
|
||||
label: 'Payment Policies',
|
||||
iconKey: 'paymentPolicy',
|
||||
path: '/dashboard/finance/paymentpolicies'
|
||||
},
|
||||
{
|
||||
key: 'taxRecords',
|
||||
iconKey: 'taxRecord',
|
||||
|
||||
@ -18,6 +18,7 @@ const salesSidebarItems = [
|
||||
iconKey: 'salesOrder',
|
||||
path: '/dashboard/sales/salesorders'
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'marketplaces',
|
||||
label: 'Marketplaces',
|
||||
@ -29,6 +30,24 @@ const salesSidebarItems = [
|
||||
label: 'Listings',
|
||||
iconKey: 'listing',
|
||||
path: '/dashboard/sales/listings'
|
||||
},
|
||||
{
|
||||
key: 'listingvarients',
|
||||
label: 'Listing Varients',
|
||||
iconKey: 'listingVarient',
|
||||
path: '/dashboard/sales/listingvarients'
|
||||
},
|
||||
{
|
||||
key: 'fulfillmentpolicies',
|
||||
label: 'Fulfillment Policies',
|
||||
iconKey: 'fulfillmentPolicy',
|
||||
path: '/dashboard/sales/fulfillmentpolicies'
|
||||
},
|
||||
{
|
||||
key: 'returnpolicies',
|
||||
label: 'Return Policies',
|
||||
iconKey: 'returnPolicy',
|
||||
path: '/dashboard/sales/returnpolicies'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -23,6 +23,9 @@ const FinanceOverview = lazy(
|
||||
const TaxRecords = lazy(
|
||||
() => import('../components/Dashboard/Finance/TaxRecords.jsx')
|
||||
)
|
||||
const PaymentPolicies = lazy(
|
||||
() => import('../components/Dashboard/Finance/PaymentPolicies.jsx')
|
||||
)
|
||||
|
||||
const FinanceRoutes = [
|
||||
<Route
|
||||
@ -33,6 +36,11 @@ const FinanceRoutes = [
|
||||
<Route key='invoices' path='finance/invoices' element={<Invoices />} />,
|
||||
<Route key='payments' path='finance/payments' element={<Payments />} />,
|
||||
<Route key='taxrecords' path='finance/taxrecords' element={<TaxRecords />} />,
|
||||
<Route
|
||||
key='paymentpolicies'
|
||||
path='finance/paymentpolicies'
|
||||
element={<PaymentPolicies />}
|
||||
/>,
|
||||
<Route
|
||||
key='taxrecords-redirect'
|
||||
path='management/taxrecords'
|
||||
|
||||
@ -14,6 +14,15 @@ const Marketplaces = lazy(
|
||||
const Listings = lazy(
|
||||
() => import('../components/Dashboard/Sales/Listings.jsx')
|
||||
)
|
||||
const ListingVarients = lazy(
|
||||
() => import('../components/Dashboard/Sales/ListingVarients.jsx')
|
||||
)
|
||||
const FulfillmentPolicies = lazy(
|
||||
() => import('../components/Dashboard/Sales/FulfillmentPolicies.jsx')
|
||||
)
|
||||
const ReturnPolicies = lazy(
|
||||
() => import('../components/Dashboard/Sales/ReturnPolicies.jsx')
|
||||
)
|
||||
|
||||
const SalesRoutes = [
|
||||
<Route key='overview' path='sales/overview' element={<SalesOverview />} />,
|
||||
@ -28,7 +37,22 @@ const SalesRoutes = [
|
||||
path='sales/marketplaces'
|
||||
element={<Marketplaces />}
|
||||
/>,
|
||||
<Route key='listings' path='sales/listings' element={<Listings />} />
|
||||
<Route key='listings' path='sales/listings' element={<Listings />} />,
|
||||
<Route
|
||||
key='listingvarients'
|
||||
path='sales/listingvarients'
|
||||
element={<ListingVarients />}
|
||||
/>,
|
||||
<Route
|
||||
key='fulfillmentpolicies'
|
||||
path='sales/fulfillmentpolicies'
|
||||
element={<FulfillmentPolicies />}
|
||||
/>,
|
||||
<Route
|
||||
key='returnpolicies'
|
||||
path='sales/returnpolicies'
|
||||
element={<ReturnPolicies />}
|
||||
/>
|
||||
]
|
||||
|
||||
export default SalesRoutes
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user