Added purchase orders and couriers.
This commit is contained in:
parent
735826bdb9
commit
17a4a5abd5
7
assets/icons/couriericon.svg
Normal file
7
assets/icons/couriericon.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?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,9.456082)">
|
||||||
|
<path d="M10.547,64.047L16.531,64.047L16.531,56.031L11.203,56.031C9.078,56.031 8.031,55.062 8.031,52.859L8.031,12.328C8.031,10.141 9.078,9.141 11.203,9.141L57.172,9.141C59.281,9.141 60.359,10.141 60.359,12.328L60.359,57.781L68.391,54.375L68.391,11.688C68.391,4.938 64.609,1.141 57.859,1.141L10.547,1.141C3.781,1.141 0,4.938 0,11.688L0,53.516C0,60.266 3.781,64.047 10.547,64.047ZM64.156,26.453L75.859,26.453C77.484,26.453 78.703,26.859 79.844,28.141L89.312,38.766C90.219,39.797 90.672,41.062 90.672,42.578L90.672,52.859C90.672,55.062 89.609,56.031 87.484,56.031L83.109,56.031L83.109,64.047L88.156,64.047C94.906,64.047 98.703,60.266 98.703,53.516L98.703,41.906C98.703,38.266 98.109,36.547 96.078,34.266L85.031,21.781C82.859,19.344 80.406,18.422 76.766,18.422L64.156,18.422L64.156,26.453ZM74.469,42.234L86.938,42.234C86.938,41.516 86.672,41.141 86.219,40.641L77.719,31.062C76.938,30.188 76.172,29.766 75.031,29.766L71.391,29.766L71.391,39.156C71.391,41.031 72.594,42.234 74.469,42.234ZM25.453,73.031C32.25,73.031 37.766,67.469 37.766,60.656C37.766,53.859 32.25,48.344 25.453,48.344C18.641,48.344 13.078,53.859 13.078,60.656C13.078,67.469 18.641,73.031 25.453,73.031ZM25.453,66.422C22.281,66.422 19.688,63.828 19.688,60.656C19.688,57.5 22.281,54.906 25.453,54.906C28.609,54.906 31.156,57.5 31.156,60.656C31.156,63.828 28.609,66.422 25.453,66.422ZM73,73.031C79.812,73.031 85.328,67.469 85.328,60.656C85.328,53.859 79.812,48.344 73,48.344C66.188,48.344 60.641,53.859 60.641,60.656C60.641,67.469 66.188,73.031 73,73.031ZM73,66.422C69.844,66.422 67.25,63.828 67.25,60.656C67.25,57.5 69.844,54.906 73,54.906C76.156,54.906 78.719,57.5 78.719,60.656C78.719,63.828 76.188,66.422 73,66.422ZM34.656,64.047L63.672,64.047L63.672,56.031L34.656,56.031L34.656,64.047Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
14
assets/icons/courierserviceicon.svg
Normal file
14
assets/icons/courierserviceicon.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="M44.708,64.047L37.295,64.047C35.825,69.222 31.078,73.031 25.453,73.031C19.815,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.422C80.406,18.422 82.859,19.344 85.031,21.781L96.078,34.266C98.109,36.547 98.703,38.266 98.703,41.906L98.703,47.207C96.335,44.629 93.539,42.449 90.427,40.78C90.205,40.029 89.834,39.359 89.312,38.766L79.844,28.141C78.703,26.859 77.484,26.453 75.859,26.453L68.391,26.453L68.391,37.936C65.543,38.615 62.846,39.684 60.359,41.084L60.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.063 9.078,56.031 11.203,56.031L13.984,56.031C15.827,51.524 20.28,48.344 25.453,48.344C30.614,48.344 35.037,51.524 36.866,56.031L46.931,56.031C45.851,58.55 45.093,61.239 44.708,64.047ZM71.391,37.375L71.391,29.766L75.031,29.766C76.172,29.766 76.938,30.188 77.719,31.062L84.08,38.231C81.402,37.485 78.578,37.086 75.66,37.086C74.212,37.086 72.787,37.185 71.391,37.375ZM25.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.46865,0,0,0.46865,32,32.014622)">
|
||||||
|
<path d="M34.125,68.25C53,68.25 68.281,52.969 68.281,34.125C68.281,15.281 53,0 34.125,0C15.281,0 0,15.281 0,34.125C0,52.969 15.281,68.25 34.125,68.25ZM34.125,57.125C21.406,57.125 11.125,46.844 11.125,34.125C11.125,21.406 21.406,11.125 34.125,11.125C46.844,11.125 57.156,21.406 57.156,34.125C57.156,46.844 46.844,57.125 34.125,57.125Z" style="fill-rule:nonzero;"/>
|
||||||
|
<path d="M20.812,40.312L34.125,40.312C36.312,40.312 38,38.594 38,36.438L38,18.656C38,16.469 36.312,14.781 34.125,14.781C31.969,14.781 30.25,16.469 30.25,18.656L30.25,32.562L20.812,32.562C18.656,32.562 16.938,34.281 16.938,36.438C16.938,38.594 18.656,40.312 20.812,40.312Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.46865,0,0,0.46865,32,32.014622)">
|
||||||
|
<path d="M34.125,68.25C53,68.25 68.281,52.969 68.281,34.125C68.281,15.281 53,0 34.125,0C15.281,0 0,15.281 0,34.125C0,52.969 15.281,68.25 34.125,68.25ZM34.125,57.125C21.406,57.125 11.125,46.844 11.125,34.125C11.125,21.406 21.406,11.125 34.125,11.125C46.844,11.125 57.156,21.406 57.156,34.125C57.156,46.844 46.844,57.125 34.125,57.125Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
7
assets/icons/orderitemsicon.svg
Normal file
7
assets/icons/orderitemsicon.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?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.822372,0,0,0.822372,2,2.325593)">
|
||||||
|
<path d="M25.615,51.541L61.925,51.541C63.571,51.541 65.041,50.302 65.041,48.462C65.041,46.631 63.571,45.412 61.925,45.412L26.434,45.412C25.11,45.412 24.295,44.46 24.093,43.051L19.177,9.045C18.785,6.142 17.522,4.675 13.848,4.675L3.326,4.675C1.539,4.675 0,6.215 0,8.032C0,9.86 1.539,11.411 3.326,11.411L12.627,11.411L17.348,43.696C18.064,48.571 20.686,51.541 25.615,51.541ZM20.362,39.849L62.285,39.849C67.216,39.849 69.857,36.878 70.563,31.952L72.815,16.861C72.877,16.425 72.96,15.886 72.96,15.485C72.96,13.488 71.586,12.07 69.142,12.07L17.504,12.07L17.525,18.229L65.71,18.229L63.868,31.389C63.678,32.83 62.925,33.711 61.558,33.711L20.319,33.711L20.362,39.849ZM28.223,67.493C31.383,67.493 33.901,64.986 33.901,61.805C33.901,58.664 31.383,56.126 28.223,56.126C25.061,56.126 22.513,58.664 22.513,61.805C22.513,64.986 25.061,67.493 28.223,67.493ZM56.875,67.493C60.047,67.493 62.575,64.986 62.575,61.805C62.575,58.664 60.047,56.126 56.875,56.126C53.735,56.126 51.175,58.664 51.175,61.805C51.175,64.986 53.735,67.493 56.875,67.493Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -1,11 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?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">
|
<!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;">
|
<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;">
|
||||||
<path d="M25,54.028C25,54.887 24.545,55.681 23.804,56.116C23.064,56.55 22.148,56.56 21.398,56.141C16.479,53.393 8.262,48.803 8.262,48.803C5.729,47.378 4.441,45.879 4.441,42.127L4.441,20.65C4.441,17.814 5.489,16.02 7.877,14.692L26.541,4.215C29.828,2.367 33.176,2.367 36.463,4.215L55.149,14.692C57.515,16.02 58.563,17.814 58.563,20.65L58.563,22.637C58.563,23.942 57.505,25 56.2,25L56.198,25C55.571,25 54.97,24.751 54.527,24.308C54.084,23.865 53.835,23.264 53.835,22.637L53.835,22.284C53.835,22.284 51.807,23.429 50.385,24.232C49.494,24.736 48.488,25 47.465,25L40.475,25C39.454,25 38.45,24.737 37.56,24.235C33.115,21.731 19.274,13.934 19.274,13.934L11.856,18.15C11.856,18.15 21.408,23.505 25.75,25.94C26.238,26.214 26.59,26.68 26.72,27.224C26.849,27.769 26.746,28.343 26.434,28.808C26.264,28.997 26.133,29.214 26.013,29.437C25.763,29.906 25.335,30.254 24.826,30.402C24.316,30.551 23.768,30.487 23.306,30.226C18.993,27.833 9.169,22.284 9.169,22.284L9.169,41.664C9.169,43.059 9.681,43.919 11.137,44.722C11.137,44.722 20.377,49.94 23.77,51.856C24.53,52.285 25,53.091 25,53.964L25,54.028ZM43.972,22.202L24.257,11.165L28.656,8.675C30.587,7.587 32.407,7.556 34.365,8.675L51.17,18.15L43.972,22.202Z"/>
|
|
||||||
<g transform="matrix(0.58577,0,0,0.58577,29,29)">
|
<g transform="matrix(0.58577,0,0,0.58577,29,29)">
|
||||||
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
|
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
|
||||||
<g transform="matrix(0.664312,0,0,0.664312,5.121464,12.135807)">
|
<g transform="matrix(0.664312,0,0,0.664312,5.121464,12.135807)">
|
||||||
<path d="M37.594,52.969C39.062,52.969 40.625,52.281 41.875,51L61.844,31.094C62.938,30 63.656,28.188 63.656,26.5C63.656,24.812 62.938,23 61.844,21.906L41.875,1.969C40.625,0.688 39.062,0 37.594,0C33.812,0 31.406,2.562 31.406,5.906C31.406,7.875 32.312,9.25 33.5,10.406L40.5,17.344L50.219,26.5L40.5,35.656L33.5,42.562C32.312,43.688 31.406,45.094 31.406,47.062C31.406,50.406 33.812,52.969 37.594,52.969ZM1.485,32.781L37.75,32.781L51.969,32.094C55.531,31.938 57.906,29.844 57.906,26.5C57.906,23.156 55.531,21.062 51.969,20.906L37.75,20.219L1.485,20.219C-2.515,20.219 -5.14,22.719 -5.14,26.5C-5.14,30.281 -2.515,32.781 1.485,32.781Z" style="fill-rule:nonzero;"/>
|
<path d="M37.594,52.969C39.062,52.969 40.625,52.281 41.875,51L61.844,31.094C62.938,30 63.656,28.188 63.656,26.5C63.656,24.812 62.938,23 61.844,21.906L41.875,1.969C40.625,0.688 39.062,0 37.594,0C33.812,0 31.406,2.562 31.406,5.906C31.406,7.875 32.312,9.25 33.5,10.406L40.5,17.344L50.219,26.5L40.5,35.656L33.5,42.562C32.312,43.688 31.406,45.094 31.406,47.062C31.406,50.406 33.812,52.969 37.594,52.969ZM1.485,32.781L37.75,32.781L51.969,32.094C55.531,31.938 57.906,29.844 57.906,26.5C57.906,23.156 55.531,21.062 51.969,20.906L37.75,20.219L1.485,20.219C-2.515,20.219 -5.14,22.719 -5.14,26.5C-5.14,30.281 -2.515,32.781 1.485,32.781Z" style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
<g transform="matrix(0.739137,0,0,0.739137,-0,2.611674)">
|
||||||
|
<path d="M35.853,52.047L25.734,52.047C20.578,52.047 17.797,48.969 17.047,43.844L12.484,12.719L3.906,12.719C1.812,12.719 0,10.906 0,8.766C0,6.625 1.812,4.828 3.906,4.828L14.172,4.828C18.125,4.828 19.609,6.437 20.047,9.625L20.402,12.109L69.062,12.109C71.922,12.109 73.438,13.75 73.438,16.031C73.438,16.5 73.344,17.062 73.281,17.516L71.172,31.781C71.146,31.963 71.118,32.143 71.087,32.319L63.084,32.319C63.272,32.028 63.394,31.657 63.453,31.219L65.078,19.094L21.4,19.094L23.397,33.062L43.157,33.063C41.542,33.617 40.191,34.464 39.103,35.547C37.914,36.73 37.007,38.224 36.457,40.031L24.393,40.031L24.844,43.188C25,44.312 25.656,45.094 26.719,45.094L35.853,45.094L35.853,52.047ZM28.422,68.437C25.078,68.437 22.359,65.766 22.359,62.391C22.359,59.063 25.078,56.359 28.422,56.359C31.781,56.359 34.453,59.063 34.453,62.391C34.453,65.766 31.781,68.437 28.422,68.437Z"/>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.6 KiB |
13
assets/icons/salesordericon.svg
Normal file
13
assets/icons/salesordericon.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?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.58577,0,0,0.58577,29,29)">
|
||||||
|
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
|
||||||
|
<g transform="matrix(4.017824,0,0,4.017824,-142.588346,-145.029826)">
|
||||||
|
<path d="M46.23,46.705C46.23,46.213 45.832,45.814 45.34,45.814L42.92,45.814L42.92,45.721C43.312,45.539 43.553,44.93 43.582,44.326L44.326,44.326C44.859,44.326 45.205,44.039 45.205,43.57C45.205,43.102 44.859,42.826 44.326,42.826L43.213,42.826C43.154,42.627 43.107,42.404 43.107,42.135C43.107,41.508 43.465,41.174 44.109,41.174C44.566,41.174 44.812,41.32 45.105,41.32C45.574,41.32 45.926,40.951 45.926,40.482C45.926,40.084 45.656,39.803 45.34,39.674C45.1,39.574 44.566,39.469 43.863,39.469C42.029,39.469 40.764,40.26 40.764,41.83C40.764,42.176 40.816,42.51 40.893,42.826C40.371,42.826 40.025,43.102 40.025,43.576C40.025,44.045 40.371,44.326 40.904,44.326L41.297,44.326C41.314,44.414 41.32,44.502 41.32,44.578C41.32,45.141 40.922,45.727 40.271,45.908C39.896,46.014 39.621,46.33 39.621,46.74C39.621,47.221 39.996,47.596 40.477,47.596L45.34,47.596C45.832,47.596 46.23,47.197 46.23,46.705Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.729338,0,0,0.729338,0.719606,4.8492)">
|
||||||
|
<path d="M35.348,52.047L25.734,52.047C20.578,52.047 17.797,48.969 17.047,43.844L12.484,12.719L3.906,12.719C1.812,12.719 0,10.906 0,8.766C0,6.625 1.812,4.828 3.906,4.828L14.172,4.828C18.125,4.828 19.609,6.438 20.047,9.625L20.402,12.109L69.062,12.109C71.922,12.109 73.438,13.75 73.438,16.031C73.438,16.5 73.344,17.062 73.281,17.516L71.482,29.686L63.659,29.686L65.078,19.094L21.4,19.094L23.397,33.062L38.537,33.063C36.873,34.767 35.775,37.089 35.449,40.031L24.393,40.031L24.844,43.188C25,44.312 25.656,45.094 26.719,45.094L35.348,45.094L35.348,52.047ZM28.422,68.438C25.078,68.438 22.359,65.766 22.359,62.391C22.359,59.062 25.078,56.359 28.422,56.359C31.781,56.359 34.453,59.062 34.453,62.391C34.453,65.766 31.781,68.438 28.422,68.438Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
@ -34,9 +34,15 @@ const routeKeyMap = {
|
|||||||
const DeveloperSidebar = (props) => {
|
const DeveloperSidebar = (props) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const selectedKey = (() => {
|
const selectedKey = (() => {
|
||||||
const match = Object.keys(routeKeyMap).find((path) =>
|
const match = Object.keys(routeKeyMap).find((path) => {
|
||||||
location.pathname.startsWith(path)
|
const pathSplit = path.split('/')
|
||||||
)
|
const locationPathSplit = location.pathname.split('/')
|
||||||
|
if (pathSplit.length > locationPathSplit.length) return false
|
||||||
|
for (let i = 0; i < pathSplit.length; i++) {
|
||||||
|
if (pathSplit[i] !== locationPathSplit[i]) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
return match ? routeKeyMap[match] : 'sessionstorage'
|
return match ? routeKeyMap[match] : 'sessionstorage'
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import PartStockIcon from '../../Icons/PartStockIcon'
|
|||||||
import ProductStockIcon from '../../Icons/ProductStockIcon'
|
import ProductStockIcon from '../../Icons/ProductStockIcon'
|
||||||
import StockEventIcon from '../../Icons/StockEventIcon'
|
import StockEventIcon from '../../Icons/StockEventIcon'
|
||||||
import StockAuditIcon from '../../Icons/StockAuditIcon'
|
import StockAuditIcon from '../../Icons/StockAuditIcon'
|
||||||
|
import PurchaseOrderIcon from '../../Icons/PurchaseOrderIcon'
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
@ -34,6 +35,13 @@ const items = [
|
|||||||
path: '/dashboard/inventory/productstocks'
|
path: '/dashboard/inventory/productstocks'
|
||||||
},
|
},
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: 'purchaseorders',
|
||||||
|
label: 'Purchase Orders',
|
||||||
|
icon: <PurchaseOrderIcon />,
|
||||||
|
path: '/dashboard/inventory/purchaseorders'
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
{
|
{
|
||||||
key: 'stockevents',
|
key: 'stockevents',
|
||||||
label: 'Stock Events',
|
label: 'Stock Events',
|
||||||
@ -54,16 +62,23 @@ const routeKeyMap = {
|
|||||||
'/dashboard/inventory/partstocks': 'partstocks',
|
'/dashboard/inventory/partstocks': 'partstocks',
|
||||||
'/dashboard/inventory/productstocks': 'productstocks',
|
'/dashboard/inventory/productstocks': 'productstocks',
|
||||||
'/dashboard/inventory/stockevents': 'stockevents',
|
'/dashboard/inventory/stockevents': 'stockevents',
|
||||||
'/dashboard/inventory/stockaudits': 'stockaudits'
|
'/dashboard/inventory/stockaudits': 'stockaudits',
|
||||||
|
'/dashboard/inventory/purchaseorders': 'purchaseorders'
|
||||||
}
|
}
|
||||||
|
|
||||||
const InventorySidebar = (props) => {
|
const InventorySidebar = (props) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const selectedKey = (() => {
|
const selectedKey = (() => {
|
||||||
const match = Object.keys(routeKeyMap).find((path) =>
|
const match = Object.keys(routeKeyMap).find((path) => {
|
||||||
location.pathname.startsWith(path)
|
const pathSplit = path.split('/')
|
||||||
)
|
const locationPathSplit = location.pathname.split('/')
|
||||||
return match ? routeKeyMap[match] : 'filaments'
|
if (pathSplit.length > locationPathSplit.length) return false
|
||||||
|
for (let i = 0; i < pathSplit.length; i++) {
|
||||||
|
if (pathSplit[i] !== locationPathSplit[i]) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return match ? routeKeyMap[match] : 'overview'
|
||||||
})()
|
})()
|
||||||
|
|
||||||
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
|
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
|
||||||
|
|||||||
101
src/components/Dashboard/Inventory/PurchaseOrders.jsx
Normal file
101
src/components/Dashboard/Inventory/PurchaseOrders.jsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { useState, useRef } from 'react'
|
||||||
|
import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
|
||||||
|
import NewPurchaseOrder from './PurchaseOrders/NewPurchaseOrder'
|
||||||
|
import ObjectTable from '../common/ObjectTable'
|
||||||
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
|
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||||
|
import GridIcon from '../../Icons/GridIcon'
|
||||||
|
import ListIcon from '../../Icons/ListIcon'
|
||||||
|
import useViewMode from '../hooks/useViewMode'
|
||||||
|
import ColumnViewButton from '../common/ColumnViewButton'
|
||||||
|
|
||||||
|
const PurchaseOrders = () => {
|
||||||
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
|
const [newPurchaseOrderOpen, setNewPurchaseOrderOpen] = useState(false)
|
||||||
|
const tableRef = useRef()
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = useViewMode('purchaseOrders')
|
||||||
|
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
useColumnVisibility('purchaseOrders')
|
||||||
|
|
||||||
|
const actionItems = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New Purchase Order',
|
||||||
|
key: 'newPurchaseOrder',
|
||||||
|
icon: <PlusIcon />
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: 'Reload List',
|
||||||
|
key: 'reloadList',
|
||||||
|
icon: <ReloadIcon />
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onClick: ({ key }) => {
|
||||||
|
if (key === 'reloadList') {
|
||||||
|
tableRef.current?.reload()
|
||||||
|
} else if (key === 'newPurchaseOrder') {
|
||||||
|
setNewPurchaseOrderOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex vertical={'true'} gap='large'>
|
||||||
|
{contextHolder}
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='small'>
|
||||||
|
<Dropdown menu={actionItems}>
|
||||||
|
<Button>Actions</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<ColumnViewButton
|
||||||
|
type='purchaseOrder'
|
||||||
|
loading={false}
|
||||||
|
visibleState={columnVisibility}
|
||||||
|
updateVisibleState={setColumnVisibility}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||||
|
onClick={() =>
|
||||||
|
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
<ObjectTable
|
||||||
|
ref={tableRef}
|
||||||
|
visibleColumns={columnVisibility}
|
||||||
|
type='purchaseOrder'
|
||||||
|
cards={viewMode === 'cards'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={newPurchaseOrderOpen}
|
||||||
|
styles={{ content: { paddingBottom: '24px' } }}
|
||||||
|
footer={null}
|
||||||
|
width={800}
|
||||||
|
onCancel={() => {
|
||||||
|
setNewPurchaseOrderOpen(false)
|
||||||
|
}}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
>
|
||||||
|
<NewPurchaseOrder
|
||||||
|
onOk={() => {
|
||||||
|
setNewPurchaseOrderOpen(false)
|
||||||
|
messageApi.success('New purchase order created successfully.')
|
||||||
|
tableRef.current?.reload()
|
||||||
|
}}
|
||||||
|
reset={newPurchaseOrderOpen}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PurchaseOrders
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
|
import WizardView from '../../common/WizardView'
|
||||||
|
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||||
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
|
|
||||||
|
const NewPurchaseOrder = ({ onOk, reset }) => {
|
||||||
|
return (
|
||||||
|
<NewObjectForm
|
||||||
|
type={'purchaseOrder'}
|
||||||
|
reset={reset}
|
||||||
|
defaultValues={{ state: { type: 'new' } }}
|
||||||
|
>
|
||||||
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Required',
|
||||||
|
key: 'required',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='purchaseOrder'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={true}
|
||||||
|
objectData={objectData}
|
||||||
|
visibleProperties={{
|
||||||
|
items: false,
|
||||||
|
cost: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Items',
|
||||||
|
key: 'items',
|
||||||
|
content: (
|
||||||
|
<ObjectProperty
|
||||||
|
{...getModelProperty('purchaseOrder', 'items')}
|
||||||
|
isEditing={true}
|
||||||
|
objectData={objectData}
|
||||||
|
loading={submitLoading}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Summary',
|
||||||
|
key: 'summary',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='purchaseOrder'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
visibleProperties={{
|
||||||
|
_id: false,
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: false,
|
||||||
|
items: false
|
||||||
|
}}
|
||||||
|
isEditing={false}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<WizardView
|
||||||
|
steps={steps}
|
||||||
|
loading={submitLoading}
|
||||||
|
formValid={formValid}
|
||||||
|
title='New Purchase Order'
|
||||||
|
onSubmit={() => {
|
||||||
|
handleSubmit()
|
||||||
|
onOk()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NewObjectForm>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NewPurchaseOrder.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
reset: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewPurchaseOrder
|
||||||
@ -0,0 +1,231 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import { Space, Flex, Card } from 'antd'
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
|
import loglevel from 'loglevel'
|
||||||
|
import config from '../../../../config.js'
|
||||||
|
import useCollapseState from '../../hooks/useCollapseState.js'
|
||||||
|
import NotesPanel from '../../common/NotesPanel.jsx'
|
||||||
|
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
||||||
|
import ViewButton from '../../common/ViewButton.jsx'
|
||||||
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
|
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||||
|
import EditButtons from '../../common/EditButtons.jsx'
|
||||||
|
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||||
|
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||||
|
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||||
|
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||||
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||||
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||||
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
|
import OrderItemsIcon from '../../../Icons/OrderItemsIcon.jsx'
|
||||||
|
|
||||||
|
const log = loglevel.getLogger('PurchaseOrderInfo')
|
||||||
|
log.setLevel(config.logLevel)
|
||||||
|
|
||||||
|
const PurchaseOrderInfo = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
const objectFormRef = useRef(null)
|
||||||
|
const actionHandlerRef = useRef(null)
|
||||||
|
const purchaseOrderId = new URLSearchParams(location.search).get(
|
||||||
|
'purchaseOrderId'
|
||||||
|
)
|
||||||
|
const [collapseState, updateCollapseState] = useCollapseState(
|
||||||
|
'PurchaseOrderInfo',
|
||||||
|
{
|
||||||
|
info: true,
|
||||||
|
notes: true,
|
||||||
|
auditLogs: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const [objectFormState, setEditFormState] = useState({
|
||||||
|
isEditing: false,
|
||||||
|
editLoading: false,
|
||||||
|
formValid: false,
|
||||||
|
lock: null,
|
||||||
|
loading: false,
|
||||||
|
objectData: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
reload: () => {
|
||||||
|
objectFormRef?.current?.handleFetchObject?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
edit: () => {
|
||||||
|
objectFormRef?.current?.startEditing?.()
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
cancelEdit: () => {
|
||||||
|
objectFormRef?.current?.cancelEditing?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
finishEdit: () => {
|
||||||
|
objectFormRef?.current?.handleUpdate?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
delete: () => {
|
||||||
|
objectFormRef?.current?.handleDelete?.()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
gap='large'
|
||||||
|
vertical='true'
|
||||||
|
style={{
|
||||||
|
maxHeight: '100%',
|
||||||
|
minHeight: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='middle'>
|
||||||
|
<Space size='small'>
|
||||||
|
<ObjectActions
|
||||||
|
type='purchaseOrder'
|
||||||
|
id={purchaseOrderId}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
<ViewButton
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
items={[
|
||||||
|
{ key: 'info', label: 'Purchase Order Information' },
|
||||||
|
{ key: 'notes', label: 'Notes' },
|
||||||
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
|
]}
|
||||||
|
visibleState={collapseState}
|
||||||
|
updateVisibleState={updateCollapseState}
|
||||||
|
/>
|
||||||
|
<DocumentPrintButton
|
||||||
|
type='purchaseOrder'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<LockIndicator lock={objectFormState.lock} />
|
||||||
|
</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.lock?.locked || objectFormState.loading}
|
||||||
|
loading={objectFormState.editLoading}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<ScrollBox>
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<ActionHandler
|
||||||
|
actions={actions}
|
||||||
|
loading={objectFormState.loading}
|
||||||
|
ref={actionHandlerRef}
|
||||||
|
>
|
||||||
|
<ObjectForm
|
||||||
|
id={purchaseOrderId}
|
||||||
|
type='purchaseOrder'
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
ref={objectFormRef}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ loading, isEditing, objectData }) => (
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Purchase Order Information'
|
||||||
|
icon={<InfoCircleIcon />}
|
||||||
|
active={collapseState.info}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('info', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='info'
|
||||||
|
>
|
||||||
|
<ObjectInfo
|
||||||
|
loading={loading}
|
||||||
|
indicator={<LoadingOutlined />}
|
||||||
|
isEditing={isEditing}
|
||||||
|
type='purchaseOrder'
|
||||||
|
objectData={objectData}
|
||||||
|
visibleProperties={{
|
||||||
|
items: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Purchase Order Items'
|
||||||
|
icon={<OrderItemsIcon />}
|
||||||
|
active={collapseState.info}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('info', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='info'
|
||||||
|
>
|
||||||
|
<ObjectProperty
|
||||||
|
{...getModelProperty('purchaseOrder', 'items')}
|
||||||
|
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'
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<NotesPanel _id={purchaseOrderId} type='purchaseOrder' />
|
||||||
|
</Card>
|
||||||
|
</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._id': purchaseOrderId }}
|
||||||
|
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
</ScrollBox>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PurchaseOrderInfo
|
||||||
98
src/components/Dashboard/Management/CourierServices.jsx
Normal file
98
src/components/Dashboard/Management/CourierServices.jsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { useState, useRef } from 'react'
|
||||||
|
import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
|
||||||
|
import NewCourierService from './CourierServices/NewCourierService'
|
||||||
|
import ObjectTable from '../common/ObjectTable'
|
||||||
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
|
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||||
|
import GridIcon from '../../Icons/GridIcon'
|
||||||
|
import ListIcon from '../../Icons/ListIcon'
|
||||||
|
import useViewMode from '../hooks/useViewMode'
|
||||||
|
import ColumnViewButton from '../common/ColumnViewButton'
|
||||||
|
|
||||||
|
const CourierServices = () => {
|
||||||
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
|
const [newCourierServiceOpen, setNewCourierServiceOpen] = useState(false)
|
||||||
|
const tableRef = useRef()
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = useViewMode('courierService')
|
||||||
|
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
useColumnVisibility('courierService')
|
||||||
|
|
||||||
|
const actionItems = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New Courier Service',
|
||||||
|
key: 'newCourierService',
|
||||||
|
icon: <PlusIcon />
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: 'Reload List',
|
||||||
|
key: 'reloadList',
|
||||||
|
icon: <ReloadIcon />
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onClick: ({ key }) => {
|
||||||
|
if (key === 'reloadList') {
|
||||||
|
tableRef.current?.reload()
|
||||||
|
} else if (key === 'newCourierService') {
|
||||||
|
setNewCourierServiceOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex vertical={'true'} gap='large'>
|
||||||
|
{contextHolder}
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='small'>
|
||||||
|
<Dropdown menu={actionItems}>
|
||||||
|
<Button>Actions</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<ColumnViewButton
|
||||||
|
type='courierService'
|
||||||
|
loading={false}
|
||||||
|
collapseState={columnVisibility}
|
||||||
|
updateCollapseState={setColumnVisibility}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||||
|
onClick={() =>
|
||||||
|
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
<ObjectTable
|
||||||
|
ref={tableRef}
|
||||||
|
visibleColumns={columnVisibility}
|
||||||
|
type='courierService'
|
||||||
|
cards={viewMode === 'cards'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={newCourierServiceOpen}
|
||||||
|
onCancel={() => setNewCourierServiceOpen(false)}
|
||||||
|
footer={null}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
width={700}
|
||||||
|
>
|
||||||
|
<NewCourierService
|
||||||
|
onOk={() => {
|
||||||
|
setNewCourierServiceOpen(false)
|
||||||
|
messageApi.success('New courier service created successfully.')
|
||||||
|
tableRef.current?.reload()
|
||||||
|
}}
|
||||||
|
reset={!newCourierServiceOpen}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CourierServices
|
||||||
@ -0,0 +1,198 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import { Space, Flex, Card } from 'antd'
|
||||||
|
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 ViewButton from '../../common/ViewButton'
|
||||||
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
|
import ObjectForm from '../../common/ObjectForm'
|
||||||
|
import EditButtons from '../../common/EditButtons'
|
||||||
|
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||||
|
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||||
|
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||||
|
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||||
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||||
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
|
const log = loglevel.getLogger('CourierServiceInfo')
|
||||||
|
log.setLevel(config.logLevel)
|
||||||
|
|
||||||
|
const CourierServiceInfo = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
const objectFormRef = useRef(null)
|
||||||
|
const actionHandlerRef = useRef(null)
|
||||||
|
const courierServiceId = new URLSearchParams(location.search).get(
|
||||||
|
'courierServiceId'
|
||||||
|
)
|
||||||
|
const [collapseState, updateCollapseState] = useCollapseState(
|
||||||
|
'CourierServiceInfo',
|
||||||
|
{
|
||||||
|
info: true,
|
||||||
|
notes: true,
|
||||||
|
auditLogs: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const [objectFormState, setEditFormState] = useState({
|
||||||
|
isEditing: false,
|
||||||
|
editLoading: false,
|
||||||
|
formValid: false,
|
||||||
|
lock: null,
|
||||||
|
loading: false,
|
||||||
|
objectData: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
reload: () => {
|
||||||
|
objectFormRef?.current?.handleFetchObject?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
edit: () => {
|
||||||
|
objectFormRef?.current?.startEditing?.()
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
cancelEdit: () => {
|
||||||
|
objectFormRef?.current?.cancelEditing?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
finishEdit: () => {
|
||||||
|
objectFormRef?.current?.handleUpdate?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
delete: () => {
|
||||||
|
objectFormRef?.current?.handleDelete?.()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
gap='large'
|
||||||
|
vertical='true'
|
||||||
|
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||||
|
>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='middle'>
|
||||||
|
<Space size='small'>
|
||||||
|
<ObjectActions
|
||||||
|
type='courierService'
|
||||||
|
id={courierServiceId}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
<ViewButton
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
items={[
|
||||||
|
{ key: 'info', label: 'Courier Service Information' },
|
||||||
|
{ key: 'notes', label: 'Notes' },
|
||||||
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
|
]}
|
||||||
|
visibleState={collapseState}
|
||||||
|
updateVisibleState={updateCollapseState}
|
||||||
|
/>
|
||||||
|
<DocumentPrintButton
|
||||||
|
type='courierService'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<LockIndicator lock={objectFormState.lock} />
|
||||||
|
</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.lock?.locked || objectFormState.loading}
|
||||||
|
loading={objectFormState.editLoading}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
<ScrollBox>
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<ActionHandler
|
||||||
|
actions={actions}
|
||||||
|
loading={objectFormState.loading}
|
||||||
|
ref={actionHandlerRef}
|
||||||
|
>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Courier Service Information'
|
||||||
|
icon={<InfoCircleIcon />}
|
||||||
|
active={collapseState.info}
|
||||||
|
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||||
|
collapseKey='info'
|
||||||
|
>
|
||||||
|
<ObjectForm
|
||||||
|
id={courierServiceId}
|
||||||
|
type='courierService'
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
ref={objectFormRef}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ loading, isEditing, objectData }) => (
|
||||||
|
<ObjectInfo
|
||||||
|
loading={loading}
|
||||||
|
isEditing={isEditing}
|
||||||
|
type='courierService'
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ObjectForm>
|
||||||
|
</InfoCollapse>
|
||||||
|
</ActionHandler>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Notes'
|
||||||
|
icon={<NoteIcon />}
|
||||||
|
active={collapseState.notes}
|
||||||
|
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||||
|
collapseKey='notes'
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<NotesPanel _id={courierServiceId} type='courierService' />
|
||||||
|
</Card>
|
||||||
|
</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._id': courierServiceId }}
|
||||||
|
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
</ScrollBox>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CourierServiceInfo
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
|
const NewCourierService = ({ onOk }) => {
|
||||||
|
return (
|
||||||
|
<NewObjectForm
|
||||||
|
type={'courierService'}
|
||||||
|
defaultValues={{
|
||||||
|
active: true,
|
||||||
|
tracked: false
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Required',
|
||||||
|
key: 'required',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='courierService'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={true}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Optional',
|
||||||
|
key: 'optional',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='courierService'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={false}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Summary',
|
||||||
|
key: 'summary',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='courierService'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
visibleProperties={{
|
||||||
|
_id: false,
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: false
|
||||||
|
}}
|
||||||
|
isEditing={false}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<WizardView
|
||||||
|
steps={steps}
|
||||||
|
loading={submitLoading}
|
||||||
|
formValid={formValid}
|
||||||
|
title='New Courier Service'
|
||||||
|
onSubmit={() => {
|
||||||
|
handleSubmit()
|
||||||
|
onOk()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NewObjectForm>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NewCourierService.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
reset: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewCourierService
|
||||||
97
src/components/Dashboard/Management/Couriers.jsx
Normal file
97
src/components/Dashboard/Management/Couriers.jsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { useState, useRef } from 'react'
|
||||||
|
import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
|
||||||
|
import NewCourier from './Couriers/NewCourier'
|
||||||
|
import ObjectTable from '../common/ObjectTable'
|
||||||
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
|
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||||
|
import GridIcon from '../../Icons/GridIcon'
|
||||||
|
import ListIcon from '../../Icons/ListIcon'
|
||||||
|
import useViewMode from '../hooks/useViewMode'
|
||||||
|
import ColumnViewButton from '../common/ColumnViewButton'
|
||||||
|
|
||||||
|
const Couriers = () => {
|
||||||
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
|
const [newCourierOpen, setNewCourierOpen] = useState(false)
|
||||||
|
const tableRef = useRef()
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = useViewMode('courier')
|
||||||
|
|
||||||
|
const [columnVisibility, setColumnVisibility] = useColumnVisibility('courier')
|
||||||
|
|
||||||
|
const actionItems = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New Courier',
|
||||||
|
key: 'newCourier',
|
||||||
|
icon: <PlusIcon />
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: 'Reload List',
|
||||||
|
key: 'reloadList',
|
||||||
|
icon: <ReloadIcon />
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onClick: ({ key }) => {
|
||||||
|
if (key === 'reloadList') {
|
||||||
|
tableRef.current?.reload()
|
||||||
|
} else if (key === 'newCourier') {
|
||||||
|
setNewCourierOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex vertical={'true'} gap='large'>
|
||||||
|
{contextHolder}
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='small'>
|
||||||
|
<Dropdown menu={actionItems}>
|
||||||
|
<Button>Actions</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<ColumnViewButton
|
||||||
|
type='courier'
|
||||||
|
loading={false}
|
||||||
|
collapseState={columnVisibility}
|
||||||
|
updateCollapseState={setColumnVisibility}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||||
|
onClick={() =>
|
||||||
|
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
<ObjectTable
|
||||||
|
ref={tableRef}
|
||||||
|
visibleColumns={columnVisibility}
|
||||||
|
type='courier'
|
||||||
|
cards={viewMode === 'cards'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={newCourierOpen}
|
||||||
|
onCancel={() => setNewCourierOpen(false)}
|
||||||
|
footer={null}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
width={700}
|
||||||
|
>
|
||||||
|
<NewCourier
|
||||||
|
onOk={() => {
|
||||||
|
setNewCourierOpen(false)
|
||||||
|
messageApi.success('New courier created successfully.')
|
||||||
|
tableRef.current?.reload()
|
||||||
|
}}
|
||||||
|
reset={!newCourierOpen}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Couriers
|
||||||
216
src/components/Dashboard/Management/Couriers/CourierInfo.jsx
Normal file
216
src/components/Dashboard/Management/Couriers/CourierInfo.jsx
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import { Space, Flex, Card } from 'antd'
|
||||||
|
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 ViewButton from '../../common/ViewButton'
|
||||||
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
|
import ObjectForm from '../../common/ObjectForm'
|
||||||
|
import EditButtons from '../../common/EditButtons'
|
||||||
|
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||||
|
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||||
|
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||||
|
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||||
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||||
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
import CourierServiceIcon from '../../../Icons/CourierServiceIcon.jsx'
|
||||||
|
|
||||||
|
const log = loglevel.getLogger('CourierInfo')
|
||||||
|
log.setLevel(config.logLevel)
|
||||||
|
|
||||||
|
const CourierInfo = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
const objectFormRef = useRef(null)
|
||||||
|
const actionHandlerRef = useRef(null)
|
||||||
|
const courierId = new URLSearchParams(location.search).get('courierId')
|
||||||
|
const [collapseState, updateCollapseState] = useCollapseState('CourierInfo', {
|
||||||
|
info: true,
|
||||||
|
notes: true,
|
||||||
|
auditLogs: true
|
||||||
|
})
|
||||||
|
const [objectFormState, setEditFormState] = useState({
|
||||||
|
isEditing: false,
|
||||||
|
editLoading: false,
|
||||||
|
formValid: false,
|
||||||
|
lock: null,
|
||||||
|
loading: false,
|
||||||
|
objectData: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
reload: () => {
|
||||||
|
objectFormRef?.current?.handleFetchObject?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
edit: () => {
|
||||||
|
objectFormRef?.current?.startEditing?.()
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
cancelEdit: () => {
|
||||||
|
objectFormRef?.current?.cancelEditing?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
finishEdit: () => {
|
||||||
|
objectFormRef?.current?.handleUpdate?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
delete: () => {
|
||||||
|
objectFormRef?.current?.handleDelete?.()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
gap='large'
|
||||||
|
vertical='true'
|
||||||
|
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||||
|
>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='middle'>
|
||||||
|
<Space size='small'>
|
||||||
|
<ObjectActions
|
||||||
|
type='courier'
|
||||||
|
id={courierId}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
<ViewButton
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
items={[
|
||||||
|
{ key: 'info', label: 'Courier Information' },
|
||||||
|
{ key: 'notes', label: 'Notes' },
|
||||||
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
|
]}
|
||||||
|
visibleState={collapseState}
|
||||||
|
updateVisibleState={updateCollapseState}
|
||||||
|
/>
|
||||||
|
<DocumentPrintButton
|
||||||
|
type='courier'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<LockIndicator lock={objectFormState.lock} />
|
||||||
|
</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.lock?.locked || objectFormState.loading}
|
||||||
|
loading={objectFormState.editLoading}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
<ScrollBox>
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<ActionHandler
|
||||||
|
actions={actions}
|
||||||
|
loading={objectFormState.loading}
|
||||||
|
ref={actionHandlerRef}
|
||||||
|
>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Courier Information'
|
||||||
|
icon={<InfoCircleIcon />}
|
||||||
|
active={collapseState.info}
|
||||||
|
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||||
|
collapseKey='info'
|
||||||
|
>
|
||||||
|
<ObjectForm
|
||||||
|
id={courierId}
|
||||||
|
type='courier'
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
ref={objectFormRef}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ loading, isEditing, objectData }) => (
|
||||||
|
<ObjectInfo
|
||||||
|
loading={loading}
|
||||||
|
isEditing={isEditing}
|
||||||
|
type='courier'
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ObjectForm>
|
||||||
|
</InfoCollapse>
|
||||||
|
</ActionHandler>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Courier Services'
|
||||||
|
icon={<CourierServiceIcon />}
|
||||||
|
active={collapseState.courierServices}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('courierServices', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='courierServices'
|
||||||
|
>
|
||||||
|
{objectFormState.loading ? (
|
||||||
|
<InfoCollapsePlaceholder />
|
||||||
|
) : (
|
||||||
|
<ObjectTable
|
||||||
|
type='courierService'
|
||||||
|
masterFilter={{ 'courier._id': courierId }}
|
||||||
|
visibleColumns={{
|
||||||
|
courier: false,
|
||||||
|
'courier._id': false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Notes'
|
||||||
|
icon={<NoteIcon />}
|
||||||
|
active={collapseState.notes}
|
||||||
|
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||||
|
collapseKey='notes'
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<NotesPanel _id={courierId} type='courier' />
|
||||||
|
</Card>
|
||||||
|
</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._id': courierId }}
|
||||||
|
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
</ScrollBox>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CourierInfo
|
||||||
80
src/components/Dashboard/Management/Couriers/NewCourier.jsx
Normal file
80
src/components/Dashboard/Management/Couriers/NewCourier.jsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
|
const NewCourier = ({ onOk }) => {
|
||||||
|
return (
|
||||||
|
<NewObjectForm type={'courier'}>
|
||||||
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Required',
|
||||||
|
key: 'required',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='courier'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={true}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Optional',
|
||||||
|
key: 'optional',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='courier'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={false}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Summary',
|
||||||
|
key: 'summary',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='courier'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
visibleProperties={{
|
||||||
|
_id: false,
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: false
|
||||||
|
}}
|
||||||
|
isEditing={false}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<WizardView
|
||||||
|
steps={steps}
|
||||||
|
loading={submitLoading}
|
||||||
|
formValid={formValid}
|
||||||
|
title='New Courier'
|
||||||
|
onSubmit={() => {
|
||||||
|
handleSubmit()
|
||||||
|
onOk()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NewObjectForm>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NewCourier.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
reset: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewCourier
|
||||||
@ -17,6 +17,8 @@ import DocumentIcon from '../../Icons/DocumentIcon'
|
|||||||
import DocumentSizeIcon from '../../Icons/DocumentSizeIcon'
|
import DocumentSizeIcon from '../../Icons/DocumentSizeIcon'
|
||||||
import DocumentJobIcon from '../../Icons/DocumentJobIcon'
|
import DocumentJobIcon from '../../Icons/DocumentJobIcon'
|
||||||
import FileIcon from '../../Icons/FileIcon'
|
import FileIcon from '../../Icons/FileIcon'
|
||||||
|
import CourierIcon from '../../Icons/CourierIcon'
|
||||||
|
import CourierServiceIcon from '../../Icons/CourierServiceIcon'
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
@ -50,6 +52,19 @@ const items = [
|
|||||||
path: '/dashboard/management/materials'
|
path: '/dashboard/management/materials'
|
||||||
},
|
},
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: 'couriers',
|
||||||
|
icon: <CourierIcon />,
|
||||||
|
label: 'Couriers',
|
||||||
|
path: '/dashboard/management/couriers'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'courierServices',
|
||||||
|
icon: <CourierServiceIcon />,
|
||||||
|
label: 'Courier Services',
|
||||||
|
path: '/dashboard/management/courierservices'
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
{
|
{
|
||||||
key: 'noteTypes',
|
key: 'noteTypes',
|
||||||
icon: <NoteTypeIcon />,
|
icon: <NoteTypeIcon />,
|
||||||
@ -139,6 +154,8 @@ const routeKeyMap = {
|
|||||||
'/dashboard/management/users': 'users',
|
'/dashboard/management/users': 'users',
|
||||||
'/dashboard/management/products': 'products',
|
'/dashboard/management/products': 'products',
|
||||||
'/dashboard/management/vendors': 'vendors',
|
'/dashboard/management/vendors': 'vendors',
|
||||||
|
'/dashboard/management/couriers': 'couriers',
|
||||||
|
'/dashboard/management/courierservices': 'courierServices',
|
||||||
'/dashboard/management/materials': 'materials',
|
'/dashboard/management/materials': 'materials',
|
||||||
'/dashboard/management/notetypes': 'noteTypes',
|
'/dashboard/management/notetypes': 'noteTypes',
|
||||||
'/dashboard/management/settings': 'settings',
|
'/dashboard/management/settings': 'settings',
|
||||||
@ -154,9 +171,15 @@ const routeKeyMap = {
|
|||||||
const ManagementSidebar = (props) => {
|
const ManagementSidebar = (props) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const selectedKey = (() => {
|
const selectedKey = (() => {
|
||||||
const match = Object.keys(routeKeyMap).find((path) =>
|
const match = Object.keys(routeKeyMap).find((path) => {
|
||||||
location.pathname.startsWith(path)
|
const pathSplit = path.split('/')
|
||||||
)
|
const locationPathSplit = location.pathname.split('/')
|
||||||
|
if (pathSplit.length > locationPathSplit.length) return false
|
||||||
|
for (let i = 0; i < pathSplit.length; i++) {
|
||||||
|
if (pathSplit[i] !== locationPathSplit[i]) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
return match ? routeKeyMap[match] : 'filaments'
|
return match ? routeKeyMap[match] : 'filaments'
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
|||||||
@ -51,9 +51,15 @@ const routeKeyMap = {
|
|||||||
const ProductionSidebar = (props) => {
|
const ProductionSidebar = (props) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const selectedKey = (() => {
|
const selectedKey = (() => {
|
||||||
const match = Object.keys(routeKeyMap).find((path) =>
|
const match = Object.keys(routeKeyMap).find((path) => {
|
||||||
location.pathname.startsWith(path)
|
const pathSplit = path.split('/')
|
||||||
)
|
const locationPathSplit = location.pathname.split('/')
|
||||||
|
if (pathSplit.length > locationPathSplit.length) return false
|
||||||
|
for (let i = 0; i < pathSplit.length; i++) {
|
||||||
|
if (pathSplit[i] !== locationPathSplit[i]) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
return match ? routeKeyMap[match] : 'overview'
|
return match ? routeKeyMap[match] : 'overview'
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,20 @@ import { Form, message } from 'antd'
|
|||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import merge from 'lodash/merge'
|
import merge from 'lodash/merge'
|
||||||
|
import set from 'lodash/set'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
|
||||||
|
const buildObjectFromEntries = (entries = []) => {
|
||||||
|
return entries.reduce((acc, entry) => {
|
||||||
|
const { namePath, value } = entry || {}
|
||||||
|
if (!Array.isArray(namePath) || value === undefined) {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
set(acc, namePath, value)
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NewObjectForm is a reusable form component for creating new objects.
|
* NewObjectForm is a reusable form component for creating new objects.
|
||||||
* It handles form validation, submission, and error handling logic.
|
* It handles form validation, submission, and error handling logic.
|
||||||
@ -30,41 +42,102 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
const model = getModelByName(type)
|
const model = getModelByName(type)
|
||||||
|
|
||||||
// Function to calculate computed values from model properties
|
// Function to calculate computed values from model properties
|
||||||
const calculateComputedValues = useCallback((currentData, model) => {
|
const calculateComputedValues = useCallback(
|
||||||
if (!model || !model.properties) return {}
|
(currentData, modelDefinition) => {
|
||||||
|
if (!modelDefinition || !Array.isArray(modelDefinition.properties)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const computedValues = {}
|
const normalizedPath = (name, parentPath = []) => {
|
||||||
|
if (Array.isArray(name)) {
|
||||||
|
return [...parentPath, ...name]
|
||||||
|
}
|
||||||
|
if (typeof name === 'number') {
|
||||||
|
return [...parentPath, name]
|
||||||
|
}
|
||||||
|
if (typeof name === 'string' && name.length > 0) {
|
||||||
|
return [...parentPath, ...name.split('.')]
|
||||||
|
}
|
||||||
|
return parentPath
|
||||||
|
}
|
||||||
|
|
||||||
model.properties.forEach((property) => {
|
const getValueAtPath = (dataSource, path) => {
|
||||||
// Check if this property has a computed value function
|
if (!Array.isArray(path) || path.length === 0) {
|
||||||
if (property.value && typeof property.value === 'function') {
|
return dataSource
|
||||||
try {
|
}
|
||||||
const computedValue = property.value(currentData)
|
return path.reduce((acc, key) => {
|
||||||
if (computedValue !== undefined) {
|
if (acc == null) return acc
|
||||||
computedValues[property.name] = computedValue
|
return acc[key]
|
||||||
|
}, dataSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
const computedEntries = []
|
||||||
|
|
||||||
|
const processProperty = (property, scopeData, parentPath = []) => {
|
||||||
|
if (!property?.name) return
|
||||||
|
|
||||||
|
const propertyPath = normalizedPath(property.name, parentPath)
|
||||||
|
|
||||||
|
if (property.value && typeof property.value === 'function') {
|
||||||
|
try {
|
||||||
|
const computedValue = property.value(scopeData || {})
|
||||||
|
if (computedValue !== undefined) {
|
||||||
|
computedEntries.push({
|
||||||
|
namePath: propertyPath,
|
||||||
|
value: computedValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
`Error calculating value for property ${property.name}:`,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(property.properties) &&
|
||||||
|
property.properties.length > 0
|
||||||
|
) {
|
||||||
|
if (property.type === 'objectChildren') {
|
||||||
|
const childValues = getValueAtPath(currentData, propertyPath)
|
||||||
|
if (Array.isArray(childValues)) {
|
||||||
|
childValues.forEach((childData = {}, index) => {
|
||||||
|
property.properties.forEach((childProperty) => {
|
||||||
|
processProperty(childProperty, childData || {}, [
|
||||||
|
...propertyPath,
|
||||||
|
index
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const nestedScope = getValueAtPath(currentData, propertyPath) || {}
|
||||||
|
property.properties.forEach((childProperty) => {
|
||||||
|
processProperty(childProperty, nestedScope || {}, propertyPath)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
`Error calculating value for property ${property.name}:`,
|
|
||||||
error
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
return computedValues
|
modelDefinition.properties.forEach((property) => {
|
||||||
}, [])
|
processProperty(property, currentData)
|
||||||
|
})
|
||||||
|
|
||||||
|
return computedEntries
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
// Set initial form values when defaultValues change
|
// Set initial form values when defaultValues change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Object.keys(defaultValues).length > 0) {
|
if (Object.keys(defaultValues).length > 0) {
|
||||||
// Calculate computed values for initial data
|
// Calculate computed values for initial data
|
||||||
const computedValues = calculateComputedValues(defaultValues, model)
|
const computedEntries = calculateComputedValues(defaultValues, model)
|
||||||
const initialFormData = { ...defaultValues, ...computedValues }
|
const computedValuesObject = buildObjectFromEntries(computedEntries)
|
||||||
|
const initialFormData = merge({}, defaultValues, computedValuesObject)
|
||||||
form.setFieldsValue(initialFormData)
|
form.setFieldsValue(initialFormData)
|
||||||
setObjectData((prev) => {
|
setObjectData((prev) => merge({}, prev, initialFormData))
|
||||||
return merge({}, prev, initialFormData)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}, [form, defaultValues, calculateComputedValues, model])
|
}, [form, defaultValues, calculateComputedValues, model])
|
||||||
|
|
||||||
@ -104,16 +177,29 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
style={style}
|
style={style}
|
||||||
onValuesChange={(_changedValues, allFormValues) => {
|
onValuesChange={(_changedValues, allFormValues) => {
|
||||||
// Calculate computed values based on current form data
|
// Calculate computed values based on current form data
|
||||||
const currentFormData = { ...objectData, ...allFormValues }
|
const currentFormData = merge({}, objectData || {}, allFormValues)
|
||||||
const computedValues = calculateComputedValues(currentFormData, model)
|
const computedEntries = calculateComputedValues(currentFormData, model)
|
||||||
|
|
||||||
// Update form with computed values if any were calculated
|
if (Array.isArray(computedEntries) && computedEntries.length > 0) {
|
||||||
if (Object.keys(computedValues).length > 0) {
|
computedEntries.forEach(({ namePath, value }) => {
|
||||||
form.setFieldsValue(computedValues)
|
if (!Array.isArray(namePath) || value === undefined) return
|
||||||
|
const currentValue = form.getFieldValue(namePath)
|
||||||
|
if (currentValue !== value) {
|
||||||
|
if (typeof form.setFieldValue === 'function') {
|
||||||
|
form.setFieldValue(namePath, value)
|
||||||
|
} else {
|
||||||
|
const fallbackPayload = buildObjectFromEntries([
|
||||||
|
{ namePath, value }
|
||||||
|
])
|
||||||
|
form.setFieldsValue(fallbackPayload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge all values (user input + computed values)
|
// Merge all values (user input + computed values)
|
||||||
const allValues = { ...allFormValues, ...computedValues }
|
const computedValuesObject = buildObjectFromEntries(computedEntries)
|
||||||
|
const allValues = merge({}, allFormValues, computedValuesObject)
|
||||||
setObjectData((prev) => {
|
setObjectData((prev) => {
|
||||||
return merge({}, prev, allValues)
|
return merge({}, prev, allValues)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -230,7 +230,11 @@ const ObjectChildTable = ({
|
|||||||
render: (_text, record) => {
|
render: (_text, record) => {
|
||||||
return (
|
return (
|
||||||
<Flex justify={'space-between'}>
|
<Flex justify={'space-between'}>
|
||||||
<Text>{record[property.name]}</Text>
|
<Text>
|
||||||
|
{property?.prefix}
|
||||||
|
{record[property.name]}
|
||||||
|
{property?.suffix}
|
||||||
|
</Text>
|
||||||
{rollupLabel && <Text type='secondary'>{rollupLabel}:</Text>}
|
{rollupLabel && <Text type='secondary'>{rollupLabel}:</Text>}
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
@ -303,7 +307,7 @@ const ObjectChildTable = ({
|
|||||||
rowKey={resolvedRowKey}
|
rowKey={resolvedRowKey}
|
||||||
scroll={scrollConfig}
|
scroll={scrollConfig}
|
||||||
locale={{ emptyText }}
|
locale={{ emptyText }}
|
||||||
style={{ maxWidth }}
|
style={{ maxWidth, minWidth: 0 }}
|
||||||
className='rollup-table'
|
className='rollup-table'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -313,15 +317,15 @@ const ObjectChildTable = ({
|
|||||||
<Flex vertical>
|
<Flex vertical>
|
||||||
<div ref={mainTableWrapperRef}>
|
<div ref={mainTableWrapperRef}>
|
||||||
<Table
|
<Table
|
||||||
style={{ maxWidth }}
|
style={{ maxWidth, minWidth: 0 }}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
columns={tableColumns}
|
columns={tableColumns}
|
||||||
loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
|
loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
|
||||||
pagination={false}
|
|
||||||
size={size}
|
size={size}
|
||||||
rowKey={resolvedRowKey}
|
rowKey={resolvedRowKey}
|
||||||
scroll={scrollConfig}
|
scroll={scrollConfig}
|
||||||
locale={{ emptyText }}
|
locale={{ emptyText }}
|
||||||
|
pagination={false}
|
||||||
className={hasRollups ? 'child-table-rollups' : 'child-table'}
|
className={hasRollups ? 'child-table-rollups' : 'child-table'}
|
||||||
{...tableProps}
|
{...tableProps}
|
||||||
/>
|
/>
|
||||||
@ -382,6 +386,7 @@ const ObjectChildTable = ({
|
|||||||
scroll={scrollConfig}
|
scroll={scrollConfig}
|
||||||
locale={{ emptyText }}
|
locale={{ emptyText }}
|
||||||
className={hasRollups ? 'child-table-rollups' : 'child-table'}
|
className={hasRollups ? 'child-table-rollups' : 'child-table'}
|
||||||
|
style={{ maxWidth, minWidth: 0 }}
|
||||||
{...tableProps}
|
{...tableProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -400,7 +405,7 @@ const ObjectChildTable = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card style={{ minWidth: 0 }}>
|
||||||
<Flex vertical gap={'middle'}>
|
<Flex vertical gap={'middle'}>
|
||||||
<Flex justify={'space-between'}>
|
<Flex justify={'space-between'}>
|
||||||
<Button>Actions</Button>
|
<Button>Actions</Button>
|
||||||
|
|||||||
@ -13,8 +13,20 @@ import { AuthContext } from '../context/AuthContext'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import DeleteObjectModal from './DeleteObjectModal'
|
import DeleteObjectModal from './DeleteObjectModal'
|
||||||
import merge from 'lodash/merge'
|
import merge from 'lodash/merge'
|
||||||
|
import set from 'lodash/set'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
|
||||||
|
const buildObjectFromEntries = (entries = []) => {
|
||||||
|
return entries.reduce((acc, entry) => {
|
||||||
|
const { namePath, value } = entry || {}
|
||||||
|
if (!Array.isArray(namePath) || value === undefined) {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
set(acc, namePath, value)
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ObjectForm is a reusable form component for editing any object type.
|
* ObjectForm is a reusable form component for editing any object type.
|
||||||
* It handles fetching, updating, locking, unlocking, and validation logic.
|
* It handles fetching, updating, locking, unlocking, and validation logic.
|
||||||
@ -116,30 +128,93 @@ const ObjectForm = forwardRef(
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Function to calculate computed values from model properties
|
// Function to calculate computed values from model properties
|
||||||
const calculateComputedValues = useCallback((currentData, model) => {
|
const calculateComputedValues = useCallback(
|
||||||
if (!model || !model.properties) return {}
|
(currentData, modelDefinition) => {
|
||||||
|
if (!modelDefinition || !Array.isArray(modelDefinition.properties)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const computedValues = {}
|
const normalizedPath = (name, parentPath = []) => {
|
||||||
|
if (Array.isArray(name)) {
|
||||||
|
return [...parentPath, ...name]
|
||||||
|
}
|
||||||
|
if (typeof name === 'number') {
|
||||||
|
return [...parentPath, name]
|
||||||
|
}
|
||||||
|
if (typeof name === 'string' && name.length > 0) {
|
||||||
|
return [...parentPath, ...name.split('.')]
|
||||||
|
}
|
||||||
|
return parentPath
|
||||||
|
}
|
||||||
|
|
||||||
model.properties.forEach((property) => {
|
const getValueAtPath = (dataSource, path) => {
|
||||||
// Check if this property has a computed value function
|
if (!Array.isArray(path) || path.length === 0) {
|
||||||
if (property.value && typeof property.value === 'function') {
|
return dataSource
|
||||||
try {
|
}
|
||||||
const computedValue = property.value(currentData)
|
return path.reduce((acc, key) => {
|
||||||
if (computedValue !== undefined) {
|
if (acc == null) return acc
|
||||||
computedValues[property.name] = computedValue
|
return acc[key]
|
||||||
|
}, dataSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
const computedEntries = []
|
||||||
|
|
||||||
|
const processProperty = (property, scopeData, parentPath = []) => {
|
||||||
|
if (!property?.name) return
|
||||||
|
|
||||||
|
const propertyPath = normalizedPath(property.name, parentPath)
|
||||||
|
|
||||||
|
if (property.value && typeof property.value === 'function') {
|
||||||
|
try {
|
||||||
|
const computedValue = property.value(scopeData || {})
|
||||||
|
if (computedValue !== undefined) {
|
||||||
|
computedEntries.push({
|
||||||
|
namePath: propertyPath,
|
||||||
|
value: computedValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
`Error calculating value for property ${property.name}:`,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(property.properties) &&
|
||||||
|
property.properties.length > 0
|
||||||
|
) {
|
||||||
|
if (property.type === 'objectChildren') {
|
||||||
|
const childValues = getValueAtPath(currentData, propertyPath)
|
||||||
|
if (Array.isArray(childValues)) {
|
||||||
|
childValues.forEach((childData = {}, index) => {
|
||||||
|
property.properties.forEach((childProperty) => {
|
||||||
|
processProperty(childProperty, childData || {}, [
|
||||||
|
...propertyPath,
|
||||||
|
index
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const nestedScope =
|
||||||
|
getValueAtPath(currentData, propertyPath) || {}
|
||||||
|
property.properties.forEach((childProperty) => {
|
||||||
|
processProperty(childProperty, nestedScope || {}, propertyPath)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
`Error calculating value for property ${property.name}:`,
|
|
||||||
error
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
return computedValues
|
modelDefinition.properties.forEach((property) => {
|
||||||
}, [])
|
processProperty(property, currentData)
|
||||||
|
})
|
||||||
|
|
||||||
|
return computedEntries
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
// Validate form on change (debounced to avoid heavy work on every keystroke)
|
// Validate form on change (debounced to avoid heavy work on every keystroke)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -204,8 +279,9 @@ const ObjectForm = forwardRef(
|
|||||||
serverObjectData.current = data
|
serverObjectData.current = data
|
||||||
|
|
||||||
// Calculate and set computed values on initial load
|
// Calculate and set computed values on initial load
|
||||||
const computedValues = calculateComputedValues(data, model)
|
const computedEntries = calculateComputedValues(data, model)
|
||||||
const initialFormData = { ...data, ...computedValues }
|
const computedValuesObject = buildObjectFromEntries(computedEntries)
|
||||||
|
const initialFormData = merge({}, data, computedValuesObject)
|
||||||
|
|
||||||
form.setFieldsValue(initialFormData)
|
form.setFieldsValue(initialFormData)
|
||||||
setFetchLoading(false)
|
setFetchLoading(false)
|
||||||
@ -290,11 +366,16 @@ const ObjectForm = forwardRef(
|
|||||||
const cancelEditing = () => {
|
const cancelEditing = () => {
|
||||||
if (serverObjectData.current) {
|
if (serverObjectData.current) {
|
||||||
// Recalculate computed values when canceling
|
// Recalculate computed values when canceling
|
||||||
const computedValues = calculateComputedValues(
|
const computedEntries = calculateComputedValues(
|
||||||
serverObjectData.current,
|
serverObjectData.current,
|
||||||
model
|
model
|
||||||
)
|
)
|
||||||
const resetFormData = { ...serverObjectData.current, ...computedValues }
|
const computedValuesObject = buildObjectFromEntries(computedEntries)
|
||||||
|
const resetFormData = merge(
|
||||||
|
{},
|
||||||
|
serverObjectData.current,
|
||||||
|
computedValuesObject
|
||||||
|
)
|
||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
isEditingRef.current = false
|
isEditingRef.current = false
|
||||||
form.setFieldsValue(resetFormData)
|
form.setFieldsValue(resetFormData)
|
||||||
@ -403,34 +484,38 @@ const ObjectForm = forwardRef(
|
|||||||
...(serverObjectData.current || {}),
|
...(serverObjectData.current || {}),
|
||||||
...allFormValues
|
...allFormValues
|
||||||
}
|
}
|
||||||
const computedValues = calculateComputedValues(
|
const computedEntries = calculateComputedValues(
|
||||||
currentFormData,
|
currentFormData,
|
||||||
model
|
model
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update form with computed values if any were calculated and they changed
|
if (Array.isArray(computedEntries) && computedEntries.length > 0) {
|
||||||
if (Object.keys(computedValues).length > 0) {
|
computedEntries.forEach(({ namePath, value }) => {
|
||||||
const currentComputedValues = form.getFieldsValue(
|
if (!Array.isArray(namePath) || value === undefined) return
|
||||||
Object.keys(computedValues)
|
const currentValue = form.getFieldValue(namePath)
|
||||||
)
|
if (currentValue !== value) {
|
||||||
const hasDiff = Object.keys(computedValues).some(
|
if (typeof form.setFieldValue === 'function') {
|
||||||
(key) => currentComputedValues[key] !== computedValues[key]
|
form.setFieldValue(namePath, value)
|
||||||
)
|
} else {
|
||||||
|
const fallbackPayload = buildObjectFromEntries([
|
||||||
if (hasDiff) {
|
{ namePath, value }
|
||||||
form.setFieldsValue(computedValues)
|
])
|
||||||
}
|
form.setFieldsValue(fallbackPayload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge all values (user input + computed values) and keep editing flag
|
const computedValuesObject = buildObjectFromEntries(computedEntries)
|
||||||
const allValues = {
|
const mergedFormValues = merge(
|
||||||
...allFormValues,
|
{},
|
||||||
...computedValues,
|
allFormValues,
|
||||||
_isEditing: isEditingRef.current
|
computedValuesObject
|
||||||
}
|
)
|
||||||
|
mergedFormValues._isEditing = isEditingRef.current
|
||||||
|
|
||||||
setObjectData((prev) => {
|
setObjectData((prev) => {
|
||||||
return { ...prev, ...allValues }
|
return { ...prev, ...mergedFormValues }
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -58,10 +58,10 @@ const WizardView = ({
|
|||||||
gap={'middle'}
|
gap={'middle'}
|
||||||
style={
|
style={
|
||||||
sideBarGrow == false
|
sideBarGrow == false
|
||||||
? { width: '100%' }
|
? { width: '100%', minWidth: 0 }
|
||||||
: isMobile
|
: isMobile
|
||||||
? { width: '100%' }
|
? { width: '100%', minWidth: 0 }
|
||||||
: { width: '400px' }
|
: { width: '400px', minWidth: 0 }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Flex vertical gap='middle' style={{ flexGrow: 1, width: '100%' }}>
|
<Flex vertical gap='middle' style={{ flexGrow: 1, width: '100%' }}>
|
||||||
|
|||||||
6
src/components/Icons/CourierIcon.jsx
Normal file
6
src/components/Icons/CourierIcon.jsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import CustomIconSvg from '../../../assets/icons/couriericon.svg?react'
|
||||||
|
|
||||||
|
const CourierIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
||||||
|
|
||||||
|
export default CourierIcon
|
||||||
7
src/components/Icons/CourierServiceIcon.jsx
Normal file
7
src/components/Icons/CourierServiceIcon.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import CustomIconSvg from '../../../assets/icons/courierserviceicon.svg?react'
|
||||||
|
|
||||||
|
const CourierServiceIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
||||||
|
|
||||||
|
export default CourierServiceIcon
|
||||||
|
|
||||||
6
src/components/Icons/OrderItemsIcon.jsx
Normal file
6
src/components/Icons/OrderItemsIcon.jsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import CustomIconSvg from '../../../assets/icons/orderitemsicon.svg?react'
|
||||||
|
|
||||||
|
const OrderItemsIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
||||||
|
|
||||||
|
export default OrderItemsIcon
|
||||||
@ -7,6 +7,8 @@ import { Job } from './models/Job'
|
|||||||
import { Product } from './models/Product'
|
import { Product } from './models/Product'
|
||||||
import { Part } from './models/Part.js'
|
import { Part } from './models/Part.js'
|
||||||
import { Vendor } from './models/Vendor'
|
import { Vendor } from './models/Vendor'
|
||||||
|
import { Courier } from './models/Courier'
|
||||||
|
import { CourierService } from './models/CourierService'
|
||||||
import { File } from './models/File'
|
import { File } from './models/File'
|
||||||
import { SubJob } from './models/SubJob'
|
import { SubJob } from './models/SubJob'
|
||||||
import { Initial } from './models/Initial'
|
import { Initial } from './models/Initial'
|
||||||
@ -36,6 +38,8 @@ export const objectModels = [
|
|||||||
Product,
|
Product,
|
||||||
Part,
|
Part,
|
||||||
Vendor,
|
Vendor,
|
||||||
|
Courier,
|
||||||
|
CourierService,
|
||||||
File,
|
File,
|
||||||
SubJob,
|
SubJob,
|
||||||
Initial,
|
Initial,
|
||||||
@ -66,6 +70,8 @@ export {
|
|||||||
Product,
|
Product,
|
||||||
Part,
|
Part,
|
||||||
Vendor,
|
Vendor,
|
||||||
|
Courier,
|
||||||
|
CourierService,
|
||||||
File,
|
File,
|
||||||
SubJob,
|
SubJob,
|
||||||
Initial,
|
Initial,
|
||||||
|
|||||||
142
src/database/models/Courier.js
Normal file
142
src/database/models/Courier.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import CourierIcon from '../../components/Icons/CourierIcon'
|
||||||
|
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 ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||||
|
import BinIcon from '../../components/Icons/BinIcon'
|
||||||
|
|
||||||
|
export const Courier = {
|
||||||
|
name: 'courier',
|
||||||
|
label: 'Courier',
|
||||||
|
prefix: 'COR',
|
||||||
|
icon: CourierIcon,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'info',
|
||||||
|
label: 'Info',
|
||||||
|
default: true,
|
||||||
|
row: true,
|
||||||
|
icon: InfoCircleIcon,
|
||||||
|
url: (_id) => `/dashboard/management/couriers/info?courierId=${_id}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reload',
|
||||||
|
label: 'Reload',
|
||||||
|
icon: ReloadIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/couriers/info?courierId=${_id}&action=reload`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'edit',
|
||||||
|
label: 'Edit',
|
||||||
|
row: true,
|
||||||
|
icon: EditIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/couriers/info?courierId=${_id}&action=edit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'finishEdit',
|
||||||
|
label: 'Save Edits',
|
||||||
|
icon: CheckIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/couriers/info?courierId=${_id}&action=finishEdit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancelEdit',
|
||||||
|
label: 'Cancel Edits',
|
||||||
|
icon: XMarkIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/couriers/info?courierId=${_id}&action=cancelEdit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
console.log(objectData?._isEditing)
|
||||||
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
label: 'Delete',
|
||||||
|
icon: BinIcon,
|
||||||
|
danger: true,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/couriers/info?courierId=${_id}&action=delete`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
columns: ['name', '_id', 'country', 'email', 'website', 'createdAt'],
|
||||||
|
filters: ['name', '_id', 'country', 'email'],
|
||||||
|
sorters: ['name', 'country', 'email', 'createdAt', '_id'],
|
||||||
|
group: ['country'],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
columnFixed: 'left',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'courier',
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
columnFixed: 'left',
|
||||||
|
required: true,
|
||||||
|
type: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'contact',
|
||||||
|
label: 'Contact',
|
||||||
|
type: 'text',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'country',
|
||||||
|
label: 'Country',
|
||||||
|
type: 'country',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
label: 'Email',
|
||||||
|
columnWidth: 300,
|
||||||
|
type: 'email',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'phone',
|
||||||
|
label: 'Phone',
|
||||||
|
type: 'phone',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'website',
|
||||||
|
label: 'Website',
|
||||||
|
columnWidth: 300,
|
||||||
|
type: 'url',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
170
src/database/models/CourierService.js
Normal file
170
src/database/models/CourierService.js
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import CourierServiceIcon from '../../components/Icons/CourierServiceIcon'
|
||||||
|
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 ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||||
|
import BinIcon from '../../components/Icons/BinIcon'
|
||||||
|
|
||||||
|
export const CourierService = {
|
||||||
|
name: 'courierService',
|
||||||
|
label: 'Courier Service',
|
||||||
|
prefix: 'COS',
|
||||||
|
icon: CourierServiceIcon,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'info',
|
||||||
|
label: 'Info',
|
||||||
|
default: true,
|
||||||
|
row: true,
|
||||||
|
icon: InfoCircleIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/courierservices/info?courierServiceId=${_id}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reload',
|
||||||
|
label: 'Reload',
|
||||||
|
icon: ReloadIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/courierservices/info?courierServiceId=${_id}&action=reload`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'edit',
|
||||||
|
label: 'Edit',
|
||||||
|
row: true,
|
||||||
|
icon: EditIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/courierservices/info?courierServiceId=${_id}&action=edit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'finishEdit',
|
||||||
|
label: 'Save Edits',
|
||||||
|
icon: CheckIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/courierservices/info?courierServiceId=${_id}&action=finishEdit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancelEdit',
|
||||||
|
label: 'Cancel Edits',
|
||||||
|
icon: XMarkIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/courierservices/info?courierServiceId=${_id}&action=cancelEdit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
console.log(objectData?._isEditing)
|
||||||
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
label: 'Delete',
|
||||||
|
icon: BinIcon,
|
||||||
|
danger: true,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/courierservices/info?courierServiceId=${_id}&action=delete`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
'name',
|
||||||
|
'_id',
|
||||||
|
'courier',
|
||||||
|
'courier._id',
|
||||||
|
'tracked',
|
||||||
|
'deliveryTime',
|
||||||
|
'active'
|
||||||
|
],
|
||||||
|
filters: ['name', '_id', 'courier._id', 'active', 'deliveryTime', 'tracked'],
|
||||||
|
sorters: [
|
||||||
|
'name',
|
||||||
|
'courier._id',
|
||||||
|
'active',
|
||||||
|
'tracked',
|
||||||
|
'estimatedDeliveryTime',
|
||||||
|
'createdAt',
|
||||||
|
'_id'
|
||||||
|
],
|
||||||
|
group: ['courier'],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
columnFixed: 'left',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'courierService',
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
columnFixed: 'left',
|
||||||
|
required: true,
|
||||||
|
type: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courier',
|
||||||
|
label: 'Courier',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'courier',
|
||||||
|
showHyperlink: true,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courier._id',
|
||||||
|
label: 'Courier ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'courier',
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deliveryTime',
|
||||||
|
label: 'Delivery Time',
|
||||||
|
type: 'number',
|
||||||
|
readOnly: false,
|
||||||
|
required: false,
|
||||||
|
suffix: 'days',
|
||||||
|
columnWidth: 175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'active',
|
||||||
|
label: 'Active',
|
||||||
|
type: 'bool',
|
||||||
|
readOnly: false,
|
||||||
|
required: true,
|
||||||
|
columnWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tracked',
|
||||||
|
label: 'Tracked',
|
||||||
|
type: 'bool',
|
||||||
|
readOnly: false,
|
||||||
|
required: true,
|
||||||
|
columnWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'website',
|
||||||
|
label: 'Website',
|
||||||
|
columnWidth: 300,
|
||||||
|
type: 'url',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -122,11 +122,14 @@ export const Part = {
|
|||||||
objectType: 'vendor'
|
objectType: 'vendor'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'globalPricing',
|
name: 'cost',
|
||||||
label: 'Global Price',
|
label: 'Cost',
|
||||||
columnWidth: 150,
|
columnWidth: 150,
|
||||||
required: true,
|
required: true,
|
||||||
type: 'bool'
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
min: 0,
|
||||||
|
step: 0.01
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'priceMode',
|
name: 'priceMode',
|
||||||
@ -153,18 +156,21 @@ export const Part = {
|
|||||||
step: 0.01
|
step: 0.01
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'amount',
|
name: 'price',
|
||||||
label: 'Amount',
|
label: 'Price',
|
||||||
required: true,
|
required: true,
|
||||||
disabled: (objectData) => {
|
|
||||||
return (
|
|
||||||
objectData.globalPricing == true || objectData.priceMode == 'margin'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
type: 'number',
|
type: 'number',
|
||||||
prefix: '£',
|
prefix: '£',
|
||||||
min: 0,
|
min: 0,
|
||||||
step: 0.1
|
step: 0.1,
|
||||||
|
roundNumber: 2,
|
||||||
|
value: (objectData) => {
|
||||||
|
if (objectData?.priceMode == 'margin') {
|
||||||
|
return objectData?.cost * (1 + objectData?.margin / 100) || undefined
|
||||||
|
} else {
|
||||||
|
return objectData?.price || undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'file',
|
name: 'file',
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import PurchaseOrderIcon from '../../components/Icons/PurchaseOrderIcon'
|
|||||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||||
|
|
||||||
export const PurchaseOrder = {
|
export const PurchaseOrder = {
|
||||||
name: 'purchaseorder',
|
name: 'purchaseOrder',
|
||||||
label: 'Product Stock',
|
label: 'Purchase Order',
|
||||||
prefix: 'PDS',
|
prefix: 'POR',
|
||||||
icon: PurchaseOrderIcon,
|
icon: PurchaseOrderIcon,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
@ -14,8 +14,167 @@ export const PurchaseOrder = {
|
|||||||
row: true,
|
row: true,
|
||||||
icon: InfoCircleIcon,
|
icon: InfoCircleIcon,
|
||||||
url: (_id) =>
|
url: (_id) =>
|
||||||
`/dashboard/management/purchaseorders/info?purchaseOrderId=${_id}`
|
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}`
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
url: (id) => `/dashboard/management/purchaseorders/info?purchaseOrderId=${id}`
|
group: ['vendor'],
|
||||||
|
filters: ['vendor', 'vendor._id'],
|
||||||
|
sorters: ['createdAt', 'state', 'updatedAt'],
|
||||||
|
columns: ['_id', 'createdAt', 'state', 'updatedAt', 'vendor', 'vendor._id'],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
type: 'id',
|
||||||
|
columnFixed: 'left',
|
||||||
|
objectType: 'purchaseOrder',
|
||||||
|
columnWidth: 140,
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{ name: 'state', label: 'State', type: 'state', readOnly: true },
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vendor',
|
||||||
|
label: 'Vendor',
|
||||||
|
required: true,
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'vendor'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vendor._id',
|
||||||
|
label: 'Vendor ID',
|
||||||
|
readOnly: true,
|
||||||
|
type: 'id',
|
||||||
|
showHyperlink: true,
|
||||||
|
objectType: 'vendor'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courierService',
|
||||||
|
label: 'Courier Service',
|
||||||
|
required: true,
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'courierService'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courierService._id',
|
||||||
|
label: 'Courier Service ID',
|
||||||
|
readOnly: true,
|
||||||
|
type: 'id',
|
||||||
|
showHyperlink: true,
|
||||||
|
objectType: 'courierService'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'items',
|
||||||
|
label: 'Order Items',
|
||||||
|
type: 'objectChildren',
|
||||||
|
required: true,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'itemType',
|
||||||
|
label: 'Item Type',
|
||||||
|
type: 'objectType',
|
||||||
|
masterFilter: ['part', 'packaging'],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'item',
|
||||||
|
label: 'Item',
|
||||||
|
type: 'object',
|
||||||
|
objectType: (objectData) => {
|
||||||
|
return objectData?.itemType
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'item._id',
|
||||||
|
label: 'Item ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: (objectData) => {
|
||||||
|
return objectData?.itemType
|
||||||
|
},
|
||||||
|
showHyperlink: true,
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.item?._id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quantity',
|
||||||
|
label: 'Quantity',
|
||||||
|
type: 'number',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'price',
|
||||||
|
label: 'Price',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
prefix: '£',
|
||||||
|
min: 0,
|
||||||
|
step: 0.01,
|
||||||
|
value: (objectData) => {
|
||||||
|
if (objectData?.price == undefined) {
|
||||||
|
console.log('PurchaseOrder.js', objectData)
|
||||||
|
return (
|
||||||
|
(objectData?.item?.cost || 0) * (objectData?.quantity || 0) ||
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return objectData?.part?.price || undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
rollups: [
|
||||||
|
{
|
||||||
|
name: 'totalQuantity',
|
||||||
|
label: 'Total',
|
||||||
|
type: 'number',
|
||||||
|
property: 'quantity',
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.items?.reduce(
|
||||||
|
(acc, item) => acc + item.quantity,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalPrice',
|
||||||
|
label: 'Total',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
property: 'price',
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.items?.reduce((acc, item) => acc + item.price, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cost',
|
||||||
|
label: 'Cost',
|
||||||
|
type: 'netGross',
|
||||||
|
required: true,
|
||||||
|
prefix: '£',
|
||||||
|
min: 0,
|
||||||
|
step: 0.01,
|
||||||
|
value: (objectData) => {
|
||||||
|
const net = objectData?.items?.reduce(
|
||||||
|
(acc, item) => acc + item.price,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
return { net: net, gross: net }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,7 +69,6 @@ export const Vendor = {
|
|||||||
`/dashboard/management/vendors/info?vendorId=${_id}&action=delete`
|
`/dashboard/management/vendors/info?vendorId=${_id}&action=delete`
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
url: (id) => `/dashboard/management/vendors/info?vendorId=${id}`,
|
|
||||||
columns: ['name', '_id', 'country', 'email', 'website', 'createdAt'],
|
columns: ['name', '_id', 'country', 'email', 'website', 'createdAt'],
|
||||||
filters: ['name', '_id', 'country', 'email'],
|
filters: ['name', '_id', 'country', 'email'],
|
||||||
sorters: ['name', 'country', 'email', 'createdAt', '_id'],
|
sorters: ['name', 'country', 'email', 'createdAt', '_id'],
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import PartStockInfo from '../components/Dashboard/Inventory/PartStocks/PartStoc
|
|||||||
import StockEvents from '../components/Dashboard/Inventory/StockEvents.jsx'
|
import StockEvents from '../components/Dashboard/Inventory/StockEvents.jsx'
|
||||||
import StockAudits from '../components/Dashboard/Inventory/StockAudits.jsx'
|
import StockAudits from '../components/Dashboard/Inventory/StockAudits.jsx'
|
||||||
import StockAuditInfo from '../components/Dashboard/Inventory/StockAudits/StockAuditInfo.jsx'
|
import StockAuditInfo from '../components/Dashboard/Inventory/StockAudits/StockAuditInfo.jsx'
|
||||||
|
import PurchaseOrders from '../components/Dashboard/Inventory/PurchaseOrders.jsx'
|
||||||
|
import PurchaseOrderInfo from '../components/Dashboard/Inventory/PurchaseOrders/PurchaseOrderInfo.jsx'
|
||||||
|
|
||||||
const InventoryRoutes = [
|
const InventoryRoutes = [
|
||||||
<Route
|
<Route
|
||||||
@ -43,6 +45,16 @@ const InventoryRoutes = [
|
|||||||
key='stockaudits-info'
|
key='stockaudits-info'
|
||||||
path='inventory/stockaudits/info'
|
path='inventory/stockaudits/info'
|
||||||
element={<StockAuditInfo />}
|
element={<StockAuditInfo />}
|
||||||
|
/>,
|
||||||
|
<Route
|
||||||
|
key='purchaseorders'
|
||||||
|
path='inventory/purchaseorders'
|
||||||
|
element={<PurchaseOrders />}
|
||||||
|
/>,
|
||||||
|
<Route
|
||||||
|
key='purchaseorders-info'
|
||||||
|
path='inventory/purchaseorders/info'
|
||||||
|
element={<PurchaseOrderInfo />}
|
||||||
/>
|
/>
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,10 @@ import ProductInfo from '../components/Dashboard/Management/Products/ProductInfo
|
|||||||
import Vendors from '../components/Dashboard/Management/Vendors'
|
import Vendors from '../components/Dashboard/Management/Vendors'
|
||||||
import VendorInfo from '../components/Dashboard/Management/Vendors/VendorInfo'
|
import VendorInfo from '../components/Dashboard/Management/Vendors/VendorInfo'
|
||||||
import Materials from '../components/Dashboard/Management/Materials'
|
import Materials from '../components/Dashboard/Management/Materials'
|
||||||
|
import Couriers from '../components/Dashboard/Management/Couriers'
|
||||||
|
import CourierInfo from '../components/Dashboard/Management/Couriers/CourierInfo.jsx'
|
||||||
|
import CourierServices from '../components/Dashboard/Management/CourierServices'
|
||||||
|
import CourierServiceInfo from '../components/Dashboard/Management/CourierServices/CourierServiceInfo.jsx'
|
||||||
import Settings from '../components/Dashboard/Management/Settings'
|
import Settings from '../components/Dashboard/Management/Settings'
|
||||||
import AuditLogs from '../components/Dashboard/Management/AuditLogs.jsx'
|
import AuditLogs from '../components/Dashboard/Management/AuditLogs.jsx'
|
||||||
import NoteTypes from '../components/Dashboard/Management/NoteTypes.jsx'
|
import NoteTypes from '../components/Dashboard/Management/NoteTypes.jsx'
|
||||||
@ -73,6 +77,22 @@ const ManagementRoutes = [
|
|||||||
element={<FileInfo />}
|
element={<FileInfo />}
|
||||||
/>,
|
/>,
|
||||||
<Route key='materials' path='management/materials' element={<Materials />} />,
|
<Route key='materials' path='management/materials' element={<Materials />} />,
|
||||||
|
<Route key='couriers' path='management/couriers' element={<Couriers />} />,
|
||||||
|
<Route
|
||||||
|
key='couriers-info'
|
||||||
|
path='management/couriers/info'
|
||||||
|
element={<CourierInfo />}
|
||||||
|
/>,
|
||||||
|
<Route
|
||||||
|
key='courierServices'
|
||||||
|
path='management/courierServices'
|
||||||
|
element={<CourierServices />}
|
||||||
|
/>,
|
||||||
|
<Route
|
||||||
|
key='courierServices-info'
|
||||||
|
path='management/courierServices/info'
|
||||||
|
element={<CourierServiceInfo />}
|
||||||
|
/>,
|
||||||
<Route key='notetypes' path='management/notetypes' element={<NoteTypes />} />,
|
<Route key='notetypes' path='management/notetypes' element={<NoteTypes />} />,
|
||||||
<Route
|
<Route
|
||||||
key='notetypes-info'
|
key='notetypes-info'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user