Compare commits
4 Commits
1882ec8e32
...
6870320ab4
| Author | SHA1 | Date | |
|---|---|---|---|
| 6870320ab4 | |||
| 6d1946b91a | |||
| 5ba205c6cc | |||
| cb9e7fcc23 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,3 +28,5 @@ yarn-error.log*
|
||||
/design_files/*.af~lock~
|
||||
|
||||
stats.html
|
||||
|
||||
test-results.xml
|
||||
13
assets/icons/filamentskuicon.svg
Normal file
13
assets/icons/filamentskuicon.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.011907,-0,-0,-0.011907,-0.017623,64)">
|
||||
<path d="M2168,4709C1894,4666 1703,4555 1616,4389C1580,4321 1580,4184 1616,4116C1686,3983 1795,3902 1990,3838C2374,3713 2898,3806 3075,4030C3139,4112 3155,4156 3155,4253C3155,4349 3139,4393 3075,4475C2930,4659 2521,4765 2168,4709ZM2531,4395C2672,4372 2780,4328 2829,4275C2848,4253 2848,4252 2829,4230C2762,4157 2554,4095 2373,4095C2192,4095 1984,4157 1917,4230C1898,4252 1898,4253 1917,4275C2010,4378 2296,4434 2531,4395Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M4413.328,0.003L3970,0C2805,0 2804,0 2641,56C2546.293,88.172 2456.943,133.018 2375.17,188.649L752.515,188.649C440.617,188.649 187.395,441.871 187.395,753.769L187.395,2042.949C187.395,2333.828 407.638,2573.672 690.318,2604.678C623.112,2767.055 626.105,2931.521 701,3087L731,3149L707,3196C648,3311 626,3466 653,3579L665,3631L598,3692C381,3894 287,4149 343,4391C403,4658 606,4880 958,5066C1270,5230 1637,5326 2121,5369C2228,5379 2689,5366 2814,5350C3439,5270 3943,5054 4214,4748C4509,4415 4483,4003 4148,3692L4081,3631L4093,3579C4120,3465 4098,3312 4038,3194C4014,3145 4014,3144 4033,3113C4078,3041 4100,2950 4100,2835C4100,2738.782 4090.9,2682.466 4058.642,2608.068L4625.617,2608.068C4937.515,2608.068 5190.736,2354.846 5190.736,2042.949L5190.736,753.769C5190.736,611.423 5137.993,481.298 5051,381.889L5051.021,155.223C5051.021,69.541 4981.4,0.003 4895.817,0.003L4413.328,0.003ZM1063.992,2608.068L1634.782,2608.068C1394.851,2668.367 1190.699,2752.015 1034,2856C966,2901 967,2901 960,2872C944,2809 979,2712 1045,2630C1050.866,2622.695 1057.204,2615.38 1063.992,2608.068ZM1078,3393C1025,3416 976,3438 970,3440C961,3444 960,3436 965,3411C1016,3186 1382,2969 1856,2883C2036,2850 2146,2841 2373,2841C2600,2841 2710,2850 2890,2883C3364,2969 3730,3186 3781,3411C3786,3436 3785,3444 3776,3440C3770,3438 3721,3416 3668,3393C3424,3286 3132,3213 2772,3169C2627,3151 2119,3151 1974,3169C1614,3213 1322,3286 1078,3393ZM4854.788,2042.949C4854.788,2169.432 4752.1,2272.12 4625.617,2272.12L752.515,2272.12C626.032,2272.12 523.344,2169.432 523.344,2042.949L523.344,753.769C523.344,627.286 626.032,524.597 752.515,524.597L4625.617,524.597C4752.1,524.597 4854.788,627.286 4854.788,753.769L4854.788,2042.949ZM2621,5050C1966,5103 1283,4947 897,4656C795,4579 733,4507 685,4411C650,4342 646,4324 646,4253C646,4146 685,4063 783,3960C1103,3621 1884,3416 2631,3476C3229,3524 3721,3702 3963,3960C4061,4063 4100,4146 4100,4253C4100,4324 4096,4342 4061,4411C4013,4507 3951,4579 3849,4656C3560,4873 3138,5009 2621,5050ZM3111.507,2608.068L3680.692,2608.068C3712.673,2642.169 3737.999,2677.689 3757,2715C3782,2766 3797,2857 3783,2881C3775,2896 3769,2895 3733,2870C3569.742,2759.218 3359.738,2670.745 3111.507,2608.068Z"/>
|
||||
<g transform="matrix(143.743961,-0,-0,-143.743961,-3687.186324,8190.967756)">
|
||||
<path d="M34.865,52.259C37.413,52.259 38.935,51.026 38.935,49.082C38.935,47.56 37.996,46.722 35.931,46.326L34.939,46.139C33.887,45.938 33.451,45.656 33.451,45.147C33.451,44.577 33.974,44.174 34.865,44.174C35.576,44.174 36.112,44.409 36.428,45.012C36.716,45.482 37.051,45.676 37.587,45.676C38.204,45.669 38.62,45.287 38.62,44.717C38.62,44.516 38.586,44.355 38.519,44.188C38.05,42.961 36.696,42.25 34.845,42.25C32.62,42.25 31.004,43.45 31.004,45.314C31.004,46.789 32.01,47.734 33.934,48.096L34.933,48.284C36.079,48.505 36.488,48.78 36.488,49.316C36.488,49.906 35.864,50.335 34.906,50.335C34.128,50.335 33.478,50.081 33.149,49.477C32.834,48.995 32.512,48.827 32.036,48.827C31.413,48.827 30.97,49.243 30.97,49.859C30.97,50.061 31.011,50.268 31.098,50.469C31.5,51.468 32.767,52.259 34.865,52.259Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M41.308,52.239C42.086,52.239 42.535,51.777 42.535,50.959L42.535,49.357L43.433,48.418L45.941,51.549C46.336,52.052 46.691,52.246 47.208,52.246C47.878,52.246 48.388,51.73 48.388,51.059C48.388,50.718 48.22,50.349 47.818,49.853L45.344,46.823L47.657,44.382C47.985,44.027 48.113,43.759 48.113,43.397C48.113,42.767 47.596,42.264 46.953,42.264C46.537,42.264 46.229,42.425 45.88,42.814L42.589,46.46L42.535,46.46L42.535,43.551C42.535,42.733 42.086,42.27 41.308,42.27C40.53,42.27 40.075,42.733 40.075,43.551L40.075,50.959C40.075,51.777 40.53,52.239 41.308,52.239Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M53.583,52.259C56.097,52.259 57.746,50.832 57.746,48.659L57.746,43.551C57.746,42.733 57.297,42.27 56.513,42.27C55.735,42.27 55.286,42.733 55.286,43.551L55.286,48.398C55.286,49.524 54.676,50.195 53.583,50.195C52.484,50.195 51.874,49.524 51.874,48.398L51.874,43.551C51.874,42.733 51.424,42.27 50.647,42.27C49.869,42.27 49.413,42.733 49.413,43.551L49.413,48.659C49.413,50.832 51.062,52.259 53.583,52.259Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
12
assets/icons/partskuicon.svg
Normal file
12
assets/icons/partskuicon.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?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(1,0,0,1,-0.5,-4)">
|
||||
<path d="M59.063,37.932C60.996,39.115 62.286,41.246 62.286,43.675L62.286,59.025C62.286,62.739 59.271,65.754 55.558,65.754L9.442,65.754C5.729,65.754 2.714,62.739 2.714,59.025L2.714,43.675C2.714,41.692 3.574,39.908 4.941,38.676L4.941,23.857C4.941,21.49 6.144,19.403 8.186,18.218L28.762,6.357C30.813,5.136 33.19,5.136 35.263,6.357L55.817,18.218C57.859,19.403 59.063,21.49 59.063,23.857L59.063,37.932ZM10.076,36.947L27.371,36.947L10.076,27.093L10.076,36.947ZM36.574,36.947L53.923,36.947L53.923,27.065L36.574,36.947ZM31.984,33.577L52.082,22.106C51.964,21.992 51.921,21.927 51.713,21.8L33.492,11.283C32.55,10.719 31.453,10.719 30.511,11.283L12.312,21.8C12.083,21.927 12.03,21.997 11.892,22.125L31.984,33.577ZM58.286,43.675C58.286,42.169 57.064,40.947 55.558,40.947L9.442,40.947C7.936,40.947 6.714,42.169 6.714,43.675L6.714,59.025C6.714,60.531 7.936,61.754 9.442,61.754L55.558,61.754C57.064,61.754 58.286,60.531 58.286,59.025L58.286,43.675Z"/>
|
||||
<g transform="matrix(1.711502,0,0,1.711502,-43.419476,-29.526577)">
|
||||
<path d="M34.865,52.259C37.413,52.259 38.935,51.026 38.935,49.082C38.935,47.56 37.996,46.722 35.931,46.326L34.939,46.139C33.887,45.938 33.451,45.656 33.451,45.147C33.451,44.577 33.974,44.174 34.865,44.174C35.576,44.174 36.112,44.409 36.428,45.012C36.716,45.482 37.051,45.676 37.587,45.676C38.204,45.669 38.62,45.287 38.62,44.717C38.62,44.516 38.586,44.355 38.519,44.188C38.05,42.961 36.696,42.25 34.845,42.25C32.62,42.25 31.004,43.45 31.004,45.314C31.004,46.789 32.01,47.734 33.934,48.096L34.933,48.284C36.079,48.505 36.488,48.78 36.488,49.316C36.488,49.906 35.864,50.335 34.906,50.335C34.128,50.335 33.478,50.081 33.149,49.477C32.834,48.995 32.512,48.827 32.036,48.827C31.413,48.827 30.97,49.243 30.97,49.859C30.97,50.061 31.011,50.268 31.098,50.469C31.5,51.468 32.767,52.259 34.865,52.259Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M41.308,52.239C42.086,52.239 42.535,51.777 42.535,50.959L42.535,49.357L43.433,48.418L45.941,51.549C46.336,52.052 46.691,52.246 47.208,52.246C47.878,52.246 48.388,51.73 48.388,51.059C48.388,50.718 48.22,50.349 47.818,49.853L45.344,46.823L47.657,44.382C47.985,44.027 48.113,43.759 48.113,43.397C48.113,42.767 47.596,42.264 46.953,42.264C46.537,42.264 46.229,42.425 45.88,42.814L42.589,46.46L42.535,46.46L42.535,43.551C42.535,42.733 42.086,42.27 41.308,42.27C40.53,42.27 40.075,42.733 40.075,43.551L40.075,50.959C40.075,51.777 40.53,52.239 41.308,52.239Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M53.583,52.259C56.097,52.259 57.746,50.832 57.746,48.659L57.746,43.551C57.746,42.733 57.297,42.27 56.513,42.27C55.735,42.27 55.286,42.733 55.286,43.551L55.286,48.398C55.286,49.524 54.676,50.195 53.583,50.195C52.484,50.195 51.874,49.524 51.874,48.398L51.874,43.551C51.874,42.733 51.424,42.27 50.647,42.27C49.869,42.27 49.413,42.733 49.413,43.551L49.413,48.659C49.413,50.832 51.062,52.259 53.583,52.259Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@ -2,11 +2,11 @@
|
||||
<!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.983114,0,0,0.983114,0.872652,-3.62694)">
|
||||
<path d="M17.414,66.503C16.713,66.492 16.015,66.297 15.368,65.918L3.47,59.042C2.153,58.292 1.364,56.929 1.364,55.39L1.364,41.653C1.364,40.114 2.153,38.751 3.47,37.993L15.368,31.125C15.408,31.101 15.448,31.079 15.488,31.057C15.484,30.981 15.482,30.906 15.482,30.83L15.482,17.094C15.482,15.554 16.271,14.192 17.588,13.433L29.487,6.565C30.819,5.777 32.374,5.777 33.714,6.565L45.605,13.433C46.922,14.192 47.71,15.554 47.71,17.094L47.71,30.83C47.71,30.882 47.71,30.934 47.708,30.986C47.794,31.029 47.879,31.075 47.963,31.125L59.854,37.993C61.171,38.751 61.96,40.114 61.96,41.653L61.96,60.463C61.96,63.797 59.253,66.504 55.919,66.504L17.543,66.503C17.5,66.504 17.457,66.504 17.414,66.503ZM56.328,40.656L56.5,40.558C56.447,40.512 56.431,40.489 56.316,40.42L46.646,34.839C46.14,34.532 45.551,34.532 45.045,34.839L35.382,40.42C35.26,40.489 35.229,40.52 35.145,40.589L35.239,40.643L55.919,40.643C56.057,40.643 56.193,40.647 56.328,40.656ZM27.985,40.643L28.117,40.567L17.928,34.679C17.513,34.548 17.07,34.601 16.678,34.839L7.015,40.42C6.892,40.489 6.862,40.52 6.777,40.589L15.655,45.672C16.137,42.819 18.622,40.643 21.612,40.643L27.985,40.643ZM57.891,46.683C57.891,45.595 57.008,44.711 55.919,44.711L21.612,44.711C20.523,44.711 19.64,45.595 19.64,46.683L19.64,60.463C19.64,61.551 20.523,62.435 21.612,62.435L55.919,62.435C57.008,62.435 57.891,61.551 57.891,60.463L57.891,46.683ZM29.663,36.94L29.663,25.531L19.341,19.62L19.341,30.103C19.341,30.57 19.539,30.987 19.878,31.288L29.663,36.94ZM33.53,36.986C33.721,36.887 33.805,36.833 34.005,36.718L43.047,31.489C43.553,31.19 43.844,30.685 43.844,30.103L43.844,19.574L33.53,25.478L33.53,36.986ZM31.554,22.132L42.251,15.999C42.197,15.953 42.182,15.93 42.067,15.861L32.397,10.279C31.891,9.973 31.302,9.973 30.796,10.279L21.133,15.861C21.011,15.93 20.98,15.96 20.896,16.029L31.554,22.132ZM15.545,61.546L15.545,50.091L5.223,44.18L5.223,54.662C5.223,55.244 5.529,55.75 6.035,56.048L15.261,61.385C15.391,61.462 15.414,61.477 15.545,61.546Z"/>
|
||||
<g transform="matrix(1.193399,0,0,1.216202,-14.746685,-3.63953)">
|
||||
<path d="M34.973,52.259C37.641,52.259 39.196,50.992 39.196,49.001C39.196,47.433 38.218,46.581 36.139,46.206L35.167,46.031C34.202,45.857 33.786,45.649 33.786,45.214C33.786,44.778 34.188,44.429 34.979,44.429C35.59,44.429 36.039,44.61 36.354,45.093C36.696,45.609 37.078,45.817 37.681,45.817C38.392,45.81 38.861,45.374 38.861,44.731C38.861,44.51 38.821,44.329 38.74,44.134C38.238,42.927 36.863,42.25 34.946,42.25C32.646,42.25 30.97,43.477 30.97,45.421C30.97,46.923 32.023,47.895 33.947,48.237L34.919,48.411C36.005,48.606 36.381,48.8 36.381,49.256C36.381,49.732 35.858,50.081 35.006,50.081C34.369,50.081 33.806,49.886 33.471,49.41C33.089,48.874 32.727,48.706 32.191,48.706C31.487,48.706 30.97,49.176 30.97,49.859C30.97,50.081 31.017,50.309 31.118,50.53C31.534,51.475 32.794,52.259 34.973,52.259Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M41.677,52.259C42.582,52.259 43.098,51.723 43.098,50.771L43.098,49.558L43.943,48.686L46.115,51.442C46.577,52.032 46.993,52.259 47.603,52.259C48.381,52.259 48.971,51.663 48.971,50.885C48.971,50.483 48.763,50.054 48.274,49.45L46.182,46.863L48.18,44.731C48.582,44.302 48.729,43.993 48.729,43.564C48.729,42.834 48.126,42.25 47.382,42.25C46.899,42.25 46.531,42.438 46.122,42.894L43.152,46.253L43.098,46.253L43.098,43.739C43.098,42.787 42.582,42.25 41.677,42.25C40.772,42.25 40.256,42.787 40.256,43.739L40.256,50.771C40.256,51.723 40.772,52.259 41.677,52.259Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M54.14,52.259C56.694,52.259 58.403,50.852 58.403,48.646L58.403,43.739C58.403,42.787 57.887,42.25 56.982,42.25C56.077,42.25 55.561,42.787 55.561,43.739L55.561,48.331C55.561,49.35 55.058,49.906 54.14,49.906C53.221,49.906 52.718,49.35 52.718,48.331L52.718,43.739C52.718,42.787 52.202,42.25 51.297,42.25C50.392,42.25 49.876,42.787 49.876,43.739L49.876,48.646C49.876,50.852 51.585,52.259 54.14,52.259Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M1.364,59.66L1.364,41.653C1.364,40.114 2.153,38.751 3.47,37.993L15.368,31.125C15.408,31.101 15.448,31.079 15.488,31.057C15.484,30.981 15.482,30.906 15.482,30.83L15.482,17.094C15.482,15.554 16.271,14.192 17.588,13.433L29.487,6.565C30.819,5.777 32.374,5.777 33.714,6.565L45.605,13.433C46.922,14.192 47.71,15.554 47.71,17.094L47.71,30.83C47.71,30.882 47.71,30.934 47.708,30.986C47.794,31.029 47.879,31.075 47.963,31.125L59.854,37.993C61.171,38.751 61.96,40.114 61.96,41.653L61.96,59.66C61.96,63.437 58.893,66.504 55.116,66.504L8.208,66.504C4.431,66.504 1.364,63.437 1.364,59.66ZM50.74,37.202L46.646,34.839C46.14,34.532 45.551,34.532 45.045,34.839L40.954,37.202L50.74,37.202ZM22.294,37.202L17.928,34.679C17.513,34.548 17.07,34.601 16.678,34.839L12.587,37.202L22.294,37.202ZM31.554,22.132L42.251,15.999C42.197,15.953 42.182,15.93 42.067,15.861L32.397,10.279C31.891,9.973 31.302,9.973 30.796,10.279L21.133,15.861C21.011,15.93 20.98,15.96 20.896,16.029L31.554,22.132ZM29.663,36.94L29.663,25.531L19.341,19.62L19.341,30.103C19.341,30.57 19.539,30.987 19.878,31.288L29.663,36.94ZM33.53,36.986C33.721,36.887 33.805,36.833 34.005,36.718L43.047,31.489C43.553,31.19 43.844,30.685 43.844,30.103L43.844,19.574L33.53,25.478L33.53,36.986ZM57.891,44.046C57.891,42.514 56.648,41.271 55.116,41.271L8.208,41.271C6.676,41.271 5.433,42.514 5.433,44.046L5.433,59.66C5.433,61.191 6.676,62.435 8.208,62.435L55.116,62.435C56.648,62.435 57.891,61.191 57.891,59.66L57.891,44.046Z"/>
|
||||
<g transform="matrix(1.740899,0,0,1.740899,-45.561479,-30.413194)">
|
||||
<path d="M34.865,52.259C37.413,52.259 38.935,51.026 38.935,49.082C38.935,47.56 37.996,46.722 35.931,46.326L34.939,46.139C33.887,45.938 33.451,45.656 33.451,45.147C33.451,44.577 33.974,44.174 34.865,44.174C35.576,44.174 36.112,44.409 36.428,45.012C36.716,45.482 37.051,45.676 37.587,45.676C38.204,45.669 38.62,45.287 38.62,44.717C38.62,44.516 38.586,44.355 38.519,44.188C38.05,42.961 36.696,42.25 34.845,42.25C32.62,42.25 31.004,43.45 31.004,45.314C31.004,46.789 32.01,47.734 33.934,48.096L34.933,48.284C36.079,48.505 36.488,48.78 36.488,49.316C36.488,49.906 35.864,50.335 34.906,50.335C34.128,50.335 33.478,50.081 33.149,49.477C32.834,48.995 32.512,48.827 32.036,48.827C31.413,48.827 30.97,49.243 30.97,49.859C30.97,50.061 31.011,50.268 31.098,50.469C31.5,51.468 32.767,52.259 34.865,52.259Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M41.308,52.239C42.086,52.239 42.535,51.777 42.535,50.959L42.535,49.357L43.433,48.418L45.941,51.549C46.336,52.052 46.691,52.246 47.208,52.246C47.878,52.246 48.388,51.73 48.388,51.059C48.388,50.718 48.22,50.349 47.818,49.853L45.344,46.823L47.657,44.382C47.985,44.027 48.113,43.759 48.113,43.397C48.113,42.767 47.596,42.264 46.953,42.264C46.537,42.264 46.229,42.425 45.88,42.814L42.589,46.46L42.535,46.46L42.535,43.551C42.535,42.733 42.086,42.27 41.308,42.27C40.53,42.27 40.075,42.733 40.075,43.551L40.075,50.959C40.075,51.777 40.53,52.239 41.308,52.239Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M53.583,52.259C56.097,52.259 57.746,50.832 57.746,48.659L57.746,43.551C57.746,42.733 57.297,42.27 56.513,42.27C55.735,42.27 55.286,42.733 55.286,43.551L55.286,48.398C55.286,49.524 54.676,50.195 53.583,50.195C52.484,50.195 51.874,49.524 51.874,48.398L51.874,43.551C51.874,42.733 51.424,42.27 50.647,42.27C49.869,42.27 49.413,42.733 49.413,43.551L49.413,48.659C49.413,50.832 51.062,52.259 53.583,52.259Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.9 KiB |
@ -46,6 +46,34 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='orderItem'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
sku: true,
|
||||
shipment: true,
|
||||
invoicedAmount: false,
|
||||
invoicedAmountWithTax: false,
|
||||
invoicedQuantity: false,
|
||||
invoicedAmountRemaining: false,
|
||||
invoicedAmountWithTaxRemaining: false,
|
||||
invoicedQuantityRemaining: false,
|
||||
orderedAt: false,
|
||||
receivedAt: false,
|
||||
syncAmount: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Pricing',
|
||||
key: 'pricing',
|
||||
@ -67,32 +95,6 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='orderItem'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
shipment: true,
|
||||
invoicedAmount: false,
|
||||
invoicedAmountWithTax: false,
|
||||
invoicedQuantity: false,
|
||||
invoicedAmountRemaining: false,
|
||||
invoicedAmountWithTaxRemaining: false,
|
||||
invoicedQuantityRemaining: false,
|
||||
orderedAt: false,
|
||||
receivedAt: false,
|
||||
syncAmount: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
|
||||
104
src/components/Dashboard/Management/FilamentSkus.jsx
Normal file
104
src/components/Dashboard/Management/FilamentSkus.jsx
Normal file
@ -0,0 +1,104 @@
|
||||
import { useState, useRef } from 'react'
|
||||
|
||||
import { Button, Flex, Space, Modal, Dropdown } from 'antd'
|
||||
|
||||
import NewFilamentSku from './FilamentSkus/NewFilamentSku'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import ListIcon from '../../Icons/ListIcon'
|
||||
import GridIcon from '../../Icons/GridIcon'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
import ColumnViewButton from '../common/ColumnViewButton'
|
||||
import ExportListButton from '../common/ExportListButton'
|
||||
|
||||
const FilamentSkus = () => {
|
||||
const tableRef = useRef()
|
||||
|
||||
const [newFilamentSkuOpen, setNewFilamentSkuOpen] = useState(false)
|
||||
|
||||
const [viewMode, setViewMode] = useViewMode('filamentSkus')
|
||||
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
useColumnVisibility('filamentSku')
|
||||
|
||||
const actionItems = {
|
||||
items: [
|
||||
{
|
||||
label: 'New Filament SKU',
|
||||
key: 'newFilamentSku',
|
||||
icon: <PlusIcon />
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
label: 'Reload List',
|
||||
key: 'reloadList',
|
||||
icon: <ReloadIcon />
|
||||
}
|
||||
],
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'reloadList') {
|
||||
tableRef.current?.reload()
|
||||
} else if (key === 'newFilamentSku') {
|
||||
setNewFilamentSkuOpen(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large'>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
<ColumnViewButton
|
||||
type='filamentSku'
|
||||
loading={false}
|
||||
visibleState={columnVisibility}
|
||||
updateVisibleState={setColumnVisibility}
|
||||
/>
|
||||
<ExportListButton objectType='filamentSku' />
|
||||
</Space>
|
||||
<Space>
|
||||
<Button
|
||||
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||
onClick={() =>
|
||||
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
visibleColumns={columnVisibility}
|
||||
type='filamentSku'
|
||||
cards={viewMode === 'cards'}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
open={newFilamentSkuOpen}
|
||||
styles={{ content: { paddingBottom: '24px' } }}
|
||||
footer={null}
|
||||
width={600}
|
||||
onCancel={() => {
|
||||
setNewFilamentSkuOpen(false)
|
||||
}}
|
||||
destroyOnHidden={true}
|
||||
>
|
||||
<NewFilamentSku
|
||||
onOk={() => {
|
||||
setNewFilamentSkuOpen(false)
|
||||
tableRef.current?.reload()
|
||||
}}
|
||||
reset={newFilamentSkuOpen}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FilamentSkus
|
||||
@ -0,0 +1,197 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const FilamentSkuInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const filamentSkuId = new URLSearchParams(location.search).get('filamentSkuId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'FilamentSkuInfo',
|
||||
{
|
||||
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?.fetchObject?.()
|
||||
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='filamentSku'
|
||||
id={filamentSkuId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Filament SKU Information' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<UserNotifierToggle
|
||||
type='filamentSku'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='filamentSku'
|
||||
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='Filament SKU Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={filamentSkuId}
|
||||
type='filamentSku'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='filamentSku'
|
||||
objectData={objectData}
|
||||
/>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={filamentSkuId} type='filamentSku' />
|
||||
</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': filamentSkuId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FilamentSkuInfo
|
||||
@ -0,0 +1,123 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import NewObjectForm from '../../common/NewObjectForm'
|
||||
import WizardView from '../../common/WizardView'
|
||||
|
||||
const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
return (
|
||||
<NewObjectForm
|
||||
type='filamentSku'
|
||||
reset={reset}
|
||||
defaultValues={defaultValues}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
labelWidth={70}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
description: false,
|
||||
cost: false,
|
||||
costWithTax: false,
|
||||
costTaxRate: false,
|
||||
vendor: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Color & Cost',
|
||||
key: 'colorCost',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
barcode: false,
|
||||
filament: false,
|
||||
name: false,
|
||||
description: false
|
||||
}}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
barcode: true,
|
||||
description: true
|
||||
}}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
visibleProperties={{
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
_id: false
|
||||
}}
|
||||
labelWidth={100}
|
||||
bordered={false}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
return (
|
||||
<WizardView
|
||||
steps={steps}
|
||||
loading={submitLoading}
|
||||
formValid={formValid}
|
||||
title='New Filament SKU'
|
||||
onSubmit={async () => {
|
||||
const result = await handleSubmit()
|
||||
if (result) {
|
||||
onOk()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NewObjectForm>
|
||||
)
|
||||
}
|
||||
|
||||
NewFilamentSku.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
reset: PropTypes.bool,
|
||||
defaultValues: PropTypes.object
|
||||
}
|
||||
|
||||
export default NewFilamentSku
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config'
|
||||
@ -20,9 +20,11 @@ import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import FilamentIcon from '../../../Icons/FilamentIcon.jsx'
|
||||
import FilamentSkuIcon from '../../../Icons/FilamentSkuIcon.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import NewFilamentSku from '../FilamentSkus/NewFilamentSku'
|
||||
|
||||
const log = loglevel.getLogger('FilamentInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -32,10 +34,13 @@ const FilamentInfo = () => {
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const filamentId = new URLSearchParams(location.search).get('filamentId')
|
||||
const [newFilamentSkuOpen, setNewFilamentSkuOpen] = useState(false)
|
||||
const filamentSkusTableRef = useRef()
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'FilamentInfo',
|
||||
{
|
||||
info: true,
|
||||
filamentSkus: true,
|
||||
stocks: true,
|
||||
notes: true,
|
||||
auditLogs: true
|
||||
@ -56,6 +61,10 @@ const FilamentInfo = () => {
|
||||
objectFormRef?.current?.fetchObject?.()
|
||||
return true
|
||||
},
|
||||
newFilamentSku: () => {
|
||||
setNewFilamentSkuOpen(true)
|
||||
return false
|
||||
},
|
||||
edit: () => {
|
||||
objectFormRef?.current?.startEditing?.()
|
||||
return false
|
||||
@ -93,6 +102,7 @@ const FilamentInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Filament Information' },
|
||||
{ key: 'filamentSkus', label: 'Filament SKUs' },
|
||||
{ key: 'stocks', label: 'Filament Stocks' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
@ -171,6 +181,27 @@ const FilamentInfo = () => {
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
|
||||
<InfoCollapse
|
||||
title='Filament SKUs'
|
||||
icon={<FilamentSkuIcon />}
|
||||
active={collapseState.filamentSkus}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('filamentSkus', expanded)
|
||||
}
|
||||
collapseKey='filamentSkus'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
ref={filamentSkusTableRef}
|
||||
type='filamentSku'
|
||||
masterFilter={{ filament: filamentId }}
|
||||
visibleColumns={{ filament: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Filament Stocks'
|
||||
icon={<FilamentIcon />}
|
||||
@ -183,16 +214,36 @@ const FilamentInfo = () => {
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='filamentStock'
|
||||
masterFilter={{ 'filament._id': filamentId }}
|
||||
masterFilter={{ 'filamentSku.filament._id': filamentId }}
|
||||
visibleColumns={{
|
||||
filament: false,
|
||||
'filament._id': false,
|
||||
filamentSku: false,
|
||||
'filamentSku.filament._id': false,
|
||||
startingWeight: false
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<Modal
|
||||
open={newFilamentSkuOpen}
|
||||
styles={{ content: { paddingBottom: '24px' } }}
|
||||
footer={null}
|
||||
width={600}
|
||||
onCancel={() => setNewFilamentSkuOpen(false)}
|
||||
destroyOnClose
|
||||
>
|
||||
<NewFilamentSku
|
||||
onOk={() => {
|
||||
setNewFilamentSkuOpen(false)
|
||||
filamentSkusTableRef.current?.reload?.()
|
||||
}}
|
||||
reset={newFilamentSkuOpen}
|
||||
defaultValues={{
|
||||
filament: filamentId ? { _id: filamentId } : undefined
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import DashboardSidebar from '../common/DashboardSidebar'
|
||||
import FilamentIcon from '../../Icons/FilamentIcon'
|
||||
import FilamentSkuIcon from '../../Icons/FilamentSkuIcon'
|
||||
import PartIcon from '../../Icons/PartIcon'
|
||||
import PartSkuIcon from '../../Icons/PartSkuIcon'
|
||||
import ProductIcon from '../../Icons/ProductIcon'
|
||||
import ProductSkuIcon from '../../Icons/ProductSkuIcon'
|
||||
import VendorIcon from '../../Icons/VendorIcon'
|
||||
@ -31,12 +33,24 @@ const items = [
|
||||
label: 'Filaments',
|
||||
path: '/dashboard/management/filaments'
|
||||
},
|
||||
{
|
||||
key: 'filamentSkus',
|
||||
icon: <FilamentSkuIcon />,
|
||||
label: 'Filament SKUs',
|
||||
path: '/dashboard/management/filamentskus'
|
||||
},
|
||||
{
|
||||
key: 'parts',
|
||||
icon: <PartIcon />,
|
||||
label: 'Parts',
|
||||
path: '/dashboard/management/parts'
|
||||
},
|
||||
{
|
||||
key: 'partSkus',
|
||||
icon: <PartSkuIcon />,
|
||||
label: 'Part SKUs',
|
||||
path: '/dashboard/management/partskus'
|
||||
},
|
||||
{
|
||||
key: 'products',
|
||||
icon: <ProductIcon />,
|
||||
@ -179,7 +193,9 @@ if (import.meta.env.MODE === 'development') {
|
||||
|
||||
const routeKeyMap = {
|
||||
'/dashboard/management/filaments': 'filaments',
|
||||
'/dashboard/management/filamentskus': 'filamentSkus',
|
||||
'/dashboard/management/parts': 'parts',
|
||||
'/dashboard/management/partskus': 'partSkus',
|
||||
'/dashboard/management/users': 'users',
|
||||
'/dashboard/management/apppasswords': 'appPasswords',
|
||||
'/dashboard/management/products': 'products',
|
||||
|
||||
@ -1,218 +1,26 @@
|
||||
// src/materials.js
|
||||
|
||||
import { useEffect, useState, useContext, useCallback } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
Table,
|
||||
Button,
|
||||
Flex,
|
||||
Space,
|
||||
Modal,
|
||||
Dropdown,
|
||||
Spin
|
||||
} from 'antd'
|
||||
import { createStyles } from 'antd-style'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { useMessageContext } from '../context/MessageContext'
|
||||
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
import { useRef, useState } from 'react'
|
||||
import { Button, Flex, Space, Modal, Dropdown } from 'antd'
|
||||
|
||||
import NewMaterial from './Materials/NewMaterial'
|
||||
import IdDisplay from '../common/IdDisplay'
|
||||
import MaterialIcon from '../../Icons/MaterialIcon'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import ColumnViewButton from '../common/ColumnViewButton'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
|
||||
import config from '../../../config'
|
||||
|
||||
const useStyle = createStyles(({ css, token }) => {
|
||||
const { antCls } = token
|
||||
return {
|
||||
customTable: css`
|
||||
${antCls}-table {
|
||||
${antCls}-table-container {
|
||||
${antCls}-table-body,
|
||||
${antCls}-table-content {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #eaeaea transparent;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
})
|
||||
import ListIcon from '../../Icons/ListIcon'
|
||||
import GridIcon from '../../Icons/GridIcon'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
import ExportListButton from '../common/ExportListButton'
|
||||
|
||||
const Materials = () => {
|
||||
const { showError } = useMessageContext()
|
||||
const navigate = useNavigate()
|
||||
const { styles } = useStyle()
|
||||
|
||||
const [materialsData, setMaterialsData] = useState([])
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [lazyLoading, setLazyLoading] = useState(false)
|
||||
const [newMaterialOpen, setNewMaterialOpen] = useState(false)
|
||||
const tableRef = useRef()
|
||||
|
||||
const { authenticated } = useContext(AuthContext)
|
||||
const [viewMode, setViewMode] = useViewMode('material')
|
||||
|
||||
const fetchMaterialsData = useCallback(
|
||||
async (pageNum = 1, append = false) => {
|
||||
try {
|
||||
const response = await axios.get(`${config.backendUrl}/materials`, {
|
||||
params: {
|
||||
page: pageNum,
|
||||
limit: 25
|
||||
},
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
withCredentials: true
|
||||
})
|
||||
|
||||
const newData = response.data
|
||||
setHasMore(newData.length === 25) // If we get less than 25 items, we've reached the end
|
||||
|
||||
if (append) {
|
||||
setMaterialsData((prev) => [...prev, ...newData])
|
||||
} else {
|
||||
setMaterialsData(newData)
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
setLazyLoading(false)
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
showError(
|
||||
`Error updating material details: ${error.response.status}`
|
||||
)
|
||||
} else {
|
||||
showError(
|
||||
'An unexpected error occurred. Please try again later.'
|
||||
)
|
||||
}
|
||||
setLoading(false)
|
||||
setLazyLoading(false)
|
||||
}
|
||||
},
|
||||
[showError]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (authenticated) {
|
||||
fetchMaterialsData()
|
||||
}
|
||||
}, [authenticated, fetchMaterialsData])
|
||||
|
||||
const handleScroll = useCallback(
|
||||
(e) => {
|
||||
const { target } = e
|
||||
const scrollHeight = target.scrollHeight
|
||||
const scrollTop = target.scrollTop
|
||||
const clientHeight = target.clientHeight
|
||||
|
||||
// If we're near the bottom (within 100px) and not currently loading
|
||||
if (
|
||||
scrollHeight - scrollTop - clientHeight < 100 &&
|
||||
!lazyLoading &&
|
||||
hasMore
|
||||
) {
|
||||
setLazyLoading(true)
|
||||
const nextPage = page + 1
|
||||
setPage(nextPage)
|
||||
fetchMaterialsData(nextPage, true)
|
||||
}
|
||||
},
|
||||
[page, lazyLoading, hasMore, fetchMaterialsData]
|
||||
)
|
||||
|
||||
const getMaterialActionItems = (id) => {
|
||||
return {
|
||||
items: [
|
||||
{
|
||||
label: 'Info',
|
||||
key: 'info',
|
||||
icon: <InfoCircleIcon />
|
||||
}
|
||||
],
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'info') {
|
||||
navigate(`/dashboard/management/materials/info?materialId=${id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: '',
|
||||
key: 'icon',
|
||||
width: 40,
|
||||
fixed: 'left',
|
||||
render: () => <MaterialIcon></MaterialIcon>
|
||||
},
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 200,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: '_id',
|
||||
key: 'id',
|
||||
width: 180,
|
||||
render: (text) => <IdDisplay id={text} type={'material'} longId={false} />
|
||||
},
|
||||
{
|
||||
title: 'Category',
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: 'Created At',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
render: (createdAt) => {
|
||||
if (createdAt) {
|
||||
return <TimeDisplay dateTime={createdAt} />
|
||||
} else {
|
||||
return 'n/a'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
render: (text, record) => {
|
||||
return (
|
||||
<Space gap='small'>
|
||||
<Button
|
||||
icon={<InfoCircleIcon />}
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/dashboard/management/materials/info?materialId=${record._id}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Dropdown menu={getMaterialActionItems(record._id)}>
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
useColumnVisibility('material')
|
||||
|
||||
const actionItems = {
|
||||
items: [
|
||||
@ -230,7 +38,7 @@ const Materials = () => {
|
||||
],
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'reloadList') {
|
||||
fetchMaterialsData()
|
||||
tableRef.current?.reload()
|
||||
} else if (key === 'newMaterial') {
|
||||
setNewMaterialOpen(true)
|
||||
}
|
||||
@ -240,30 +48,38 @@ const Materials = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large'>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
<Table
|
||||
dataSource={materialsData}
|
||||
className={styles.customTable}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
rowKey='_id'
|
||||
loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
|
||||
scroll={{ y: 'calc(100vh - 270px)' }}
|
||||
onScroll={handleScroll}
|
||||
<ColumnViewButton
|
||||
type='material'
|
||||
loading={false}
|
||||
visibleState={columnVisibility}
|
||||
updateVisibleState={setColumnVisibility}
|
||||
/>
|
||||
{lazyLoading && (
|
||||
<div style={{ textAlign: 'center', padding: '10px' }}>
|
||||
<Spin indicator={<LoadingOutlined spin />} />
|
||||
</div>
|
||||
)}
|
||||
<ExportListButton objectType='material' />
|
||||
</Space>
|
||||
<Space>
|
||||
<Button
|
||||
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||
onClick={() =>
|
||||
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
type='material'
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
open={newMaterialOpen}
|
||||
styles={{ content: { paddingBottom: '24px' } }}
|
||||
footer={null}
|
||||
width={700}
|
||||
onCancel={() => {
|
||||
@ -271,12 +87,14 @@ const Materials = () => {
|
||||
}}
|
||||
>
|
||||
<NewMaterial
|
||||
onSuccess={() => {
|
||||
onOk={() => {
|
||||
setNewMaterialOpen(false)
|
||||
fetchMaterialsData()
|
||||
tableRef.current?.reload()
|
||||
}}
|
||||
reset={newMaterialOpen}
|
||||
/>
|
||||
</Modal>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
199
src/components/Dashboard/Management/Materials/MaterialInfo.jsx
Normal file
199
src/components/Dashboard/Management/Materials/MaterialInfo.jsx
Normal file
@ -0,0 +1,199 @@
|
||||
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 UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const log = loglevel.getLogger('MaterialInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
|
||||
const MaterialInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const materialId = new URLSearchParams(location.search).get('materialId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('MaterialInfo', {
|
||||
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='material'
|
||||
id={materialId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Material Information' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<UserNotifierToggle
|
||||
type='material'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='material'
|
||||
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='Material Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={materialId}
|
||||
type='material'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='material'
|
||||
objectData={objectData}
|
||||
/>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={materialId} type='material' />
|
||||
</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': materialId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialInfo
|
||||
@ -61,9 +61,11 @@ const NewMaterial = ({ onOk }) => {
|
||||
loading={submitLoading}
|
||||
formValid={formValid}
|
||||
title='New Material'
|
||||
onSubmit={() => {
|
||||
handleSubmit()
|
||||
onSubmit={async () => {
|
||||
const result = await handleSubmit()
|
||||
if (result) {
|
||||
onOk()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
104
src/components/Dashboard/Management/PartSkus.jsx
Normal file
104
src/components/Dashboard/Management/PartSkus.jsx
Normal file
@ -0,0 +1,104 @@
|
||||
import { useState, useRef } from 'react'
|
||||
|
||||
import { Button, Flex, Space, Modal, Dropdown } from 'antd'
|
||||
|
||||
import NewPartSku from './PartSkus/NewPartSku'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import ListIcon from '../../Icons/ListIcon'
|
||||
import GridIcon from '../../Icons/GridIcon'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
import ColumnViewButton from '../common/ColumnViewButton'
|
||||
import ExportListButton from '../common/ExportListButton'
|
||||
|
||||
const PartSkus = () => {
|
||||
const tableRef = useRef()
|
||||
|
||||
const [newPartSkuOpen, setNewPartSkuOpen] = useState(false)
|
||||
|
||||
const [viewMode, setViewMode] = useViewMode('partSkus')
|
||||
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
useColumnVisibility('partSku')
|
||||
|
||||
const actionItems = {
|
||||
items: [
|
||||
{
|
||||
label: 'New Part SKU',
|
||||
key: 'newPartSku',
|
||||
icon: <PlusIcon />
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
label: 'Reload List',
|
||||
key: 'reloadList',
|
||||
icon: <ReloadIcon />
|
||||
}
|
||||
],
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'reloadList') {
|
||||
tableRef.current?.reload()
|
||||
} else if (key === 'newPartSku') {
|
||||
setNewPartSkuOpen(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large'>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
<ColumnViewButton
|
||||
type='partSku'
|
||||
loading={false}
|
||||
visibleState={columnVisibility}
|
||||
updateVisibleState={setColumnVisibility}
|
||||
/>
|
||||
<ExportListButton objectType='partSku' />
|
||||
</Space>
|
||||
<Space>
|
||||
<Button
|
||||
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||
onClick={() =>
|
||||
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
visibleColumns={columnVisibility}
|
||||
type='partSku'
|
||||
cards={viewMode === 'cards'}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
open={newPartSkuOpen}
|
||||
styles={{ content: { paddingBottom: '24px' } }}
|
||||
footer={null}
|
||||
width={600}
|
||||
onCancel={() => {
|
||||
setNewPartSkuOpen(false)
|
||||
}}
|
||||
destroyOnHidden={true}
|
||||
>
|
||||
<NewPartSku
|
||||
onOk={() => {
|
||||
setNewPartSkuOpen(false)
|
||||
tableRef.current?.reload()
|
||||
}}
|
||||
reset={newPartSkuOpen}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PartSkus
|
||||
129
src/components/Dashboard/Management/PartSkus/NewPartSku.jsx
Normal file
129
src/components/Dashboard/Management/PartSkus/NewPartSku.jsx
Normal file
@ -0,0 +1,129 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import NewObjectForm from '../../common/NewObjectForm'
|
||||
import WizardView from '../../common/WizardView'
|
||||
|
||||
const NewPartSku = ({ onOk, reset, defaultValues }) => {
|
||||
return (
|
||||
<NewObjectForm
|
||||
type='partSku'
|
||||
reset={reset}
|
||||
defaultValues={defaultValues}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='partSku'
|
||||
column={1}
|
||||
labelWidth={70}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
description: false,
|
||||
priceMode: false,
|
||||
cost: false,
|
||||
costWithTax: false,
|
||||
costTaxRate: false,
|
||||
price: false,
|
||||
priceWithTax: false,
|
||||
margin: false,
|
||||
amount: false,
|
||||
priceTaxRate: false,
|
||||
vendor: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Pricing',
|
||||
key: 'pricing',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='partSku'
|
||||
column={1}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
barcode: false,
|
||||
part: false,
|
||||
name: false,
|
||||
description: false
|
||||
}}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='partSku'
|
||||
column={1}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
barcode: true,
|
||||
description: true
|
||||
}}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='partSku'
|
||||
column={1}
|
||||
visibleProperties={{
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
_id: false
|
||||
}}
|
||||
labelWidth={100}
|
||||
bordered={false}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
return (
|
||||
<WizardView
|
||||
steps={steps}
|
||||
loading={submitLoading}
|
||||
formValid={formValid}
|
||||
title='New Part SKU'
|
||||
onSubmit={async () => {
|
||||
const result = await handleSubmit()
|
||||
if (result) {
|
||||
onOk()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NewObjectForm>
|
||||
)
|
||||
}
|
||||
|
||||
NewPartSku.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
reset: PropTypes.bool,
|
||||
defaultValues: PropTypes.object
|
||||
}
|
||||
|
||||
export default NewPartSku
|
||||
194
src/components/Dashboard/Management/PartSkus/PartSkuInfo.jsx
Normal file
194
src/components/Dashboard/Management/PartSkus/PartSkuInfo.jsx
Normal file
@ -0,0 +1,194 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const PartSkuInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const partSkuId = new URLSearchParams(location.search).get('partSkuId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('PartSkuInfo', {
|
||||
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?.fetchObject?.()
|
||||
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='partSku'
|
||||
id={partSkuId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Part SKU Information' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<UserNotifierToggle
|
||||
type='partSku'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='partSku'
|
||||
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='Part SKU Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={partSkuId}
|
||||
type='partSku'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='partSku'
|
||||
objectData={objectData}
|
||||
/>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={partSkuId} type='partSku' />
|
||||
</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': partSkuId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PartSkuInfo
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
@ -19,15 +19,19 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import PartSkuIcon from '../../../Icons/PartSkuIcon.jsx'
|
||||
import NewPartSku from '../PartSkus/NewPartSku'
|
||||
|
||||
const PartInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const partId = new URLSearchParams(location.search).get('partId')
|
||||
const [newPartSkuOpen, setNewPartSkuOpen] = useState(false)
|
||||
const partSkusTableRef = useRef()
|
||||
const [collapseState, updateCollapseState] = useCollapseState('PartInfo', {
|
||||
info: true,
|
||||
parts: true,
|
||||
partSkus: true,
|
||||
notes: true,
|
||||
auditLogs: true
|
||||
})
|
||||
@ -45,6 +49,10 @@ const PartInfo = () => {
|
||||
objectFormRef?.current?.fetchObject?.()
|
||||
return true
|
||||
},
|
||||
newPartSku: () => {
|
||||
setNewPartSkuOpen(true)
|
||||
return false
|
||||
},
|
||||
edit: () => {
|
||||
objectFormRef?.current?.startEditing?.()
|
||||
return false
|
||||
@ -79,6 +87,7 @@ const PartInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Part Information' },
|
||||
{ key: 'partSkus', label: 'Part SKUs' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
@ -123,13 +132,6 @@ const PartInfo = () => {
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Part Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={partId}
|
||||
@ -141,16 +143,62 @@ const PartInfo = () => {
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<InfoCollapse
|
||||
title='Part Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='part'
|
||||
objectData={objectData}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
)}
|
||||
</ObjectForm>
|
||||
<InfoCollapse
|
||||
title='Part SKUs'
|
||||
icon={<PartSkuIcon />}
|
||||
active={collapseState.partSkus}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('partSkus', expanded)
|
||||
}
|
||||
collapseKey='partSkus'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
ref={partSkusTableRef}
|
||||
type='partSku'
|
||||
masterFilter={{ part: partId }}
|
||||
visibleColumns={{ part: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
<Modal
|
||||
open={newPartSkuOpen}
|
||||
styles={{ content: { paddingBottom: '24px' } }}
|
||||
footer={null}
|
||||
width={600}
|
||||
onCancel={() => setNewPartSkuOpen(false)}
|
||||
destroyOnClose
|
||||
>
|
||||
<NewPartSku
|
||||
onOk={() => {
|
||||
setNewPartSkuOpen(false)
|
||||
partSkusTableRef.current?.reload?.()
|
||||
}}
|
||||
reset={newPartSkuOpen}
|
||||
defaultValues={{
|
||||
part: partId ? { _id: partId } : undefined
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
|
||||
@ -24,6 +24,77 @@ const NewProductSku = ({ onOk, reset, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
description: false,
|
||||
priceMode: false,
|
||||
cost: false,
|
||||
costWithTax: false,
|
||||
costTaxRate: false,
|
||||
price: false,
|
||||
priceWithTax: false,
|
||||
margin: false,
|
||||
amount: false,
|
||||
priceTaxRate: false,
|
||||
vendor: false,
|
||||
parts: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Pricing',
|
||||
key: 'pricing',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='productSku'
|
||||
column={1}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
barcode: false,
|
||||
product: false,
|
||||
name: false,
|
||||
description: false,
|
||||
parts: false
|
||||
}}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Parts',
|
||||
key: 'parts',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='productSku'
|
||||
column={1}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
barcode: false,
|
||||
product: false,
|
||||
name: false,
|
||||
description: false,
|
||||
priceMode: false,
|
||||
cost: false,
|
||||
costWithTax: false,
|
||||
costTaxRate: false,
|
||||
price: false,
|
||||
priceWithTax: false,
|
||||
margin: false,
|
||||
amount: false,
|
||||
priceTaxRate: false,
|
||||
vendor: false
|
||||
}}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -36,10 +107,12 @@ const NewProductSku = ({ onOk, reset, defaultValues }) => {
|
||||
column={1}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
barcode: true,
|
||||
description: true
|
||||
}}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -58,6 +131,7 @@ const NewProductSku = ({ onOk, reset, defaultValues }) => {
|
||||
labelWidth={100}
|
||||
bordered={false}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -19,17 +19,24 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||
import PartIcon from '../../../Icons/PartIcon.jsx'
|
||||
|
||||
const ProductSkuInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const productSkuId = new URLSearchParams(location.search).get('productSkuId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('ProductSkuInfo', {
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'ProductSkuInfo',
|
||||
{
|
||||
info: true,
|
||||
parts: true,
|
||||
notes: true,
|
||||
auditLogs: true
|
||||
})
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
@ -82,6 +89,7 @@ const ProductSkuInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Product SKU Information' },
|
||||
{ key: 'parts', label: 'SKU Parts' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
@ -144,16 +152,36 @@ const ProductSkuInfo = () => {
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<>
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='productSku'
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
parts: false
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='SKU Parts'
|
||||
icon={<PartIcon />}
|
||||
active={collapseState.parts}
|
||||
onToggle={(expanded) => updateCollapseState('parts', expanded)}
|
||||
collapseKey='parts'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('productSku', 'parts')}
|
||||
isEditing={objectFormState.isEditing}
|
||||
objectData={objectFormState.objectData}
|
||||
loading={objectFormState.loading}
|
||||
size='medium'
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
|
||||
@ -20,9 +20,6 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||
import PartIcon from '../../../Icons/PartIcon.jsx'
|
||||
import ProductSkuIcon from '../../../Icons/ProductSkuIcon.jsx'
|
||||
import NewProductSku from '../ProductSkus/NewProductSku'
|
||||
|
||||
@ -33,7 +30,6 @@ const ProductInfo = () => {
|
||||
const productId = new URLSearchParams(location.search).get('productId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('ProductInfo', {
|
||||
info: true,
|
||||
parts: true,
|
||||
productSkus: true,
|
||||
notes: true,
|
||||
auditLogs: true
|
||||
@ -92,7 +88,6 @@ const ProductInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Product Information' },
|
||||
{ key: 'parts', label: 'Product Parts' },
|
||||
{ key: 'productSkus', label: 'Product SKUs' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
@ -164,26 +159,6 @@ const ProductInfo = () => {
|
||||
isEditing={isEditing}
|
||||
type='product'
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
parts: false
|
||||
}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Product Parts'
|
||||
icon={<PartIcon />}
|
||||
active={collapseState.parts}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('parts', expanded)
|
||||
}
|
||||
collapseKey='parts'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('product', 'parts')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
size='medium'
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
|
||||
@ -521,11 +521,15 @@ const ObjectForm = forwardRef(
|
||||
onEdit(allFormValues)
|
||||
}
|
||||
|
||||
// Calculate computed values based on current form data
|
||||
const currentFormData = {
|
||||
...(serverObjectData.current || {}),
|
||||
...changedValues
|
||||
}
|
||||
// Recompute derived fields from the full current form snapshot so
|
||||
// toggles like overridePrice/overrideCost are preserved while typing.
|
||||
const currentFormData = mergeWith(
|
||||
{},
|
||||
serverObjectData.current || {},
|
||||
objectData || {},
|
||||
allFormValues,
|
||||
arrayReplaceCustomizer
|
||||
)
|
||||
const computedEntries = calculateComputedValues(
|
||||
currentFormData,
|
||||
model
|
||||
|
||||
@ -51,15 +51,6 @@ import { round } from '../utils/Utils'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const MATERIAL_OPTIONS = [
|
||||
{ value: 'PLA', label: 'PLA' },
|
||||
{ value: 'PETG', label: 'PETG' },
|
||||
{ value: 'ABS', label: 'ABS' },
|
||||
{ value: 'ASA', label: 'ASA' },
|
||||
{ value: 'HIPS', label: 'HIPS' },
|
||||
{ value: 'TPU', label: 'TPU' }
|
||||
]
|
||||
|
||||
const ObjectProperty = ({
|
||||
type = 'text',
|
||||
prefix,
|
||||
@ -432,17 +423,6 @@ const ObjectProperty = ({
|
||||
)
|
||||
}
|
||||
}
|
||||
case 'material': {
|
||||
if (value) {
|
||||
return <Text {...textParams}>{value}</Text>
|
||||
} else {
|
||||
return (
|
||||
<Text type='secondary' {...textParams}>
|
||||
n/a
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}
|
||||
case 'id': {
|
||||
if (value) {
|
||||
return (
|
||||
@ -754,14 +734,6 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'markdown':
|
||||
return <MarkdownInput {...inputProps} />
|
||||
case 'material':
|
||||
return (
|
||||
<Select
|
||||
options={MATERIAL_OPTIONS}
|
||||
placeholder={label}
|
||||
{...inputProps}
|
||||
/>
|
||||
)
|
||||
case 'id':
|
||||
// id is not editable, just show view mode
|
||||
if (value) {
|
||||
|
||||
@ -18,7 +18,8 @@ export const HistoryProvider = ({ children }) => {
|
||||
'/dashboard/management/filaments': 'Filaments',
|
||||
'/dashboard/management/parts': 'Parts',
|
||||
'/dashboard/management/products': 'Products',
|
||||
'/dashboard/management/vendors': 'Vendors'
|
||||
'/dashboard/management/vendors': 'Vendors',
|
||||
'/dashboard/management/materials': 'Materials'
|
||||
}
|
||||
|
||||
const getEntityDetails = (pathname, search) => {
|
||||
@ -65,6 +66,14 @@ export const HistoryProvider = ({ children }) => {
|
||||
displayName: `Vendor Info${vendorId ? ` (${vendorId})` : ''}`
|
||||
}
|
||||
}
|
||||
if (pathname.includes('/materials/info')) {
|
||||
const materialId = searchParams.get('materialId')
|
||||
return {
|
||||
type: 'material',
|
||||
id: materialId,
|
||||
displayName: `Material Info${materialId ? ` (${materialId})` : ''}`
|
||||
}
|
||||
}
|
||||
|
||||
// For base routes, return the simple name
|
||||
const baseName = baseRouteNames[pathname]
|
||||
@ -85,7 +94,8 @@ export const HistoryProvider = ({ children }) => {
|
||||
|
||||
if (
|
||||
newPath === '/dashboard/production/gcodefiles' ||
|
||||
newPath === '/dashboard/management/filaments'
|
||||
newPath === '/dashboard/management/filaments' ||
|
||||
newPath === '/dashboard/management/materials'
|
||||
) {
|
||||
setNavigationHistory([
|
||||
{
|
||||
|
||||
8
src/components/Icons/FilamentSkuIcon.jsx
Normal file
8
src/components/Icons/FilamentSkuIcon.jsx
Normal file
@ -0,0 +1,8 @@
|
||||
import Icon from '@ant-design/icons'
|
||||
import CustomIconSvg from '../../../assets/icons/filamentskuicon.svg?react'
|
||||
|
||||
const FilamentSkuIcon = (props) => (
|
||||
<Icon component={CustomIconSvg} {...props} />
|
||||
)
|
||||
|
||||
export default FilamentSkuIcon
|
||||
8
src/components/Icons/PartSkuIcon.jsx
Normal file
8
src/components/Icons/PartSkuIcon.jsx
Normal file
@ -0,0 +1,8 @@
|
||||
import Icon from '@ant-design/icons'
|
||||
import CustomIconSvg from '../../../assets/icons/partskuicon.svg?react'
|
||||
|
||||
const PartSkuIcon = (props) => (
|
||||
<Icon component={CustomIconSvg} {...props} />
|
||||
)
|
||||
|
||||
export default PartSkuIcon
|
||||
@ -1,12 +1,15 @@
|
||||
import { Printer } from './models/Printer.js'
|
||||
import { Host } from './models/Host.js'
|
||||
import { Filament } from './models/Filament.js'
|
||||
import { Material } from './models/Material.js'
|
||||
import { FilamentSku } from './models/FilamentSku.js'
|
||||
import { Spool } from './models/Spool'
|
||||
import { GCodeFile } from './models/GCodeFile'
|
||||
import { Job } from './models/Job'
|
||||
import { Product } from './models/Product'
|
||||
import { ProductSku } from './models/ProductSku'
|
||||
import { Part } from './models/Part.js'
|
||||
import { PartSku } from './models/PartSku.js'
|
||||
import { Vendor } from './models/Vendor'
|
||||
import { Courier } from './models/Courier'
|
||||
import { CourierService } from './models/CourierService'
|
||||
@ -42,12 +45,15 @@ export const objectModels = [
|
||||
Printer,
|
||||
Host,
|
||||
Filament,
|
||||
FilamentSku,
|
||||
Material,
|
||||
Spool,
|
||||
GCodeFile,
|
||||
Job,
|
||||
Product,
|
||||
ProductSku,
|
||||
Part,
|
||||
PartSku,
|
||||
Vendor,
|
||||
Courier,
|
||||
CourierService,
|
||||
@ -84,12 +90,15 @@ export {
|
||||
Printer,
|
||||
Host,
|
||||
Filament,
|
||||
FilamentSku,
|
||||
Material,
|
||||
Spool,
|
||||
GCodeFile,
|
||||
Job,
|
||||
Product,
|
||||
ProductSku,
|
||||
Part,
|
||||
PartSku,
|
||||
Vendor,
|
||||
Courier,
|
||||
CourierService,
|
||||
|
||||
@ -4,6 +4,7 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
|
||||
export const Filament = {
|
||||
name: 'filament',
|
||||
@ -56,31 +57,33 @@ export const Filament = {
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'newFilamentSku',
|
||||
label: 'New Filament SKU',
|
||||
type: 'button',
|
||||
icon: PlusIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/filaments/info?filamentId=${_id}&action=newFilamentSku`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
'_reference',
|
||||
'name',
|
||||
'type',
|
||||
'color',
|
||||
'vendor',
|
||||
'cost',
|
||||
'material',
|
||||
'density',
|
||||
'diameter',
|
||||
'cost',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor'],
|
||||
sorters: [
|
||||
'name',
|
||||
'createdAt',
|
||||
'type',
|
||||
'vendor',
|
||||
'cost',
|
||||
'updatedAt',
|
||||
'createdAt'
|
||||
],
|
||||
group: ['diameter', 'type', 'vendor'],
|
||||
filters: ['_id', 'name', 'material'],
|
||||
sorters: ['name', 'createdAt', 'material', 'updatedAt'],
|
||||
group: ['diameter', 'material'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
@ -110,67 +113,14 @@ export const Filament = {
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'vendor',
|
||||
label: 'Vendor',
|
||||
required: true,
|
||||
type: 'object',
|
||||
objectType: 'vendor',
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
name: 'material',
|
||||
label: 'Material',
|
||||
required: true,
|
||||
columnWidth: 150,
|
||||
type: 'material'
|
||||
},
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
columnWidth: 150,
|
||||
required: true,
|
||||
type: 'number',
|
||||
prefix: '£'
|
||||
},
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
columnWidth: 150,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
value: (objectData) => {
|
||||
if (objectData?.costTaxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(
|
||||
objectData?.cost *
|
||||
(1 + objectData?.costTaxRate?.rate / 100)
|
||||
).toFixed(2) || undefined
|
||||
)
|
||||
} else if (objectData?.costTaxRate?.rateType == 'amount') {
|
||||
return (
|
||||
(objectData?.cost + objectData?.costTaxRate?.rate).toFixed(2) ||
|
||||
undefined
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: true,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
objectType: 'material',
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
label: 'Color',
|
||||
columnWidth: 150,
|
||||
required: true,
|
||||
type: 'color'
|
||||
},
|
||||
{
|
||||
name: 'diameter',
|
||||
label: 'Diameter',
|
||||
@ -195,6 +145,47 @@ export const Filament = {
|
||||
type: 'number',
|
||||
suffix: 'g'
|
||||
},
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
required: false,
|
||||
columnWidth: 120,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01
|
||||
},
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
value: (objectData) => {
|
||||
const cost = objectData?.cost
|
||||
if (!cost) return undefined
|
||||
if (objectData?.costTaxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) ||
|
||||
undefined
|
||||
)
|
||||
} else if (objectData?.costTaxRate?.rateType == 'amount') {
|
||||
return (cost + objectData?.costTaxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
return cost
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
label: 'Link',
|
||||
|
||||
184
src/database/models/FilamentSku.js
Normal file
184
src/database/models/FilamentSku.js
Normal file
@ -0,0 +1,184 @@
|
||||
import FilamentSkuIcon from '../../components/Icons/FilamentSkuIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import BinIcon from '../../components/Icons/BinIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
|
||||
export const FilamentSku = {
|
||||
name: 'filamentSku',
|
||||
label: 'Filament SKU',
|
||||
prefix: 'FSU',
|
||||
icon: FilamentSkuIcon,
|
||||
actions: [
|
||||
{
|
||||
name: 'info',
|
||||
label: 'Info',
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon,
|
||||
url: (_id) => `/dashboard/management/filamentskus/info?filamentSkuId=${_id}`
|
||||
},
|
||||
{
|
||||
name: 'reload',
|
||||
label: 'Reload',
|
||||
icon: ReloadIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=reload`
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'delete',
|
||||
label: 'Delete',
|
||||
icon: BinIcon,
|
||||
danger: true,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=delete`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/management/filamentskus/info?filamentSkuId=${id}`,
|
||||
columns: ['_reference', 'barcode', 'filament', 'name', 'color', 'overrideCost', 'cost', 'createdAt', 'updatedAt'],
|
||||
filters: ['_id', 'barcode', 'filament', 'filament._id', 'name', 'color', 'cost'],
|
||||
sorters: ['barcode', 'filament', 'name', 'color', 'cost', 'createdAt', 'updatedAt'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
label: 'ID',
|
||||
type: 'id',
|
||||
objectType: 'filamentSku',
|
||||
showCopy: true,
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
required: true,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'filament',
|
||||
label: 'Filament',
|
||||
type: 'object',
|
||||
objectType: 'filament',
|
||||
required: true,
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'barcode',
|
||||
label: 'Barcode',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
label: 'Color',
|
||||
required: true,
|
||||
type: 'color'
|
||||
},
|
||||
{
|
||||
name: 'overrideCost',
|
||||
label: 'Override Cost',
|
||||
required: false,
|
||||
type: 'bool',
|
||||
value: (objectData) => objectData?.overrideCost ?? false
|
||||
},
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) =>
|
||||
objectData?.overrideCost ? objectData?.cost : undefined
|
||||
},
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) => {
|
||||
if (!objectData?.overrideCost) return undefined
|
||||
const cost = objectData?.cost
|
||||
const taxRate = objectData?.costTaxRate
|
||||
if (!cost) return undefined
|
||||
if (taxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(cost * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
|
||||
)
|
||||
} else if (taxRate?.rateType == 'amount') {
|
||||
return (cost + taxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
return cost
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) =>
|
||||
objectData?.overrideCost ? objectData?.costTaxRate : undefined
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -22,13 +22,13 @@ export const FilamentStock = {
|
||||
'state',
|
||||
'currentWeight',
|
||||
'startingWeight',
|
||||
'filament',
|
||||
'filamentSku',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
filters: ['_id'],
|
||||
sorters: ['createdAt', 'updatedAt'],
|
||||
group: ['filament'],
|
||||
group: ['filamentSku'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
@ -58,10 +58,10 @@ export const FilamentStock = {
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'filament',
|
||||
label: 'Filament',
|
||||
name: 'filamentSku',
|
||||
label: 'Filament SKU',
|
||||
type: 'object',
|
||||
objectType: 'filament',
|
||||
objectType: 'filamentSku',
|
||||
readOnly: true,
|
||||
initial: true,
|
||||
required: true,
|
||||
@ -93,7 +93,7 @@ export const FilamentStock = {
|
||||
required: true,
|
||||
columnWidth: 300,
|
||||
difference: (objectData) => {
|
||||
return objectData?.filament?.emptySpoolWeight
|
||||
return objectData?.filamentSku?.filament?.emptySpoolWeight
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@ -71,7 +71,7 @@ export const GCodeFile = {
|
||||
columns: [
|
||||
'name',
|
||||
'_reference',
|
||||
'filament',
|
||||
'filamentSku',
|
||||
'gcodeFileInfo.estimatedPrintingTimeNormalMode',
|
||||
'gcodeFileInfo.sparseInfillDensity',
|
||||
'gcodeFileInfo.sparseInfillPattern',
|
||||
@ -81,7 +81,7 @@ export const GCodeFile = {
|
||||
],
|
||||
filters: ['_id', 'name', 'updatedAt'],
|
||||
sorters: ['name', 'createdAt', 'updatedAt'],
|
||||
group: ['filament'],
|
||||
group: ['filamentSku'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
@ -125,11 +125,11 @@ export const GCodeFile = {
|
||||
filter: ['.gcode', '.g']
|
||||
},
|
||||
{
|
||||
name: 'filament',
|
||||
label: 'Filament',
|
||||
name: 'filamentSku',
|
||||
label: 'Filament SKU',
|
||||
type: 'object',
|
||||
value: null,
|
||||
objectType: 'filament',
|
||||
objectType: 'filamentSku',
|
||||
required: true,
|
||||
showHyperlink: true
|
||||
},
|
||||
@ -139,10 +139,11 @@ export const GCodeFile = {
|
||||
type: 'number',
|
||||
roundNumber: 2,
|
||||
value: (objectData) => {
|
||||
return (
|
||||
objectData?.file?.metaData?.filamentUsedG *
|
||||
(objectData?.filament?.cost / 1000)
|
||||
)
|
||||
const fs = objectData?.filamentSku
|
||||
const costPerKg =
|
||||
fs?.overrideCost ? fs?.cost : fs?.filament?.cost
|
||||
if (!costPerKg || !objectData?.file?.metaData?.filamentUsedG) return undefined
|
||||
return objectData.file.metaData.filamentUsedG * (costPerKg / 1000)
|
||||
},
|
||||
readOnly: true,
|
||||
prefix: '£'
|
||||
|
||||
117
src/database/models/Material.js
Normal file
117
src/database/models/Material.js
Normal file
@ -0,0 +1,117 @@
|
||||
import MaterialIcon from '../../components/Icons/MaterialIcon'
|
||||
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 Material = {
|
||||
name: 'material',
|
||||
label: 'Material',
|
||||
prefix: 'MAT',
|
||||
icon: MaterialIcon,
|
||||
actions: [
|
||||
{
|
||||
name: 'info',
|
||||
label: 'Info',
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon,
|
||||
url: (_id) => `/dashboard/management/materials/info?materialId=${_id}`
|
||||
},
|
||||
{
|
||||
name: 'reload',
|
||||
label: 'Reload',
|
||||
icon: ReloadIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/materials/info?materialId=${_id}&action=reload`
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/materials/info?materialId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/materials/info?materialId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/materials/info?materialId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'delete',
|
||||
label: 'Delete',
|
||||
icon: BinIcon,
|
||||
danger: true,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/materials/info?materialId=${_id}&action=delete`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/management/materials/info?materialId=${id}`,
|
||||
columns: ['_reference', 'name', 'tags', 'createdAt', 'updatedAt'],
|
||||
filters: ['_id', 'name', 'tags'],
|
||||
sorters: ['name', 'createdAt', 'updatedAt', '_id'],
|
||||
group: ['tags'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
label: 'ID',
|
||||
columnFixed: 'left',
|
||||
type: 'id',
|
||||
objectType: 'material',
|
||||
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: 'tags',
|
||||
label: 'Tags',
|
||||
required: false,
|
||||
type: 'tags'
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
label: 'Link',
|
||||
required: false,
|
||||
type: 'url'
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -78,7 +78,7 @@ export const OrderItem = {
|
||||
}
|
||||
],
|
||||
group: [],
|
||||
filters: ['itemType', 'item', 'order'],
|
||||
filters: ['itemType', 'item', 'sku', 'order'],
|
||||
sorters: ['createdAt', 'updatedAt', 'itemAmount', 'quantity'],
|
||||
columns: [
|
||||
'_reference',
|
||||
@ -86,6 +86,7 @@ export const OrderItem = {
|
||||
'state',
|
||||
'itemType',
|
||||
'item',
|
||||
'sku',
|
||||
'itemAmount',
|
||||
'quantity',
|
||||
'totalAmount',
|
||||
@ -141,7 +142,7 @@ export const OrderItem = {
|
||||
type: 'text',
|
||||
readOnly: true,
|
||||
value: (objectData) => {
|
||||
return objectData?.item?.name
|
||||
return objectData?.sku?.name ?? objectData?.item?.name
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -213,6 +214,35 @@ export const OrderItem = {
|
||||
showHyperlink: true,
|
||||
columnWidth: 300
|
||||
},
|
||||
{
|
||||
name: 'sku',
|
||||
label: 'SKU',
|
||||
type: 'object',
|
||||
objectType: (objectData) => {
|
||||
if (objectData?.itemType === 'filament') return 'filamentSku'
|
||||
if (objectData?.itemType === 'part') return 'partSku'
|
||||
if (objectData?.itemType === 'product') return 'productSku'
|
||||
return undefined
|
||||
},
|
||||
required: false,
|
||||
showHyperlink: true,
|
||||
columnWidth: 300,
|
||||
visible: (objectData) =>
|
||||
['filament', 'part', 'product'].includes(objectData?.itemType),
|
||||
masterFilter: (objectData) => {
|
||||
console.log(objectData)
|
||||
if (objectData?.itemType === 'filament' && objectData?.item?._id) {
|
||||
return { filament: objectData.item._id }
|
||||
}
|
||||
if (objectData?.itemType === 'part' && objectData?.item?._id) {
|
||||
return { part: objectData.item._id }
|
||||
}
|
||||
if (objectData?.itemType === 'product' && objectData?.item?._id) {
|
||||
return { product: objectData.item._id }
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'syncAmount',
|
||||
label: 'Sync Amount',
|
||||
@ -239,11 +269,24 @@ export const OrderItem = {
|
||||
},
|
||||
columnWidth: 150,
|
||||
value: (objectData) => {
|
||||
if (objectData?.item?.cost && objectData?.syncAmount == 'itemCost') {
|
||||
return objectData?.item?.cost || undefined
|
||||
const sku = objectData?.sku
|
||||
const item = objectData?.item
|
||||
if (objectData?.syncAmount == 'itemCost') {
|
||||
const cost =
|
||||
sku && sku.overrideCost ? sku.cost : (item?.cost ?? sku?.cost)
|
||||
return cost ?? objectData?.itemAmount
|
||||
}
|
||||
if (objectData?.item?.price && objectData?.syncAmount == 'itemPrice') {
|
||||
return objectData?.item?.price || undefined
|
||||
if (objectData?.syncAmount == 'itemPrice') {
|
||||
if (sku && sku.overridePrice) {
|
||||
return sku.price ?? objectData?.itemAmount
|
||||
}
|
||||
const priceMode = item?.priceMode ?? sku?.priceMode
|
||||
const margin = item?.margin ?? sku?.margin
|
||||
const cost = item?.cost ?? sku?.cost
|
||||
if (priceMode == 'margin' && margin != null && cost != null) {
|
||||
return cost * (1 + margin / 100)
|
||||
}
|
||||
return item?.price ?? sku?.price ?? objectData?.itemAmount
|
||||
}
|
||||
return objectData?.itemAmount || undefined
|
||||
}
|
||||
@ -283,19 +326,19 @@ export const OrderItem = {
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
value: (objectData) => {
|
||||
if (
|
||||
objectData?.item?.costTaxRate?._id &&
|
||||
objectData?.syncAmount == 'itemCost'
|
||||
) {
|
||||
return objectData?.item?.costTaxRate || undefined
|
||||
} else if (
|
||||
objectData?.item?.priceTaxRate?._id &&
|
||||
objectData?.syncAmount == 'itemPrice'
|
||||
) {
|
||||
return objectData?.item?.priceTaxRate || undefined
|
||||
} else {
|
||||
return objectData?.taxRate || undefined
|
||||
const sku = objectData?.sku
|
||||
const item = objectData?.item
|
||||
if (objectData?.syncAmount == 'itemCost') {
|
||||
const source = sku && sku.overrideCost ? sku : item
|
||||
return source?.costTaxRate ?? sku?.costTaxRate ?? objectData?.taxRate
|
||||
}
|
||||
if (objectData?.syncAmount == 'itemPrice') {
|
||||
const source = sku && sku.overridePrice ? sku : item
|
||||
return (
|
||||
source?.priceTaxRate ?? sku?.priceTaxRate ?? objectData?.taxRate
|
||||
)
|
||||
}
|
||||
return objectData?.taxRate || undefined
|
||||
},
|
||||
readOnly: (objectData) => {
|
||||
return objectData?.syncAmount != null
|
||||
|
||||
@ -4,6 +4,8 @@ import PartIcon from '../../components/Icons/PartIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
|
||||
export const Part = {
|
||||
name: 'part',
|
||||
label: 'Part',
|
||||
@ -55,11 +57,23 @@ export const Part = {
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'newPartSku',
|
||||
label: 'New Part SKU',
|
||||
type: 'button',
|
||||
icon: PlusIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/parts/info?partId=${_id}&action=newPartSku`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
}
|
||||
],
|
||||
columns: ['name', '_reference', 'product', 'globalPricing', 'createdAt'],
|
||||
filters: ['name', '_id', 'product', 'globalPricing'],
|
||||
sorters: ['name', 'email', 'role', 'createdAt', '_id'],
|
||||
columns: ['name', '_reference', 'cost', 'price', 'createdAt'],
|
||||
filters: ['name', '_id'],
|
||||
sorters: ['name', 'createdAt', '_id'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
@ -90,25 +104,23 @@ export const Part = {
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'vendor',
|
||||
label: 'Vendor',
|
||||
required: true,
|
||||
type: 'object',
|
||||
objectType: 'vendor',
|
||||
showHyperlink: true,
|
||||
value: (objectData) => {
|
||||
if (!objectData?.vendor && objectData?.product?.vendor) {
|
||||
return objectData?.product?.vendor
|
||||
} else {
|
||||
return objectData?.vendor
|
||||
}
|
||||
}
|
||||
name: 'fileName',
|
||||
label: 'File Name',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'file',
|
||||
label: 'File',
|
||||
type: 'file',
|
||||
value: null,
|
||||
required: false,
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
columnWidth: 150,
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
@ -117,128 +129,114 @@ export const Part = {
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
columnWidth: 150,
|
||||
required: true,
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
value: (objectData) => {
|
||||
const cost = objectData?.cost
|
||||
if (!cost) return undefined
|
||||
if (objectData?.costTaxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(
|
||||
objectData?.cost *
|
||||
(1 + objectData?.costTaxRate?.rate / 100)
|
||||
).toFixed(2) || undefined
|
||||
)
|
||||
} else if (objectData?.costTaxRate?.rateType == 'amount') {
|
||||
return (
|
||||
(objectData?.cost + objectData?.costTaxRate?.rate).toFixed(2) ||
|
||||
(cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) ||
|
||||
undefined
|
||||
)
|
||||
} else {
|
||||
return objectData?.cost || undefined
|
||||
} else if (objectData?.costTaxRate?.rateType == 'amount') {
|
||||
return (cost + objectData?.costTaxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
return cost
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'priceMode',
|
||||
label: 'Price Mode',
|
||||
required: false,
|
||||
type: 'priceMode'
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: 'Price',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.1,
|
||||
readOnly: (objectData) => {
|
||||
return objectData?.priceMode == 'margin'
|
||||
},
|
||||
readOnly: (objectData) => objectData?.priceMode == 'margin',
|
||||
value: (objectData) => {
|
||||
if (
|
||||
objectData?.priceMode == 'margin' &&
|
||||
objectData?.margin !== undefined &&
|
||||
objectData?.margin !== null
|
||||
objectData?.margin !== null &&
|
||||
objectData?.cost != null
|
||||
) {
|
||||
return (
|
||||
(objectData?.cost * (1 + objectData?.margin / 100)).toFixed(2) ||
|
||||
(objectData.cost * (1 + objectData.margin / 100)).toFixed(2) ||
|
||||
undefined
|
||||
)
|
||||
} else {
|
||||
return objectData?.price || undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'priceWithTax',
|
||||
label: 'Price w/ Tax',
|
||||
columnWidth: 150,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
value: (objectData) => {
|
||||
if (objectData?.priceTaxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(
|
||||
objectData?.price *
|
||||
(1 + objectData?.priceTaxRate?.rate / 100)
|
||||
).toFixed(2) || undefined
|
||||
)
|
||||
} else if (objectData?.priceTaxRate?.rateType == 'amount') {
|
||||
return (
|
||||
(objectData?.price + objectData?.priceTaxRate?.rate).toFixed(2) ||
|
||||
undefined
|
||||
)
|
||||
} else {
|
||||
return objectData?.price
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'priceMode',
|
||||
label: 'Price Mode',
|
||||
required: true,
|
||||
type: 'priceMode'
|
||||
},
|
||||
{
|
||||
name: 'margin',
|
||||
label: 'Margin',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'number',
|
||||
disabled: (objectData) => {
|
||||
return objectData.priceMode == 'amount'
|
||||
},
|
||||
disabled: (objectData) => objectData?.priceMode == 'amount',
|
||||
suffix: '%',
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 0.01
|
||||
},
|
||||
{
|
||||
name: 'priceWithTax',
|
||||
label: 'Price w/ Tax',
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
value: (objectData) => {
|
||||
let price
|
||||
if (
|
||||
objectData?.priceMode == 'margin' &&
|
||||
objectData?.margin != null &&
|
||||
objectData?.cost != null
|
||||
) {
|
||||
price = objectData.cost * (1 + objectData.margin / 100)
|
||||
} else {
|
||||
price = objectData?.price
|
||||
}
|
||||
if (!price) return undefined
|
||||
if (objectData?.priceTaxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(price * (1 + objectData?.priceTaxRate?.rate / 100)).toFixed(2) ||
|
||||
undefined
|
||||
)
|
||||
} else if (objectData?.priceTaxRate?.rateType == 'amount') {
|
||||
return (price + objectData?.priceTaxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
return price
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'priceTaxRate',
|
||||
label: 'Price Tax Rate',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true
|
||||
},
|
||||
|
||||
{
|
||||
name: 'file',
|
||||
label: 'File',
|
||||
type: 'file',
|
||||
value: null,
|
||||
required: false,
|
||||
showHyperlink: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
293
src/database/models/PartSku.js
Normal file
293
src/database/models/PartSku.js
Normal file
@ -0,0 +1,293 @@
|
||||
import PartSkuIcon from '../../components/Icons/PartSkuIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import BinIcon from '../../components/Icons/BinIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
|
||||
export const PartSku = {
|
||||
name: 'partSku',
|
||||
label: 'Part SKU',
|
||||
prefix: 'PSU',
|
||||
icon: PartSkuIcon,
|
||||
actions: [
|
||||
{
|
||||
name: 'info',
|
||||
label: 'Info',
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon,
|
||||
url: (_id) => `/dashboard/management/partskus/info?partSkuId=${_id}`
|
||||
},
|
||||
{
|
||||
name: 'reload',
|
||||
label: 'Reload',
|
||||
icon: ReloadIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/partskus/info?partSkuId=${_id}&action=reload`
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/partskus/info?partSkuId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/partskus/info?partSkuId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/partskus/info?partSkuId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'delete',
|
||||
label: 'Delete',
|
||||
icon: BinIcon,
|
||||
danger: true,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/partskus/info?partSkuId=${_id}&action=delete`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/management/partskus/info?partSkuId=${id}`,
|
||||
columns: [
|
||||
'_reference',
|
||||
'barcode',
|
||||
'part',
|
||||
'name',
|
||||
'overrideCost',
|
||||
'cost',
|
||||
'overridePrice',
|
||||
'price',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
filters: ['_id', 'barcode', 'part', 'part._id', 'name', 'cost', 'price'],
|
||||
sorters: ['barcode', 'part', 'name', 'cost', 'price', 'createdAt', 'updatedAt'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
label: 'ID',
|
||||
type: 'id',
|
||||
objectType: 'partSku',
|
||||
showCopy: true,
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
required: true,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'part',
|
||||
label: 'Part',
|
||||
type: 'object',
|
||||
objectType: 'part',
|
||||
required: true,
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'barcode',
|
||||
label: 'Barcode',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'priceMode',
|
||||
label: 'Price Mode',
|
||||
required: false,
|
||||
type: 'priceMode'
|
||||
},
|
||||
{
|
||||
name: 'overrideCost',
|
||||
label: 'Override Cost',
|
||||
required: false,
|
||||
type: 'bool',
|
||||
value: (objectData) => objectData?.overrideCost ?? false
|
||||
},
|
||||
{
|
||||
name: 'overridePrice',
|
||||
label: 'Override Price',
|
||||
required: false,
|
||||
type: 'bool',
|
||||
value: (objectData) => objectData?.overridePrice ?? false
|
||||
},
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) =>
|
||||
objectData?.overrideCost ? objectData?.cost : undefined
|
||||
},
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) => {
|
||||
if (!objectData?.overrideCost) return undefined
|
||||
const cost = objectData?.cost
|
||||
const taxRate = objectData?.costTaxRate
|
||||
if (!cost) return undefined
|
||||
if (taxRate?.rateType == 'percentage') {
|
||||
return (cost * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
|
||||
} else if (taxRate?.rateType == 'amount') {
|
||||
return (cost + taxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
return cost
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) =>
|
||||
objectData?.overrideCost ? objectData?.costTaxRate : undefined
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: 'Price',
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.1,
|
||||
disabled: (objectData) => !objectData?.overridePrice,
|
||||
readOnly: (objectData) =>
|
||||
objectData?.overridePrice && objectData?.priceMode == 'margin',
|
||||
value: (objectData) => {
|
||||
if (!objectData?.overridePrice) return undefined
|
||||
const priceMode = objectData?.priceMode ?? objectData?.part?.priceMode
|
||||
const cost = objectData?.overrideCost
|
||||
? objectData?.cost
|
||||
: objectData?.part?.cost
|
||||
const margin = objectData?.margin ?? objectData?.part?.margin
|
||||
if (
|
||||
priceMode == 'margin' &&
|
||||
margin !== undefined &&
|
||||
margin !== null &&
|
||||
cost != null
|
||||
) {
|
||||
return (
|
||||
(cost * (1 + margin / 100)).toFixed(2) || undefined
|
||||
)
|
||||
}
|
||||
return objectData?.price
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'priceWithTax',
|
||||
label: 'Price w/ Tax',
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
disabled: (objectData) => !objectData?.overridePrice,
|
||||
value: (objectData) => {
|
||||
if (!objectData?.overridePrice) return undefined
|
||||
let price
|
||||
const priceMode = objectData?.priceMode ?? objectData?.part?.priceMode
|
||||
const cost = objectData?.overrideCost
|
||||
? objectData?.cost
|
||||
: objectData?.part?.cost
|
||||
const margin = objectData?.margin ?? objectData?.part?.margin
|
||||
if (
|
||||
priceMode == 'margin' &&
|
||||
margin != null &&
|
||||
cost != null
|
||||
) {
|
||||
price = cost * (1 + margin / 100)
|
||||
} else {
|
||||
price = objectData?.price
|
||||
}
|
||||
if (price == null) return undefined
|
||||
const taxRate = objectData?.priceTaxRate ?? objectData?.part?.priceTaxRate
|
||||
if (taxRate?.rateType == 'percentage') {
|
||||
return (price * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
|
||||
} else if (taxRate?.rateType == 'amount') {
|
||||
return (price + taxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
return price
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'margin',
|
||||
label: 'Margin',
|
||||
required: false,
|
||||
type: 'number',
|
||||
disabled: (objectData) =>
|
||||
!objectData?.overridePrice || objectData?.priceMode == 'amount',
|
||||
suffix: '%',
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 0.01,
|
||||
value: (objectData) =>
|
||||
objectData?.overridePrice ? objectData?.margin : undefined
|
||||
},
|
||||
{
|
||||
name: 'priceTaxRate',
|
||||
label: 'Price Tax Rate',
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
disabled: (objectData) => !objectData?.overridePrice,
|
||||
value: (objectData) =>
|
||||
objectData?.overridePrice ? objectData?.priceTaxRate : undefined
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -17,14 +17,14 @@ export const PartStock = {
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/inventory/partstocks/info?partStockId=${id}`,
|
||||
filters: ['_id', 'part', 'startingQuantity', 'currentQuantity'],
|
||||
sorters: ['part', 'startingQuantity', 'currentQuantity'],
|
||||
filters: ['_id', 'partSku', 'startingQuantity', 'currentQuantity'],
|
||||
sorters: ['partSku', 'startingQuantity', 'currentQuantity'],
|
||||
columns: [
|
||||
'_reference',
|
||||
'state',
|
||||
'startingQuantity',
|
||||
'currentQuantity',
|
||||
'part',
|
||||
'partSku',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
@ -66,10 +66,10 @@ export const PartStock = {
|
||||
masterFilter: ['subJob']
|
||||
},
|
||||
{
|
||||
name: 'part',
|
||||
label: 'Part',
|
||||
name: 'partSku',
|
||||
label: 'Part SKU',
|
||||
type: 'object',
|
||||
objectType: 'part',
|
||||
objectType: 'partSku',
|
||||
required: true,
|
||||
showHyperlink: true
|
||||
},
|
||||
|
||||
@ -78,12 +78,13 @@ export const Product = {
|
||||
'name',
|
||||
'tags',
|
||||
'vendor',
|
||||
'cost',
|
||||
'price',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor'],
|
||||
sorters: ['name', 'createdAt', 'type', 'vendor', 'cost', 'updatedAt'],
|
||||
filters: ['_id', 'name', 'type', 'color', 'vendor'],
|
||||
sorters: ['name', 'createdAt', 'type', 'vendor', 'updatedAt'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
@ -131,72 +132,126 @@ export const Product = {
|
||||
required: false,
|
||||
type: 'tags'
|
||||
},
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01
|
||||
},
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
value: (objectData) => {
|
||||
const cost = objectData?.cost
|
||||
if (!cost) return undefined
|
||||
if (objectData?.costTaxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) ||
|
||||
undefined
|
||||
)
|
||||
} else if (objectData?.costTaxRate?.rateType == 'amount') {
|
||||
return (cost + objectData?.costTaxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
return cost
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'priceMode',
|
||||
label: 'Price Mode',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'priceMode'
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: 'Price',
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.1,
|
||||
readOnly: (objectData) => objectData?.priceMode == 'margin',
|
||||
value: (objectData) => {
|
||||
if (
|
||||
objectData?.priceMode == 'margin' &&
|
||||
objectData?.margin !== undefined &&
|
||||
objectData?.margin !== null &&
|
||||
objectData?.cost != null
|
||||
) {
|
||||
return (
|
||||
(objectData.cost * (1 + objectData.margin / 100)).toFixed(2) ||
|
||||
undefined
|
||||
)
|
||||
}
|
||||
return objectData?.price
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'margin',
|
||||
label: 'Margin',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'number',
|
||||
disabled: (objectData) => {
|
||||
return objectData.priceMode == 'amount'
|
||||
},
|
||||
disabled: (objectData) => objectData?.priceMode == 'amount',
|
||||
suffix: '%',
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 0.01
|
||||
},
|
||||
{
|
||||
name: 'amount',
|
||||
label: 'Amount',
|
||||
disabled: (objectData) => {
|
||||
return objectData.priceMode == 'margin'
|
||||
},
|
||||
name: 'priceWithTax',
|
||||
label: 'Price w/ Tax',
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
required: true,
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.1
|
||||
},
|
||||
{
|
||||
name: 'parts',
|
||||
label: 'Parts',
|
||||
type: 'objectChildren',
|
||||
objectType: 'part',
|
||||
properties: [
|
||||
{
|
||||
name: 'part',
|
||||
label: 'Part',
|
||||
type: 'object',
|
||||
objectType: 'part',
|
||||
required: true,
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
label: 'Quantity',
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
rollups: [
|
||||
{
|
||||
name: 'totalQuantity',
|
||||
label: 'Total',
|
||||
type: 'number',
|
||||
property: 'quantity',
|
||||
step: 0.01,
|
||||
value: (objectData) => {
|
||||
return objectData?.parts?.reduce(
|
||||
(acc, part) => acc + part.quantity,
|
||||
0
|
||||
let price
|
||||
if (
|
||||
objectData?.priceMode == 'margin' &&
|
||||
objectData?.margin != null &&
|
||||
objectData?.cost != null
|
||||
) {
|
||||
price = objectData.cost * (1 + objectData.margin / 100)
|
||||
} else {
|
||||
price = objectData?.price
|
||||
}
|
||||
if (!price) return undefined
|
||||
if (objectData?.priceTaxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(price * (1 + objectData?.priceTaxRate?.rate / 100)).toFixed(2) ||
|
||||
undefined
|
||||
)
|
||||
} else if (objectData?.priceTaxRate?.rateType == 'amount') {
|
||||
return (price + objectData?.priceTaxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
return price
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'priceTaxRate',
|
||||
label: 'Price Tax Rate',
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -69,9 +69,9 @@ export const ProductSku = {
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/management/productskus/info?productSkuId=${id}`,
|
||||
columns: ['_reference', 'sku', 'product', 'name', 'createdAt', 'updatedAt'],
|
||||
filters: ['_id', 'sku', 'product', 'product._id', 'name'],
|
||||
sorters: ['sku', 'product', 'name', 'createdAt', 'updatedAt'],
|
||||
columns: ['_reference', 'barcode', 'product', 'name', 'overrideCost', 'cost', 'overridePrice', 'price', 'createdAt', 'updatedAt'],
|
||||
filters: ['_id', 'barcode', 'product', 'product._id', 'name', 'cost', 'price'],
|
||||
sorters: ['barcode', 'product', 'name', 'cost', 'price', 'createdAt', 'updatedAt'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
@ -108,9 +108,9 @@ export const ProductSku = {
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'sku',
|
||||
label: 'SKU',
|
||||
required: true,
|
||||
name: 'barcode',
|
||||
label: 'Barcode',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
@ -118,6 +118,205 @@ export const ProductSku = {
|
||||
label: 'Description',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'priceMode',
|
||||
label: 'Price Mode',
|
||||
required: false,
|
||||
type: 'priceMode'
|
||||
},
|
||||
{
|
||||
name: 'overrideCost',
|
||||
label: 'Override Cost',
|
||||
required: false,
|
||||
type: 'bool',
|
||||
value: (objectData) => objectData?.overrideCost ?? false
|
||||
},
|
||||
{
|
||||
name: 'overridePrice',
|
||||
label: 'Override Price',
|
||||
required: false,
|
||||
type: 'bool',
|
||||
value: (objectData) => objectData?.overridePrice ?? false
|
||||
},
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) =>
|
||||
objectData?.overrideCost ? objectData?.cost : undefined
|
||||
},
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) => {
|
||||
if (!objectData?.overrideCost) return undefined
|
||||
const cost = objectData?.cost
|
||||
const taxRate = objectData?.costTaxRate
|
||||
if (!cost) return undefined
|
||||
if (taxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(cost * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
|
||||
)
|
||||
} else if (taxRate?.rateType == 'amount') {
|
||||
return (cost + taxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
return cost
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) =>
|
||||
objectData?.overrideCost ? objectData?.costTaxRate : undefined
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: 'Price',
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.1,
|
||||
disabled: (objectData) => !objectData?.overridePrice,
|
||||
readOnly: (objectData) =>
|
||||
objectData?.overridePrice && objectData?.priceMode == 'margin',
|
||||
value: (objectData) => {
|
||||
if (!objectData?.overridePrice) return undefined
|
||||
const priceMode = objectData?.priceMode ?? objectData?.product?.priceMode
|
||||
const cost = objectData?.overrideCost
|
||||
? objectData?.cost
|
||||
: objectData?.product?.cost
|
||||
const margin = objectData?.margin ?? objectData?.product?.margin
|
||||
if (
|
||||
priceMode == 'margin' &&
|
||||
margin !== undefined &&
|
||||
margin !== null &&
|
||||
cost != null
|
||||
) {
|
||||
return (
|
||||
(cost * (1 + margin / 100)).toFixed(2) || undefined
|
||||
)
|
||||
}
|
||||
return objectData?.price
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'priceWithTax',
|
||||
label: 'Price w/ Tax',
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
disabled: (objectData) => !objectData?.overridePrice,
|
||||
value: (objectData) => {
|
||||
if (!objectData?.overridePrice) return undefined
|
||||
let price
|
||||
const priceMode = objectData?.priceMode ?? objectData?.product?.priceMode
|
||||
const cost = objectData?.overrideCost
|
||||
? objectData?.cost
|
||||
: objectData?.product?.cost
|
||||
const margin = objectData?.margin ?? objectData?.product?.margin
|
||||
if (
|
||||
priceMode == 'margin' &&
|
||||
margin != null &&
|
||||
cost != null
|
||||
) {
|
||||
price = cost * (1 + margin / 100)
|
||||
} else {
|
||||
price = objectData?.price
|
||||
}
|
||||
if (price == null) return undefined
|
||||
const taxRate = objectData?.priceTaxRate ?? objectData?.product?.priceTaxRate
|
||||
if (taxRate?.rateType == 'percentage') {
|
||||
return (
|
||||
(price * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
|
||||
)
|
||||
} else if (taxRate?.rateType == 'amount') {
|
||||
return (price + taxRate?.rate).toFixed(2) || undefined
|
||||
}
|
||||
return price
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'margin',
|
||||
label: 'Margin',
|
||||
required: false,
|
||||
type: 'number',
|
||||
disabled: (objectData) =>
|
||||
!objectData?.overridePrice || objectData?.priceMode == 'amount',
|
||||
suffix: '%',
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 0.01,
|
||||
value: (objectData) =>
|
||||
objectData?.overridePrice ? objectData?.margin : undefined
|
||||
},
|
||||
{
|
||||
name: 'priceTaxRate',
|
||||
label: 'Price Tax Rate',
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
disabled: (objectData) => !objectData?.overridePrice,
|
||||
value: (objectData) =>
|
||||
objectData?.overridePrice ? objectData?.priceTaxRate : undefined
|
||||
},
|
||||
{
|
||||
name: 'parts',
|
||||
label: 'Parts',
|
||||
type: 'objectChildren',
|
||||
objectType: 'partSku',
|
||||
properties: [
|
||||
{
|
||||
name: 'partSku',
|
||||
label: 'Part SKU',
|
||||
type: 'object',
|
||||
objectType: 'partSku',
|
||||
required: true,
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
label: 'Quantity',
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
rollups: [
|
||||
{
|
||||
name: 'totalQuantity',
|
||||
label: 'Total',
|
||||
type: 'number',
|
||||
property: 'quantity',
|
||||
value: (objectData) => {
|
||||
return objectData?.parts?.reduce(
|
||||
(acc, part) => acc + part.quantity,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -85,13 +85,13 @@ export const ProductStock = {
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/inventory/productstocks/info?productStockId=${id}`,
|
||||
filters: ['_id', 'product', 'currentQuantity'],
|
||||
sorters: ['product', 'currentQuantity'],
|
||||
filters: ['_id', 'productSku', 'currentQuantity'],
|
||||
sorters: ['productSku', 'currentQuantity'],
|
||||
columns: [
|
||||
'_reference',
|
||||
'state',
|
||||
'currentQuantity',
|
||||
'product',
|
||||
'productSku',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
@ -130,10 +130,10 @@ export const ProductStock = {
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'product',
|
||||
label: 'Product',
|
||||
name: 'productSku',
|
||||
label: 'Product SKU',
|
||||
type: 'object',
|
||||
objectType: 'product',
|
||||
objectType: 'productSku',
|
||||
required: true,
|
||||
showHyperlink: true
|
||||
},
|
||||
@ -151,10 +151,10 @@ export const ProductStock = {
|
||||
canAddRemove: false,
|
||||
properties: [
|
||||
{
|
||||
name: 'part',
|
||||
label: 'Part',
|
||||
name: 'partSku',
|
||||
label: 'Part SKU',
|
||||
type: 'object',
|
||||
objectType: 'part',
|
||||
objectType: 'partSku',
|
||||
readOnly: true,
|
||||
required: true,
|
||||
showHyperlink: true
|
||||
@ -167,9 +167,9 @@ export const ProductStock = {
|
||||
required: true,
|
||||
showHyperlink: true,
|
||||
masterFilter: (objectData) => {
|
||||
const partId = objectData?.part?._id
|
||||
if (partId == null) return {}
|
||||
return { 'part._id': partId }
|
||||
const partSkuId = objectData?.partSku?._id
|
||||
if (partSkuId == null) return {}
|
||||
return { 'partSku._id': partSkuId }
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -3,8 +3,12 @@ import { Route } from 'react-router-dom'
|
||||
|
||||
const Filaments = lazy(() => import('../components/Dashboard/Management/Filaments'))
|
||||
const FilamentInfo = lazy(() => import('../components/Dashboard/Management/Filaments/FilamentInfo.jsx'))
|
||||
const FilamentSkus = lazy(() => import('../components/Dashboard/Management/FilamentSkus.jsx'))
|
||||
const FilamentSkuInfo = lazy(() => import('../components/Dashboard/Management/FilamentSkus/FilamentSkuInfo.jsx'))
|
||||
const Parts = lazy(() => import('../components/Dashboard/Management/Parts.jsx'))
|
||||
const PartInfo = lazy(() => import('../components/Dashboard/Management/Parts/PartInfo.jsx'))
|
||||
const PartSkus = lazy(() => import('../components/Dashboard/Management/PartSkus.jsx'))
|
||||
const PartSkuInfo = lazy(() => import('../components/Dashboard/Management/PartSkus/PartSkuInfo.jsx'))
|
||||
const Products = lazy(() => import('../components/Dashboard/Management/Products.jsx'))
|
||||
const ProductInfo = lazy(() => import('../components/Dashboard/Management/Products/ProductInfo.jsx'))
|
||||
const ProductSkus = lazy(() => import('../components/Dashboard/Management/ProductSkus.jsx'))
|
||||
@ -12,6 +16,7 @@ const ProductSkuInfo = lazy(() => import('../components/Dashboard/Management/Pro
|
||||
const Vendors = lazy(() => import('../components/Dashboard/Management/Vendors'))
|
||||
const VendorInfo = lazy(() => import('../components/Dashboard/Management/Vendors/VendorInfo'))
|
||||
const Materials = lazy(() => import('../components/Dashboard/Management/Materials'))
|
||||
const MaterialInfo = lazy(() => import('../components/Dashboard/Management/Materials/MaterialInfo.jsx'))
|
||||
const Couriers = lazy(() => import('../components/Dashboard/Management/Couriers'))
|
||||
const CourierInfo = lazy(() => import('../components/Dashboard/Management/Couriers/CourierInfo.jsx'))
|
||||
const CourierServices = lazy(() => import('../components/Dashboard/Management/CourierServices'))
|
||||
@ -50,12 +55,24 @@ const ManagementRoutes = [
|
||||
path='management/filaments/info'
|
||||
element={<FilamentInfo />}
|
||||
/>,
|
||||
<Route key='filamentskus' path='management/filamentskus' element={<FilamentSkus />} />,
|
||||
<Route
|
||||
key='filamentskus-info'
|
||||
path='management/filamentskus/info'
|
||||
element={<FilamentSkuInfo />}
|
||||
/>,
|
||||
<Route key='parts' path='management/parts' element={<Parts />} />,
|
||||
<Route
|
||||
key='parts-info'
|
||||
path='management/parts/info'
|
||||
element={<PartInfo />}
|
||||
/>,
|
||||
<Route key='partskus' path='management/partskus' element={<PartSkus />} />,
|
||||
<Route
|
||||
key='partskus-info'
|
||||
path='management/partskus/info'
|
||||
element={<PartSkuInfo />}
|
||||
/>,
|
||||
<Route key='products' path='management/products' element={<Products />} />,
|
||||
<Route
|
||||
key='products-info'
|
||||
@ -92,6 +109,11 @@ const ManagementRoutes = [
|
||||
element={<FileInfo />}
|
||||
/>,
|
||||
<Route key='materials' path='management/materials' element={<Materials />} />,
|
||||
<Route
|
||||
key='materials-info'
|
||||
path='management/materials/info'
|
||||
element={<MaterialInfo />}
|
||||
/>,
|
||||
<Route key='couriers' path='management/couriers' element={<Couriers />} />,
|
||||
<Route
|
||||
key='couriers-info'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user