Compare commits

..

No commits in common. "84e98aa62684dfbf5be9b79b7c4f8b952e6aedda" and "24502dddfca61a334b3ff4dc11bd9fffece91b7f" have entirely different histories.

89 changed files with 504 additions and 3964 deletions

View File

@ -1,9 +0,0 @@
<?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.889695,0,0,0.889695,3,2.383378)">
<path d="M0,33.052C0,45.309 6.996,56.09 16.234,61.178C20.659,63.638 24.162,57.594 19.732,55.013C12.395,50.857 7.182,42.577 7.182,33.052C7.182,18.948 18.465,7.622 32.569,7.622C46.673,7.622 58.03,18.948 58.03,33.052C58.03,35.325 59.805,36.728 61.717,36.728C63.565,36.728 65.191,35.303 65.191,33.052C65.191,15.177 50.444,0.462 32.569,0.462C14.726,0.462 0,15.177 0,33.052Z" style="fill-rule:nonzero;"/>
<path d="M13.589,33.042C13.589,39.892 17.302,45.715 21.556,48.454C25.359,50.937 28.902,45.774 25.59,43.286C22.321,41.043 20.176,37.305 20.176,33.042C20.176,26.121 25.701,20.596 32.579,20.596C39.458,20.596 44.753,26.1 45.025,32.804C45.127,34.798 46.618,36.042 48.354,36.042C50.099,36.042 51.612,34.718 51.612,32.764C51.612,22.599 43.022,14.009 32.579,14.009C22.178,14.009 13.589,22.599 13.589,33.042Z" style="fill-rule:nonzero;"/>
<path d="M47.066,65.874C48.64,65.243 49.395,63.472 48.712,61.887L44.195,51.408L50.357,51.285C51.793,51.304 52.485,49.84 51.462,48.796L34.618,31.611C33.647,30.639 32.221,31.207 32.19,32.582L31.859,56.934C31.817,58.42 33.513,58.935 34.483,57.946L38.738,53.572L43.036,64.259C43.647,65.781 45.481,66.537 47.066,65.874Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,13 +0,0 @@
<?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.993756,0,0,0.993756,4.70027,3)">
<path d="M21.917,58.364L7.977,58.364C2.598,58.364 0,56.673 0,53.004C0,45.534 8.171,35.818 21.434,33.487L21.434,38.985C11.428,41.086 5.926,48.099 5.926,52.158C5.926,52.741 6.224,52.965 6.958,52.965L21.434,52.965L21.434,54.927C21.434,56.202 21.603,57.347 21.917,58.364ZM22.29,28.195C17.315,25.985 13.813,20.695 13.813,14.529C13.813,6.508 19.979,0 27.481,0C35.04,0 41.156,6.405 41.156,14.478C41.156,17.712 40.199,20.706 38.573,23.144L30.955,23.144C30.844,23.144 30.734,23.146 30.625,23.148C33.464,21.732 35.494,18.445 35.494,14.478C35.494,9.257 31.873,5.399 27.481,5.399C23.126,5.399 19.474,9.334 19.474,14.524C19.474,19.579 22.791,23.478 26.82,23.866C25.711,24.303 24.771,24.923 23.999,25.691C23.298,26.389 22.72,27.223 22.29,28.195Z"/>
<g transform="matrix(0.589451,0,0,0.589451,24.45243,26.163338)">
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
<g transform="matrix(0.570497,0,0,0.570497,16.522802,13.725853)">
<path d="M6.499,56.236L41.124,56.236C44.294,56.236 46.809,54.281 46.809,50.825C46.809,47.443 44.391,45.464 41.124,45.464L18.117,45.464L18.117,45.062C22.049,43.001 23.774,38.335 23.774,33.596C23.774,32.751 23.669,32.057 23.534,31.399L37.509,31.399C39.584,31.399 41.06,30.032 41.06,28.125C41.06,26.241 39.584,24.905 37.509,24.905L22.134,24.905C21.788,23.604 21.265,21.585 21.265,19.395C21.265,13.243 26.441,10.758 32.681,10.758C34.774,10.758 36.433,10.942 37.882,11.247C38.957,11.394 40.282,11.543 41.544,11.543C44.007,11.543 46.13,10.334 46.13,7.354C46.13,5.297 45.211,3.871 43.447,2.745C39.934,0.628 34.458,0.378 30.367,0.378C18.274,0.378 7.903,5.899 7.903,17.414C7.903,19.396 8.212,21.38 9.112,24.905L3.574,24.905C1.5,24.905 0,26.241 0,28.125C0,30.071 1.514,31.399 3.574,31.399L10.467,31.399C10.73,32.486 10.797,33.332 10.797,34.157C10.797,38.814 8.74,42.769 5.065,44.806C3.057,46.118 0.808,47.931 0.808,50.875C0.808,54.298 3.219,56.236 6.499,56.236Z" style="fill-rule:nonzero;"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,10 +1 @@
<?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>
<g transform="matrix(0.5364,0,0,0.5364,20.017716,17.05902)">
<path d="M4.547,55.679L40.65,55.679C42.9,55.679 44.677,54.281 44.677,51.912C44.677,49.519 42.886,48.112 40.65,48.112L13.379,48.112L13.379,47.835C18.007,45.956 20.276,40.62 20.276,35.255C20.276,33.462 20.031,31.987 19.701,30.619L35.224,30.619C36.908,30.619 38.161,29.503 38.161,27.888C38.161,26.283 36.908,25.198 35.224,25.198L18.371,25.198C17.843,23.27 17.28,20.86 17.28,18.015C17.28,10.985 22.845,7.524 30.131,7.524C32.516,7.524 34.83,7.82 36.656,8.348C37.578,8.537 38.512,8.672 39.384,8.672C41.303,8.672 42.716,7.658 42.716,5.542C42.716,3.792 41.713,2.742 40.158,1.909C36.798,0.28 32.549,0.03 28.876,0.03C17.257,0.03 8.014,5.885 8.014,17.428C8.014,19.73 8.435,21.979 9.266,25.198L2.947,25.198C1.263,25.198 0,26.283 0,27.888C0,29.555 1.305,30.619 2.947,30.619L10.523,30.619C11.107,32.695 11.299,34.196 11.299,35.634C11.299,41.002 8.545,45.64 3.477,47.51C1.747,48.347 0.209,49.631 0.209,51.878C0.209,54.27 1.978,55.679 4.547,55.679Z" style="fill-rule:nonzero;"/>
</g>
<path d="M12.892,61L51.107,61C57.723,61 60.999,57.755 60.999,51.265L60.999,12.766C60.999,6.276 57.723,3 51.107,3L12.892,3C6.308,3 3,6.276 3,12.766L3,51.265C3,57.755 6.308,61 12.892,61ZM12.955,55.928C9.805,55.928 8.072,54.258 8.072,50.982L8.072,13.05C8.072,9.774 9.805,8.072 12.955,8.072L51.044,8.072C54.163,8.072 55.927,9.773 55.927,13.05L55.927,50.982C55.927,54.258 54.163,55.928 51.044,55.928L12.955,55.928Z" style="fill-rule:nonzero;"/>
</g>
</svg>
<?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><path d="M29.953,40.788l10.342,0c1.761,0 3.18,1.258 3.18,3.055c0,1.788 -1.349,3.03 -3.18,3.03l-18.004,0c-1.785,0 -3.086,-1.234 -3.086,-3.075c0,-1.581 0.953,-2.598 2.336,-3.193l0.024,-0.01c2.054,-0.833 3.153,-2.551 3.153,-4.757c0,-0.425 -0.052,-0.856 -0.133,-1.305l-2.8,0c-1.588,0 -2.701,-1.131 -2.701,-2.591c0,-1.408 1.105,-2.56 2.701,-2.56l1.544,-0c-0.168,-0.87 -0.242,-1.672 -0.242,-2.485c0,-6.288 4.756,-9.887 11.404,-9.887c2.234,0 3.578,0.148 5.274,0.766c1.53,0.469 2.646,1.469 2.646,3.106c0,0.878 -0.321,1.553 -0.828,2.023c-0.48,0.446 -1.159,0.725 -2.011,0.725c-0.479,0 -1.089,-0.115 -1.715,-0.248l-0.045,-0.01c-0.697,-0.176 -1.631,-0.301 -2.775,-0.301c-2.932,0 -5.078,1.33 -5.078,3.98c0,0.734 0.074,1.394 0.272,2.332l7.292,0c1.585,0 2.701,1.163 2.701,2.56c0,1.449 -1.124,2.591 -2.701,2.591l-6.16,0c0.024,0.361 0.034,0.743 0.034,1.147c0,1.832 -0.479,3.671 -1.444,5.108Z"/><path d="M12.892,61l38.215,0c6.616,0 9.892,-3.245 9.892,-9.735l0,-38.499c0,-6.49 -3.276,-9.766 -9.892,-9.766l-38.215,-0c-6.584,0 -9.892,3.276 -9.892,9.766l0,38.499c0,6.49 3.308,9.735 9.892,9.735Zm0.063,-5.072c-3.15,0 -4.883,-1.67 -4.883,-4.946l0,-37.932c0,-3.276 1.733,-4.978 4.883,-4.978l38.089,0c3.119,0 4.883,1.701 4.883,4.978l0,37.932c0,3.276 -1.764,4.946 -4.883,4.946l-38.089,0Z" style="fill-rule:nonzero;"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g><rect x="9.624" y="3.845" width="44.752" height="56.309" style="fill-opacity:0;"/><path d="M26.257,60.138l-8.186,-0c-5.571,0 -8.446,-2.897 -8.446,-8.515l0,-39.242c0,-5.597 2.897,-8.536 8.446,-8.536l12.536,0c3.245,0 5.052,0.512 7.225,2.733l13.66,13.874c1.791,1.825 2.456,3.244 2.655,5.548l-18.685,0c-1.881,0 -3.481,0.368 -4.804,1.026c-0.718,-0.905 -1.073,-2.175 -1.073,-3.822l0,-14.263l-11.086,0c-2.532,0 -3.779,1.356 -3.779,3.758l0,38.608c0,2.423 1.247,3.736 3.758,3.736l7.523,0l0,2.541c0,0.919 0.089,1.77 0.256,2.554Zm9.331,-35.907l12.829,0l-14.339,-14.583l0,13.051c0,1.073 0.432,1.531 1.51,1.531Z"/><path d="M40.85,55.833l11.571,0c1.059,0 1.9,-0.653 1.9,-1.808c0,-1.13 -0.808,-1.792 -1.9,-1.792l-7.688,0l0,-0.134c1.314,-0.689 1.89,-2.248 1.89,-3.831c0,-0.282 -0.035,-0.515 -0.08,-0.734l4.67,0c0.693,0 1.187,-0.457 1.187,-1.094c0,-0.629 -0.493,-1.076 -1.187,-1.076l-5.138,0c-0.116,-0.435 -0.29,-1.109 -0.29,-1.841c0,-2.056 1.729,-2.886 3.815,-2.886c0.699,0 1.254,0.061 1.738,0.164c0.359,0.049 0.802,0.099 1.224,0.099c0.823,0 1.533,-0.404 1.533,-1.4c0,-0.687 -0.307,-1.164 -0.897,-1.54c-1.174,-0.707 -3.004,-0.791 -4.371,-0.791c-4.041,0 -7.507,1.845 -7.507,5.693c0,0.662 0.103,1.325 0.404,2.503l-1.851,0c-0.693,0 -1.194,0.446 -1.194,1.076c0,0.65 0.506,1.094 1.194,1.094l2.303,0c0.088,0.363 0.11,0.646 0.11,0.921c0,1.556 -0.687,2.878 -1.915,3.559c-0.671,0.438 -1.423,1.044 -1.423,2.028c0,1.144 0.806,1.792 1.902,1.792Z" style="fill-rule:nonzero;"/><g><path d="M35.462,64l22.077,0c4.182,0 6.462,-2.26 6.462,-6.416l0,-22.159c0,-4.146 -2.279,-6.425 -6.462,-6.425l-22.077,0c-4.174,0 -6.462,2.279 -6.462,6.425l0,22.159c0,4.155 2.288,6.416 6.462,6.416Zm0.513,-4.722c-1.473,0 -2.251,-0.714 -2.251,-2.27l0,-20.996c0,-1.556 0.778,-2.279 2.251,-2.279l21.051,0c1.464,0 2.252,0.723 2.252,2.279l0,20.996c0,1.556 -0.787,2.27 -2.252,2.27l-21.051,0Z" style="fill-rule:nonzero;"/></g></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.58577,0,0,0.58577,29,29)">
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
<g transform="matrix(0.664312,0,0,0.664312,5.121464,12.135807)">
<path d="M37.594,52.969C39.062,52.969 40.625,52.281 41.875,51L61.844,31.094C62.938,30 63.656,28.188 63.656,26.5C63.656,24.812 62.938,23 61.844,21.906L41.875,1.969C40.625,0.688 39.062,0 37.594,0C33.812,0 31.406,2.562 31.406,5.906C31.406,7.875 32.312,9.25 33.5,10.406L40.5,17.344L50.219,26.5L40.5,35.656L33.5,42.562C32.312,43.688 31.406,45.094 31.406,47.062C31.406,50.406 33.812,52.969 37.594,52.969ZM1.485,32.781L37.75,32.781L51.969,32.094C55.531,31.938 57.906,29.844 57.906,26.5C57.906,23.156 55.531,21.062 51.969,20.906L37.75,20.219L1.485,20.219C-2.515,20.219 -5.14,22.719 -5.14,26.5C-5.14,30.281 -2.515,32.781 1.485,32.781Z" style="fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(0.739137,0,0,0.739137,-0,2.611674)">
<path d="M35.853,52.047L25.734,52.047C20.578,52.047 17.797,48.969 17.047,43.844L12.484,12.719L3.906,12.719C1.812,12.719 0,10.906 0,8.766C0,6.625 1.812,4.828 3.906,4.828L14.172,4.828C18.125,4.828 19.609,6.437 20.047,9.625L20.402,12.109L69.062,12.109C71.922,12.109 73.438,13.75 73.438,16.031C73.438,16.5 73.344,17.062 73.281,17.516L71.172,31.781C71.146,31.963 71.118,32.143 71.087,32.319L63.084,32.319C63.272,32.028 63.394,31.657 63.453,31.219L65.078,19.094L21.4,19.094L23.397,33.062L43.157,33.063C41.542,33.617 40.191,34.464 39.103,35.547C37.914,36.73 37.007,38.224 36.457,40.031L24.393,40.031L24.844,43.188C25,44.312 25.656,45.094 26.719,45.094L35.853,45.094L35.853,52.047ZM28.422,68.437C25.078,68.437 22.359,65.766 22.359,62.391C22.359,59.063 25.078,56.359 28.422,56.359C31.781,56.359 34.453,59.063 34.453,62.391C34.453,65.766 31.781,68.437 28.422,68.437Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.58577,0,0,0.58577,29.000019,28.999974)">
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
<g transform="matrix(0.570497,0,0,0.570497,16.522802,13.725853)">
<path d="M6.499,56.236L41.124,56.236C44.294,56.236 46.809,54.281 46.809,50.825C46.809,47.443 44.391,45.464 41.124,45.464L18.117,45.464L18.117,45.062C22.049,43.001 23.774,38.335 23.774,33.596C23.774,32.751 23.669,32.057 23.534,31.399L37.509,31.399C39.584,31.399 41.06,30.032 41.06,28.125C41.06,26.241 39.584,24.905 37.509,24.905L22.134,24.905C21.788,23.604 21.265,21.585 21.265,19.395C21.265,13.243 26.441,10.758 32.681,10.758C34.774,10.758 36.433,10.942 37.882,11.247C38.957,11.394 40.282,11.543 41.544,11.543C44.007,11.543 46.13,10.334 46.13,7.354C46.13,5.297 45.211,3.871 43.447,2.745C39.934,0.628 34.458,0.378 30.367,0.378C18.274,0.378 7.903,5.899 7.903,17.414C7.903,19.396 8.212,21.38 9.112,24.905L3.574,24.905C1.5,24.905 0,26.241 0,28.125C0,30.071 1.514,31.399 3.574,31.399L10.467,31.399C10.73,32.486 10.797,33.332 10.797,34.157C10.797,38.814 8.74,42.769 5.065,44.806C3.057,46.118 0.808,47.931 0.808,50.875C0.808,54.298 3.219,56.236 6.499,56.236Z" style="fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,11.133834,5.394156)">
<rect x="0" y="0" width="74.451" height="58.962" style="fill-opacity:0;"/>
<g transform="matrix(0.700206,0,0,0.700206,-5.133834,5.956054)">
<path d="M28.563,58.962L10.588,58.962C3.679,58.962 0,55.326 0,48.481L0,10.522C0,3.667 3.679,0.02 10.588,0.02L63.676,0.02C70.606,0.02 74.264,3.667 74.264,10.522L74.264,20.933C74.047,20.926 73.827,20.922 73.604,20.922L67.305,20.922L67.305,11.251C67.305,8.378 65.813,6.979 63.097,6.979L11.167,6.979C8.429,6.979 6.959,8.378 6.959,11.251L6.959,47.743C6.959,50.615 8.429,52.003 11.167,52.003L28.563,52.003L28.563,58.962ZM28.857,31.097L16.175,31.097C14.981,31.097 14.107,30.204 14.107,29.051C14.107,27.929 14.981,27.055 16.175,27.055L30.296,27.055C29.634,28.235 29.144,29.582 28.857,31.097ZM16.175,19.207C14.981,19.207 14.107,18.313 14.107,17.138C14.107,16.016 14.981,15.164 16.175,15.164L58.13,15.164C59.295,15.164 60.157,16.016 60.157,17.138C60.157,18.313 59.295,19.207 58.13,19.207L16.175,19.207Z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1,13 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.822372,0,0,0.822372,2,2.325593)">
<path d="M25.615,51.541L61.925,51.541C63.571,51.541 65.041,50.302 65.041,48.462C65.041,46.631 63.571,45.412 61.925,45.412L26.434,45.412C25.11,45.412 24.295,44.46 24.093,43.051L19.177,9.045C18.785,6.142 17.522,4.675 13.848,4.675L3.326,4.675C1.539,4.675 0,6.215 0,8.032C0,9.86 1.539,11.411 3.326,11.411L12.627,11.411L17.348,43.696C18.064,48.571 20.686,51.541 25.615,51.541ZM20.362,39.849L62.285,39.849C67.216,39.849 69.857,36.878 70.563,31.952L72.815,16.861C72.877,16.425 72.96,15.886 72.96,15.485C72.96,13.488 71.586,12.07 69.142,12.07L17.504,12.07L17.525,18.229L65.71,18.229L63.868,31.389C63.678,32.83 62.925,33.711 61.558,33.711L20.319,33.711L20.362,39.849ZM28.223,67.493C31.383,67.493 33.901,64.986 33.901,61.805C33.901,58.664 31.383,56.126 28.223,56.126C25.061,56.126 22.513,58.664 22.513,61.805C22.513,64.986 25.061,67.493 28.223,67.493ZM56.875,67.493C60.047,67.493 62.575,64.986 62.575,61.805C62.575,58.664 60.047,56.126 56.875,56.126C53.735,56.126 51.175,58.664 51.175,61.805C51.175,64.986 53.735,67.493 56.875,67.493Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.754667,0,0,0.754667,0,25.090254)">
<g transform="matrix(1.26871,0,0,1.26871,0,-2.460294)">
<path d="M3.178,17.892L14.469,11.058C16.041,10.11 16.004,8.217 14.469,7.275L3.178,0.415C1.563,-0.576 0,0.29 0,2.137L0,16.19C0,18.069 1.569,18.872 3.178,17.892Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.842778,0,0,0.842778,34.449623,-16.022037)">
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
<path d="M11.031,59.75C3.906,59.75 0,55.891 0,48.797L0,10.969C0,3.891 3.906,0 11.031,0L48.719,0C55.859,0 59.75,3.891 59.75,10.969L59.75,48.797C59.75,55.891 55.859,59.75 48.719,59.75L11.031,59.75ZM11.906,51.688L47.844,51.688C50.344,51.688 51.688,50.469 51.688,47.813L51.688,11.969C51.688,9.313 50.344,8.078 47.844,8.078L11.906,8.078C9.391,8.078 8.063,9.313 8.063,11.969L8.063,47.813C8.063,50.469 9.391,51.688 11.906,51.688Z"/>
</g>
<path d="M3.178,17.892L14.469,11.058C16.041,10.11 16.004,8.217 14.469,7.275L3.178,0.415C1.563,-0.576 0,0.29 0,2.137L0,16.19C0,18.069 1.569,18.872 3.178,17.892ZM25.758,14.461L80.772,14.461C83.437,14.461 84.806,13.117 84.806,10.453L84.806,7.792C84.806,5.153 83.437,3.784 80.772,3.784L25.758,3.784C23.094,3.784 21.725,5.153 21.725,7.792L21.725,10.453C21.725,13.117 23.094,14.461 25.758,14.461Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.81149,0,0,0.81149,2,10.044134)">
<path d="M14.864,42.322L22.52,42.322C24.364,42.322 25.582,41.078 25.582,39.328L25.582,33.547C25.582,31.766 24.364,30.547 22.52,30.547L14.864,30.547C13.02,30.547 11.801,31.766 11.801,33.547L11.801,39.328C11.801,41.078 13.02,42.322 14.864,42.322ZM3.51,20.305L70.459,20.305L70.459,12.998L3.51,12.998L3.51,20.305ZM10.251,54.093L63.687,54.093C70.463,54.093 73.938,50.638 73.938,43.969L73.938,10.169C73.938,3.501 70.463,0.02 63.687,0.02L10.251,0.02C3.506,0.02 0,3.501 0,10.169L0,43.969C0,50.638 3.506,54.093 10.251,54.093ZM10.606,47.97C7.701,47.97 6.122,46.471 6.122,43.429L6.122,10.709C6.122,7.667 7.701,6.142 10.606,6.142L63.332,6.142C66.186,6.142 67.816,7.667 67.816,10.709L67.816,43.429C67.816,46.471 66.186,47.97 63.332,47.97L10.606,47.97Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.822372,0,0,0.822372,2,2.325529)">
<path d="M25.615,51.541L61.925,51.541C63.571,51.541 65.041,50.302 65.041,48.462C65.041,46.631 63.571,45.412 61.925,45.412L26.434,45.412C25.11,45.412 24.295,44.46 24.093,43.051L19.177,9.045C18.785,6.142 17.522,4.675 13.848,4.675L3.326,4.675C1.539,4.675 0,6.215 0,8.032C0,9.86 1.539,11.411 3.326,11.411L12.627,11.411L17.348,43.696C18.064,48.571 20.686,51.541 25.615,51.541ZM20.362,39.849L62.285,39.849C67.216,39.849 69.857,36.878 70.563,31.952L72.815,16.861C72.877,16.425 72.96,15.886 72.96,15.485C72.96,13.488 71.586,12.07 69.142,12.07L17.504,12.07L17.525,18.229L65.71,18.229L63.868,31.389C63.678,32.83 62.925,33.711 61.558,33.711L20.319,33.711L20.362,39.849ZM28.223,67.493C31.383,67.493 33.901,64.986 33.901,61.805C33.901,58.664 31.383,56.126 28.223,56.126C25.061,56.126 22.513,58.664 22.513,61.805C22.513,64.986 25.061,67.493 28.223,67.493ZM56.875,67.493C60.047,67.493 62.575,64.986 62.575,61.805C62.575,58.664 60.047,56.126 56.875,56.126C53.735,56.126 51.175,58.664 51.175,61.805C51.175,64.986 53.735,67.493 56.875,67.493Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -32,7 +32,6 @@ import {
ProductionRoutes,
InventoryRoutes,
FinanceRoutes,
SalesRoutes,
ManagementRoutes,
DeveloperRoutes
} from './routes'
@ -99,7 +98,6 @@ const AppContent = () => {
{ProductionRoutes}
{InventoryRoutes}
{FinanceRoutes}
{SalesRoutes}
{ManagementRoutes}
{DeveloperRoutes}
</Route>

View File

@ -13,8 +13,7 @@ const FinanceOverview = () => {
const [collapseState, updateCollapseState] = useCollapseState(
'FinanceOverview',
{
invoiceStats: true,
paymentStats: true
invoiceStats: true
}
)
@ -42,7 +41,6 @@ const FinanceOverview = () => {
}
className='no-t-padding-collapse'
collapseKey='invoiceStats'
canCollapse={false}
>
<Flex
justify='flex-start'
@ -53,26 +51,6 @@ const FinanceOverview = () => {
<StatsDisplay objectType='invoice' />
</Flex>
</InfoCollapse>
<InfoCollapse
title='Payment Statistics'
icon={null}
active={collapseState.paymentStats}
onToggle={(isActive) =>
updateCollapseState('paymentStats', isActive)
}
className='no-t-padding-collapse'
collapseKey='paymentStats'
canCollapse={false}
>
<Flex
justify='flex-start'
gap='middle'
wrap='wrap'
align='flex-start'
>
<StatsDisplay objectType='payment' />
</Flex>
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
@ -80,3 +58,4 @@ const FinanceOverview = () => {
}
export default FinanceOverview

View File

@ -1,7 +1,6 @@
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import InvoiceIcon from '../../Icons/InvoiceIcon'
import PaymentIcon from '../../Icons/PaymentIcon'
import FinanceIcon from '../../Icons/FinanceIcon'
const items = [
@ -17,19 +16,12 @@ const items = [
label: 'Invoices',
icon: <InvoiceIcon />,
path: '/dashboard/finance/invoices'
},
{
key: 'payments',
label: 'Payments',
icon: <PaymentIcon />,
path: '/dashboard/finance/payments'
}
]
const routeKeyMap = {
'/dashboard/finance/overview': 'overview',
'/dashboard/finance/invoices': 'invoices',
'/dashboard/finance/payments': 'payments'
'/dashboard/finance/invoices': 'invoices'
}
const FinanceSidebar = (props) => {
@ -51,3 +43,4 @@ const FinanceSidebar = (props) => {
}
export default FinanceSidebar

View File

@ -1,47 +0,0 @@
import { useState, useContext } from 'react'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../../context/ApiServerContext'
import { message } from 'antd'
import MessageDialogView from '../../common/MessageDialogView.jsx'
const AcknowledgeInvoice = ({ onOk, objectData }) => {
const [acknowledgeLoading, setAcknowledgeLoading] = useState(false)
const { sendObjectFunction } = useContext(ApiServerContext)
const handleAcknowledge = async () => {
setAcknowledgeLoading(true)
try {
const result = await sendObjectFunction(
objectData._id,
'Invoice',
'acknowledge'
)
if (result) {
message.success('Invoice acknowledged successfully')
onOk(result)
}
} catch (error) {
console.error('Error acknowledging invoice:', error)
} finally {
setAcknowledgeLoading(false)
}
}
return (
<MessageDialogView
title={'Are you sure you want to acknowledge this invoice?'}
description={`Acknowledging invoice ${objectData?.name || objectData?._reference || objectData?._id} will update its status to acknowledged.`}
onOk={handleAcknowledge}
okText='Acknowledge'
okLoading={acknowledgeLoading}
/>
)
}
AcknowledgeInvoice.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default AcknowledgeInvoice

View File

@ -1,6 +1,6 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card, Modal } from 'antd'
import { Space, Flex, Card } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
@ -8,11 +8,9 @@ import useCollapseState from '../../hooks/useCollapseState.js'
import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx'
import ObjectInfo from '../../common/ObjectInfo.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import PaymentIcon from '../../../Icons/PaymentIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx'
@ -23,15 +21,7 @@ import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import {
getModelByName,
getModelProperty
} from '../../../../database/ObjectModels.js'
import OrderItemIcon from '../../../Icons/OrderItemIcon.jsx'
import ShipmentIcon from '../../../Icons/ShipmentIcon.jsx'
import PostInvoice from './PostInvoice.jsx'
import AcknowledgeInvoice from './AcknowledgeInvoice.jsx'
import NewPayment from '../Payments/NewPayment.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js'
const log = loglevel.getLogger('InvoiceInfo')
log.setLevel(config.logLevel)
@ -41,14 +31,14 @@ const InvoiceInfo = () => {
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const invoiceId = new URLSearchParams(location.search).get('invoiceId')
const [collapseState, updateCollapseState] = useCollapseState('InvoiceInfo', {
info: true,
invoiceOrderItems: true,
invoiceShipments: true,
payments: true,
notes: true,
auditLogs: true
})
const [collapseState, updateCollapseState] = useCollapseState(
'InvoiceInfo',
{
info: true,
notes: true,
auditLogs: true
}
)
const [objectFormState, setEditFormState] = useState({
isEditing: false,
@ -58,9 +48,6 @@ const InvoiceInfo = () => {
loading: false,
objectData: {}
})
const [postInvoiceOpen, setPostInvoiceOpen] = useState(false)
const [acknowledgeInvoiceOpen, setAcknowledgeInvoiceOpen] = useState(false)
const [newPaymentOpen, setNewPaymentOpen] = useState(false)
const actions = {
reload: () => {
@ -82,25 +69,12 @@ const InvoiceInfo = () => {
delete: () => {
objectFormRef?.current?.handleDelete?.()
return true
},
post: () => {
setPostInvoiceOpen(true)
return true
},
acknowledge: () => {
setAcknowledgeInvoiceOpen(true)
return true
},
newPayment: () => {
setNewPaymentOpen(true)
return true
}
}
const editDisabled =
getModelByName('invoice')
?.actions?.find((action) => action.name === 'edit')
?.disabled(objectFormState.objectData) ?? false
const editDisabled = getModelByName('invoice')
?.actions?.find((action) => action.name === 'edit')
?.disabled(objectFormState.objectData) ?? false
return (
<>
@ -125,9 +99,6 @@ const InvoiceInfo = () => {
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Invoice Information' },
{ key: 'invoiceOrderItems', label: 'Invoice Order Items' },
{ key: 'invoiceShipments', label: 'Invoice Shipments' },
{ key: 'payments', label: 'Payments' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
@ -200,67 +171,12 @@ const InvoiceInfo = () => {
type='invoice'
labelWidth='225px'
objectData={objectData}
visibleProperties={{
invoiceOrderItems: false,
invoiceShipments: false
}}
/>
</InfoCollapse>
<InfoCollapse
title='Invoice Order Items'
icon={<OrderItemIcon />}
active={collapseState.invoiceOrderItems}
onToggle={(expanded) =>
updateCollapseState('invoiceOrderItems', expanded)
}
collapseKey='invoiceOrderItems'
>
<ObjectProperty
{...getModelProperty('invoice', 'invoiceOrderItems')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
size='medium'
/>
</InfoCollapse>
<InfoCollapse
title='Invoice Shipments'
icon={<ShipmentIcon />}
active={collapseState.invoiceShipments}
onToggle={(expanded) =>
updateCollapseState('invoiceShipments', expanded)
}
collapseKey='invoiceShipments'
>
<ObjectProperty
{...getModelProperty('invoice', 'invoiceShipments')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
size='medium'
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Payments'
icon={<PaymentIcon />}
active={collapseState.payments}
onToggle={(expanded) => updateCollapseState('payments', expanded)}
collapseKey='payments'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='payment'
masterFilter={{ 'invoice._id': invoiceId }}
visibleColumns={{ invoice: false }}
/>
)}
</InfoCollapse>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
@ -294,66 +210,9 @@ const InvoiceInfo = () => {
</Flex>
</ScrollBox>
</Flex>
<Modal
open={postInvoiceOpen}
onCancel={() => {
setPostInvoiceOpen(false)
}}
width={500}
footer={null}
destroyOnHidden={true}
centered={true}
>
<PostInvoice
onOk={() => {
setPostInvoiceOpen(false)
actions.reload()
}}
objectData={objectFormState.objectData}
/>
</Modal>
<Modal
open={acknowledgeInvoiceOpen}
onCancel={() => {
setAcknowledgeInvoiceOpen(false)
}}
width={515}
footer={null}
destroyOnHidden={true}
centered={true}
>
<AcknowledgeInvoice
onOk={() => {
setAcknowledgeInvoiceOpen(false)
actions.reload()
}}
objectData={objectFormState.objectData}
/>
</Modal>
<Modal
open={newPaymentOpen}
styles={{ content: { paddingBottom: '24px' } }}
footer={null}
width={800}
onCancel={() => {
setNewPaymentOpen(false)
}}
destroyOnHidden={true}
>
<NewPayment
onOk={() => {
setNewPaymentOpen(false)
actions.reload()
}}
reset={newPaymentOpen}
defaultValues={{
invoice: { ...objectFormState.objectData },
amount: objectFormState.objectData?.grandTotalAmount
}}
/>
</Modal>
</>
)
}
export default InvoiceInfo

View File

@ -1,5 +1,4 @@
import PropTypes from 'prop-types'
import dayjs from 'dayjs'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
@ -11,8 +10,7 @@ const NewInvoice = ({ onOk, reset, defaultValues }) => {
reset={reset}
defaultValues={{
state: { type: 'draft' },
issuedAt: new Date(),
dueAt: dayjs().add(3, 'day').toDate(),
invoiceType: 'sales',
...defaultValues
}}
>
@ -32,10 +30,27 @@ const NewInvoice = ({ onOk, reset, defaultValues }) => {
visibleProperties={{
orderType: true,
order: true,
to: true,
from: true,
issuedAt: true,
dueAt: true
vendor: true,
invoiceDate: true,
dueDate: true
}}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='invoice'
column={1}
bordered={false}
isEditing={true}
required={false}
objectData={objectData}
visibleProperties={{
relatedOrderType: true,
relatedOrder: true
}}
/>
)
@ -51,8 +66,6 @@ const NewInvoice = ({ onOk, reset, defaultValues }) => {
visibleProperties={{
_id: false,
createdAt: false,
invoiceOrderItems: false,
invoiceShipments: false,
updatedAt: false,
_reference: false,
totalAmount: false,

View File

@ -1,42 +0,0 @@
import { useState, useContext } from 'react'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../../context/ApiServerContext'
import { message } from 'antd'
import MessageDialogView from '../../common/MessageDialogView.jsx'
const PostInvoice = ({ onOk, objectData }) => {
const [postLoading, setPostLoading] = useState(false)
const { sendObjectFunction } = useContext(ApiServerContext)
const handlePost = async () => {
setPostLoading(true)
try {
const result = await sendObjectFunction(objectData._id, 'Invoice', 'post')
if (result) {
message.success('Invoice posted successfully')
onOk(result)
}
} catch (error) {
console.error('Error posting invoice:', error)
} finally {
setPostLoading(false)
}
}
return (
<MessageDialogView
title={'Are you sure you want to post this invoice?'}
description={`Posting invoice ${objectData?.name || objectData?._reference || objectData?._id} will set it to sent status.`}
onOk={handlePost}
okText='Post'
okLoading={postLoading}
/>
)
}
PostInvoice.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default PostInvoice

View File

@ -1,99 +0,0 @@
import { useState, useRef } from 'react'
import { Button, Flex, Space, Dropdown, Modal } from 'antd'
import NewPayment from './Payments/NewPayment'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
const Payments = () => {
const [newPaymentOpen, setNewPaymentOpen] = useState(false)
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('payments')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('payments')
const actionItems = {
items: [
{
label: 'New Payment',
key: 'newPayment',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newPayment') {
setNewPaymentOpen(true)
}
}
}
return (
<>
<Flex vertical={'true'} gap='large'>
<Flex justify={'space-between'}>
<Space size='small'>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='payment'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
</Space>
<Space>
<Button
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
onClick={() =>
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='payment'
cards={viewMode === 'cards'}
/>
</Flex>
<Modal
open={newPaymentOpen}
styles={{ content: { paddingBottom: '24px' } }}
footer={null}
width={800}
onCancel={() => {
setNewPaymentOpen(false)
}}
destroyOnHidden={true}
>
<NewPayment
onOk={() => {
setNewPaymentOpen(false)
tableRef.current?.reload()
}}
reset={newPaymentOpen}
/>
</Modal>
</>
)
}
export default Payments

View File

@ -1,89 +0,0 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewPayment = ({ onOk, reset, defaultValues }) => {
return (
<NewObjectForm
type={'payment'}
reset={reset}
defaultValues={{
state: { type: 'draft' },
paymentDate: new Date(),
amount: 0,
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='payment'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{
invoice: true,
vendor: true,
client: true,
paymentDate: true,
amount: true,
paymentMethod: true
}}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='payment'
column={1}
bordered={false}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false,
_reference: false,
postedAt: false,
cancelledAt: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Payment'
onSubmit={async () => {
const result = await handleSubmit()
if (result) {
onOk()
}
}}
/>
)
}}
</NewObjectForm>
)
}
NewPayment.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewPayment

View File

@ -1,242 +0,0 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card, Modal } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
import useCollapseState from '../../hooks/useCollapseState.js'
import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx'
import ObjectInfo from '../../common/ObjectInfo.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import {
getModelByName
} from '../../../../database/ObjectModels.js'
import PostPayment from './PostPayment.jsx'
const log = loglevel.getLogger('PaymentInfo')
log.setLevel(config.logLevel)
const PaymentInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const paymentId = new URLSearchParams(location.search).get('paymentId')
const [collapseState, updateCollapseState] = useCollapseState('PaymentInfo', {
info: true,
notes: true,
auditLogs: true
})
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const [postPaymentOpen, setPostPaymentOpen] = useState(false)
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
},
post: () => {
setPostPaymentOpen(true)
return true
}
}
const editDisabled =
getModelByName('payment')
?.actions?.find((action) => action.name === 'edit')
?.disabled(objectFormState.objectData) ?? false
return (
<>
<Flex
gap='large'
vertical='true'
style={{
maxHeight: '100%',
minHeight: 0
}}
>
<Flex justify={'space-between'}>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='payment'
id={paymentId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Payment Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<DocumentPrintButton
type='payment'
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 ||
editDisabled
}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={paymentId}
type='payment'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap={'large'}>
<InfoCollapse
title='Payment Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='payment'
labelWidth='225px'
objectData={objectData}
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={paymentId} type='payment' />
</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': paymentId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
<Modal
open={postPaymentOpen}
onCancel={() => {
setPostPaymentOpen(false)
}}
width={500}
footer={null}
destroyOnHidden={true}
centered={true}
>
<PostPayment
onOk={() => {
setPostPaymentOpen(false)
actions.reload()
}}
objectData={objectFormState.objectData}
/>
</Modal>
</>
)
}
export default PaymentInfo

View File

@ -1,43 +0,0 @@
import { useState, useContext } from 'react'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../../context/ApiServerContext'
import { message } from 'antd'
import MessageDialogView from '../../common/MessageDialogView.jsx'
const PostPayment = ({ onOk, objectData }) => {
const [postLoading, setPostLoading] = useState(false)
const { sendObjectFunction } = useContext(ApiServerContext)
const handlePost = async () => {
setPostLoading(true)
try {
const result = await sendObjectFunction(objectData._id, 'Payment', 'post')
if (result) {
message.success('Payment posted successfully')
onOk(result)
}
} catch (error) {
console.error('Error posting payment:', error)
} finally {
setPostLoading(false)
}
}
return (
<MessageDialogView
title={'Are you sure you want to post this payment?'}
description={`Posting payment ${objectData?.name || objectData?._reference || objectData?._id} will set it to posted status.`}
onOk={handlePost}
okText='Post'
okLoading={postLoading}
/>
)
}
PostPayment.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default PostPayment

View File

@ -42,7 +42,6 @@ const InventoryOverview = () => {
}
className='no-t-padding-collapse'
collapseKey='inventoryStats'
canCollapse={false}
>
<Flex
justify='flex-start'

View File

@ -43,7 +43,6 @@ const items = [
icon: <PurchaseOrderIcon />,
path: '/dashboard/inventory/purchaseorders'
},
{ type: 'divider' },
{
key: 'orderitems',
label: 'Order Items',

View File

@ -33,15 +33,7 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
itemAmount: false,
totalAmount: false,
totalAmountWithTax: false,
quantity: false,
invoicedAmount: false,
invoicedAmountWithTax: false,
invoicedQuantity: false,
invoicedAmountRemaining: false,
invoicedAmountWithTaxRemaining: false,
invoicedQuantityRemaining: false,
orderedAt: false,
receivedAt: false
quantity: false
}}
/>
)
@ -79,16 +71,7 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
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
shipment: true
}}
/>
)
@ -105,15 +88,7 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
_id: false,
createdAt: false,
updatedAt: false,
_reference: false,
invoicedAmount: false,
invoicedAmountWithTax: false,
invoicedQuantity: false,
invoicedAmountRemaining: false,
invoicedAmountWithTaxRemaining: false,
invoicedQuantityRemaining: false,
orderedAt: false,
receivedAt: false
_reference: false
}}
isEditing={false}
objectData={objectData}

View File

@ -155,7 +155,7 @@ const OrderItemInfo = () => {
isEditing={isEditing}
type='orderItem'
objectData={objectData}
labelWidth='275px'
labelWidth='200px'
/>
</InfoCollapse>
</Flex>

View File

@ -53,8 +53,7 @@ const NewPurchaseOrder = ({ onOk, reset, defaultValues }) => {
acknowledgedAt: false,
shippingAmount: false,
shippingAmountWithTax: false,
grandTotalAmount: false,
completedAt: false
grandTotalAmount: false
}}
isEditing={false}
objectData={objectData}

View File

@ -28,10 +28,7 @@ import PostPurchaseOrder from './PostPurchaseOrder.jsx'
import AcknowledgePurchaseOrder from './AcknowledgePurchaseOrder.jsx'
import CancelPurchaseOrder from './CancelPurchaseOrder.jsx'
import ShipmentIcon from '../../../Icons/ShipmentIcon.jsx'
import InvoiceIcon from '../../../Icons/InvoiceIcon.jsx'
import StockEventIcon from '../../../Icons/StockEventIcon.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js'
import NewInvoice from '../../Finance/Invoices/NewInvoice.jsx'
const log = loglevel.getLogger('PurchaseOrderInfo')
log.setLevel(config.logLevel)
@ -48,7 +45,6 @@ const PurchaseOrderInfo = () => {
const [acknowledgePurchaseOrderOpen, setAcknowledgePurchaseOrderOpen] =
useState(false)
const [cancelPurchaseOrderOpen, setCancelPurchaseOrderOpen] = useState(false)
const [newInvoiceOpen, setNewInvoiceOpen] = useState(false)
const purchaseOrderId = new URLSearchParams(location.search).get(
'purchaseOrderId'
)
@ -57,9 +53,7 @@ const PurchaseOrderInfo = () => {
{
info: true,
notes: true,
auditLogs: true,
invoices: true,
stockEvents: true
auditLogs: true
}
)
@ -104,10 +98,6 @@ const PurchaseOrderInfo = () => {
setNewShipmentOpen(true)
return true
},
newInvoice: () => {
setNewInvoiceOpen(true)
return true
},
post: () => {
setPostPurchaseOrderOpen(true)
return true
@ -151,8 +141,6 @@ const PurchaseOrderInfo = () => {
{ key: 'info', label: 'Purchase Order Information' },
{ key: 'orderItems', label: 'Order Items' },
{ key: 'shipments', label: 'Shipments' },
{ key: 'invoices', label: 'Invoices' },
{ key: 'stockEvents', label: 'Stock Events' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
@ -268,48 +256,6 @@ const PurchaseOrderInfo = () => {
ref={shipmentsTableRef}
/>
</InfoCollapse>
<InfoCollapse
title='Invoices'
icon={<InvoiceIcon />}
active={collapseState.invoices}
onToggle={(expanded) =>
updateCollapseState('invoices', expanded)
}
collapseKey='invoices'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='invoice'
masterFilter={{
'order._id': purchaseOrderId,
orderType: 'purchaseOrder'
}}
visibleColumns={{ order: false }}
/>
)}
</InfoCollapse>
<InfoCollapse
title='Stock Events'
icon={<StockEventIcon />}
active={collapseState.stockEvents}
onToggle={(expanded) =>
updateCollapseState('stockEvents', expanded)
}
collapseKey='stockEvents'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='stockEvent'
masterFilter={{
'owner._id': purchaseOrderId
}}
/>
)}
</InfoCollapse>
</Flex>
)}
</ObjectForm>
@ -388,26 +334,6 @@ const PurchaseOrderInfo = () => {
}}
/>
</Modal>
<Modal
open={newInvoiceOpen}
onCancel={() => {
setNewInvoiceOpen(false)
}}
width={800}
footer={null}
destroyOnHidden={true}
>
<NewInvoice
onOk={() => {
setNewInvoiceOpen(false)
}}
reset={newInvoiceOpen}
defaultValues={{
orderType: 'purchaseOrder',
order: { ...objectFormState.objectData }
}}
/>
</Modal>
<Modal
open={postPurchaseOrderOpen}
onCancel={() => {

View File

@ -85,11 +85,7 @@ const NewShipment = ({ onOk, reset, defaultValues }) => {
shippedAt: false,
expectedAt: false,
deliveredAt: false,
taxRecord: false,
invoicedAmount: false,
invoicedAmountWithTax: false,
invoicedAmountRemaining: false,
invoicedAmountWithTaxRemaining: false
taxRecord: false
}}
isEditing={false}
objectData={objectData}

View File

@ -5,7 +5,6 @@ import { useLocation } from 'react-router-dom'
import ProductionSidebar from './Production/ProductionSidebar'
import InventorySidebar from './Inventory/InventorySidebar'
import FinanceSidebar from './Finance/FinanceSidebar'
import SalesSidebar from './Sales/SalesSidebar'
import ManagementSidebar from './Management/ManagementSidebar'
import DashboardNavigation from './common/DashboardNavigation'
import DashboardBreadcrumb from './common/DashboardBreadcrumb'
@ -20,7 +19,6 @@ const DashboardLayout = ({ children }) => {
const isProduction = location.pathname.startsWith('/dashboard/production')
const isInventory = location.pathname.startsWith('/dashboard/inventory')
const isFinance = location.pathname.startsWith('/dashboard/finance')
const isSales = location.pathname.startsWith('/dashboard/sales')
const isManagement = location.pathname.startsWith('/dashboard/management')
const isDeveloper = location.pathname.startsWith('/dashboard/developer')
@ -40,8 +38,6 @@ const DashboardLayout = ({ children }) => {
<InventorySidebar />
) : isFinance ? (
<FinanceSidebar />
) : isSales ? (
<SalesSidebar />
) : isManagement ? (
<ManagementSidebar />
) : isDeveloper ? (

View File

@ -1,95 +0,0 @@
import { useState, useRef } from 'react'
import { Button, Flex, Space, Modal, Dropdown } from 'antd'
import NewClient from './Clients/NewClient'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
const Clients = () => {
const [newClientOpen, setNewClientOpen] = useState(false)
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('client')
const [columnVisibility, setColumnVisibility] = useColumnVisibility('client')
const actionItems = {
items: [
{
label: 'New Client',
key: 'newClient',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newClient') {
setNewClientOpen(true)
}
}
}
return (
<>
<Flex vertical={'true'} gap='large'>
<Flex justify={'space-between'}>
<Space size='small'>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='client'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
</Space>
<Space>
<Button
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
onClick={() =>
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='client'
cards={viewMode === 'cards'}
/>
</Flex>
<Modal
open={newClientOpen}
onCancel={() => setNewClientOpen(false)}
footer={null}
destroyOnHidden={true}
width={700}
>
<NewClient
onOk={() => {
setNewClientOpen(false)
tableRef.current?.reload()
}}
reset={!newClientOpen}
/>
</Modal>
</>
)
}
export default Clients

View File

@ -1,194 +0,0 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import loglevel from 'loglevel'
import config from '../../../../config'
import useCollapseState from '../../hooks/useCollapseState'
import NotesPanel from '../../common/NotesPanel'
import InfoCollapse from '../../common/InfoCollapse'
import ObjectInfo from '../../common/ObjectInfo'
import ViewButton from '../../common/ViewButton'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm'
import EditButtons from '../../common/EditButtons'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('ClientInfo')
log.setLevel(config.logLevel)
const ClientInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const clientId = new URLSearchParams(location.search).get('clientId')
const [collapseState, updateCollapseState] = useCollapseState('ClientInfo', {
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='client'
id={clientId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Client Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<DocumentPrintButton
type='client'
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='Client Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
>
<ObjectForm
id={clientId}
type='client'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<ObjectInfo
loading={loading}
isEditing={isEditing}
type='client'
objectData={objectData}
/>
)}
</ObjectForm>
</InfoCollapse>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={clientId} type='client' />
</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': clientId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
</>
)
}
export default ClientInfo

View File

@ -1,87 +0,0 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewClient = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'client'}
defaultValues={{ active: true, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='client'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='client'
column={1}
bordered={false}
isEditing={true}
required={false}
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='client'
column={1}
bordered={false}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Client'
onSubmit={async () => {
const result = await handleSubmit()
if (result) {
onOk()
}
}}
/>
)
}}
</NewObjectForm>
)
}
NewClient.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewClient

View File

@ -1,99 +0,0 @@
import { useState, useRef } from 'react'
import { Button, Flex, Space, Modal, Dropdown } from 'antd'
import NewSalesOrder from './SalesOrders/NewSalesOrder'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
const SalesOrders = () => {
const [newSalesOrderOpen, setNewSalesOrderOpen] = useState(false)
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('salesOrders')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('salesOrders')
const actionItems = {
items: [
{
label: 'New Sales Order',
key: 'newSalesOrder',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newSalesOrder') {
setNewSalesOrderOpen(true)
}
}
}
return (
<>
<Flex vertical={'true'} gap='large'>
<Flex justify={'space-between'}>
<Space size='small'>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='salesOrder'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
</Space>
<Space>
<Button
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
onClick={() =>
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='salesOrder'
cards={viewMode === 'cards'}
/>
</Flex>
<Modal
open={newSalesOrderOpen}
styles={{ content: { paddingBottom: '24px' } }}
footer={null}
width={800}
onCancel={() => {
setNewSalesOrderOpen(false)
}}
destroyOnHidden={true}
>
<NewSalesOrder
onOk={() => {
setNewSalesOrderOpen(false)
tableRef.current?.reload()
}}
reset={newSalesOrderOpen}
/>
</Modal>
</>
)
}
export default SalesOrders

View File

@ -1,47 +0,0 @@
import { useState, useContext } from 'react'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../../context/ApiServerContext'
import { message } from 'antd'
import MessageDialogView from '../../common/MessageDialogView.jsx'
const CancelSalesOrder = ({ onOk, objectData }) => {
const [cancelLoading, setCancelLoading] = useState(false)
const { sendObjectFunction } = useContext(ApiServerContext)
const handleCancel = async () => {
setCancelLoading(true)
try {
const result = await sendObjectFunction(
objectData._id,
'SalesOrder',
'cancel'
)
if (result) {
message.success('Sales order cancelled successfully')
onOk(result)
}
} catch (error) {
console.error('Error cancelling sales order:', error)
} finally {
setCancelLoading(false)
}
}
return (
<MessageDialogView
title={'Are you sure you want to cancel this sales order?'}
description={`Cancelling sales order ${objectData?.name || objectData?._reference || objectData?._id} will update its status to cancelled.`}
onOk={handleCancel}
okText='Cancel'
okLoading={cancelLoading}
/>
)
}
CancelSalesOrder.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default CancelSalesOrder

View File

@ -1,47 +0,0 @@
import { useState, useContext } from 'react'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../../context/ApiServerContext'
import { message } from 'antd'
import MessageDialogView from '../../common/MessageDialogView.jsx'
const ConfirmSalesOrder = ({ onOk, objectData }) => {
const [confirmLoading, setConfirmLoading] = useState(false)
const { sendObjectFunction } = useContext(ApiServerContext)
const handleConfirm = async () => {
setConfirmLoading(true)
try {
const result = await sendObjectFunction(
objectData._id,
'SalesOrder',
'confirm'
)
if (result) {
message.success('Sales order confirmed successfully')
onOk(result)
}
} catch (error) {
console.error('Error confirming sales order:', error)
} finally {
setConfirmLoading(false)
}
}
return (
<MessageDialogView
title={'Are you sure you want to confirm this sales order?'}
description={`Confirming sales order ${objectData?.name || objectData?._reference || objectData?._id} will update its status to confirmed.`}
onOk={handleConfirm}
okText='Confirm'
okLoading={confirmLoading}
/>
)
}
ConfirmSalesOrder.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default ConfirmSalesOrder

View File

@ -1,90 +0,0 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewSalesOrder = ({ onOk, reset, defaultValues }) => {
return (
<NewObjectForm
type={'salesOrder'}
reset={reset}
defaultValues={{
state: { type: 'draft' },
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='salesOrder'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{
_reference: false,
items: false,
cost: false
}}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='salesOrder'
column={1}
bordered={false}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false,
_reference: false,
totalAmount: false,
totalAmountWithTax: false,
totalTaxAmount: false,
postedAt: false,
confirmedAt: false,
shippingAmount: false,
shippingAmountWithTax: false,
grandTotalAmount: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Sales Order'
onSubmit={async () => {
const result = await handleSubmit()
if (result) {
onOk()
}
}}
/>
)
}}
</NewObjectForm>
)
}
NewSalesOrder.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewSalesOrder

View File

@ -1,47 +0,0 @@
import { useState, useContext } from 'react'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../../context/ApiServerContext'
import { message } from 'antd'
import MessageDialogView from '../../common/MessageDialogView.jsx'
const PostSalesOrder = ({ onOk, objectData }) => {
const [postLoading, setPostLoading] = useState(false)
const { sendObjectFunction } = useContext(ApiServerContext)
const handlePost = async () => {
setPostLoading(true)
try {
const result = await sendObjectFunction(
objectData._id,
'SalesOrder',
'post'
)
if (result) {
message.success('Sales order posted successfully')
onOk(result)
}
} catch (error) {
console.error('Error posting sales order:', error)
} finally {
setPostLoading(false)
}
}
return (
<MessageDialogView
title={'Are you sure you want to post this sales order?'}
description={`Posting sales order ${objectData?.name || objectData?._reference || objectData?._id} will finalize it and update inventory levels where applicable.`}
onOk={handlePost}
okText='Post'
okLoading={postLoading}
/>
)
}
PostSalesOrder.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default PostSalesOrder

View File

@ -1,466 +0,0 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card, Modal } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
import useCollapseState from '../../hooks/useCollapseState.js'
import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx'
import ObjectInfo from '../../common/ObjectInfo.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
import NewOrderItem from '../../Inventory/OrderItems/NewOrderItem.jsx'
import NewShipment from '../../Inventory/Shipments/NewShipment.jsx'
import PostSalesOrder from './PostSalesOrder.jsx'
import ConfirmSalesOrder from './ConfirmSalesOrder.jsx'
import CancelSalesOrder from './CancelSalesOrder.jsx'
import ShipmentIcon from '../../../Icons/ShipmentIcon.jsx'
import InvoiceIcon from '../../../Icons/InvoiceIcon.jsx'
import StockEventIcon from '../../../Icons/StockEventIcon.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js'
import NewInvoice from '../../Finance/Invoices/NewInvoice.jsx'
const log = loglevel.getLogger('SalesOrderInfo')
log.setLevel(config.logLevel)
const SalesOrderInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const orderItemsTableRef = useRef(null)
const shipmentsTableRef = useRef(null)
const actionHandlerRef = useRef(null)
const [newOrderItemOpen, setNewOrderItemOpen] = useState(false)
const [newShipmentOpen, setNewShipmentOpen] = useState(false)
const [postSalesOrderOpen, setPostSalesOrderOpen] = useState(false)
const [confirmSalesOrderOpen, setConfirmSalesOrderOpen] = useState(false)
const [cancelSalesOrderOpen, setCancelSalesOrderOpen] = useState(false)
const [newInvoiceOpen, setNewInvoiceOpen] = useState(false)
const salesOrderId = new URLSearchParams(location.search).get('salesOrderId')
const [collapseState, updateCollapseState] = useCollapseState(
'SalesOrderInfo',
{
info: true,
notes: true,
auditLogs: true,
invoices: true,
stockEvents: 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: () => {
orderItemsTableRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
orderItemsTableRef?.current?.cancelEditing?.()
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
orderItemsTableRef?.current?.handleUpdate?.()
objectFormRef?.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef?.current?.handleDelete?.()
return true
},
newOrderItem: () => {
setNewOrderItemOpen(true)
return true
},
newShipment: () => {
setNewShipmentOpen(true)
return true
},
newInvoice: () => {
setNewInvoiceOpen(true)
return true
},
post: () => {
setPostSalesOrderOpen(true)
return true
},
confirm: () => {
setConfirmSalesOrderOpen(true)
return true
},
cancel: () => {
setCancelSalesOrderOpen(true)
return true
}
}
const editDisabled = getModelByName('salesOrder')
.actions.find((action) => action.name === 'edit')
.disabled(objectFormState.objectData)
return (
<>
<Flex
gap='large'
vertical='true'
style={{
maxHeight: '100%',
minHeight: 0
}}
>
<Flex justify={'space-between'}>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='salesOrder'
id={salesOrderId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Sales Order Information' },
{ key: 'orderItems', label: 'Order Items' },
{ key: 'shipments', label: 'Shipments' },
{ key: 'invoices', label: 'Invoices' },
{ key: 'stockEvents', label: 'Stock Events' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<DocumentPrintButton
type='salesOrder'
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 ||
editDisabled
}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={salesOrderId}
type='salesOrder'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap={'large'}>
<InfoCollapse
title='Sales Order Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='salesOrder'
labelWidth='225px'
objectData={objectData}
visibleProperties={{
items: false
}}
/>
</InfoCollapse>
<InfoCollapse
title='Sales Order Items'
icon={<OrderItemsIcon />}
active={collapseState.orderItems}
onToggle={(expanded) =>
updateCollapseState('orderItems', expanded)
}
collapseKey='orderItems'
>
<ObjectTable
type='orderItem'
masterFilter={{
'order._id': salesOrderId,
orderType: 'salesOrder'
}}
visibleColumns={{ order: false }}
ref={orderItemsTableRef}
/>
</InfoCollapse>
<InfoCollapse
title='Shipments'
icon={<ShipmentIcon />}
active={collapseState.shipments}
onToggle={(expanded) =>
updateCollapseState('shipments', expanded)
}
collapseKey='shipments'
>
<ObjectTable
type='shipment'
masterFilter={{
'order._id': salesOrderId,
orderType: 'salesOrder'
}}
visibleColumns={{ order: false }}
ref={shipmentsTableRef}
/>
</InfoCollapse>
<InfoCollapse
title='Invoices'
icon={<InvoiceIcon />}
active={collapseState.invoices}
onToggle={(expanded) =>
updateCollapseState('invoices', expanded)
}
collapseKey='invoices'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='invoice'
masterFilter={{
'order._id': salesOrderId,
orderType: 'salesOrder'
}}
visibleColumns={{ order: false }}
/>
)}
</InfoCollapse>
<InfoCollapse
title='Stock Events'
icon={<StockEventIcon />}
active={collapseState.stockEvents}
onToggle={(expanded) =>
updateCollapseState('stockEvents', expanded)
}
collapseKey='stockEvents'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='stockEvent'
masterFilter={{
'owner._id': salesOrderId
}}
/>
)}
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={salesOrderId} type='salesOrder' />
</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': salesOrderId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
<Modal
open={newOrderItemOpen}
onCancel={() => {
setNewOrderItemOpen(false)
}}
width={800}
footer={null}
destroyOnHidden={true}
>
<NewOrderItem
onOk={() => {
setNewOrderItemOpen(false)
}}
reset={newOrderItemOpen}
defaultValues={{
order: { _id: salesOrderId },
orderType: 'salesOrder',
syncAmount: 'itemPrice'
}}
/>
</Modal>
<Modal
open={newShipmentOpen}
onCancel={() => {
setNewShipmentOpen(false)
}}
width={800}
footer={null}
destroyOnHidden={true}
>
<NewShipment
onOk={() => {
setNewShipmentOpen(false)
}}
reset={newShipmentOpen}
defaultValues={{
orderType: 'salesOrder',
order: { _id: salesOrderId }
}}
/>
</Modal>
<Modal
open={newInvoiceOpen}
onCancel={() => {
setNewInvoiceOpen(false)
}}
width={800}
footer={null}
destroyOnHidden={true}
>
<NewInvoice
onOk={() => {
setNewInvoiceOpen(false)
}}
reset={newInvoiceOpen}
defaultValues={{
orderType: 'salesOrder',
order: { ...objectFormState.objectData }
}}
/>
</Modal>
<Modal
open={postSalesOrderOpen}
onCancel={() => {
setPostSalesOrderOpen(false)
}}
width={500}
footer={null}
destroyOnHidden={true}
centered={true}
>
<PostSalesOrder
onOk={() => {
setPostSalesOrderOpen(false)
actions.reload()
}}
objectData={objectFormState.objectData}
/>
</Modal>
<Modal
open={confirmSalesOrderOpen}
onCancel={() => {
setConfirmSalesOrderOpen(false)
}}
width={515}
footer={null}
destroyOnHidden={true}
centered={true}
>
<ConfirmSalesOrder
onOk={() => {
setConfirmSalesOrderOpen(false)
actions.reload()
}}
objectData={objectFormState.objectData}
/>
</Modal>
<Modal
open={cancelSalesOrderOpen}
onCancel={() => {
setCancelSalesOrderOpen(false)
}}
width={515}
footer={null}
destroyOnHidden={true}
centered={true}
>
<CancelSalesOrder
onOk={() => {
setCancelSalesOrderOpen(false)
actions.reload()
}}
objectData={objectFormState.objectData}
/>
</Modal>
</>
)
}
export default SalesOrderInfo

View File

@ -1,62 +0,0 @@
import { useContext } from 'react'
import { Flex } from 'antd'
import useCollapseState from '../hooks/useCollapseState'
import StatsDisplay from '../common/StatsDisplay'
import InfoCollapse from '../common/InfoCollapse'
import ScrollBox from '../common/ScrollBox'
import { ApiServerContext } from '../context/ApiServerContext'
const SalesOverview = () => {
const { connected } = useContext(ApiServerContext)
const [collapseState, updateCollapseState] = useCollapseState(
'SalesOverview',
{
clientStats: true,
salesOrderStats: true
}
)
if (!connected) {
return null
}
return (
<Flex
gap='large'
vertical='true'
style={{
maxHeight: '100%',
minHeight: 0
}}
>
<ScrollBox>
<Flex vertical gap={'large'}>
<InfoCollapse
title='Sales Order Statistics'
icon={null}
active={collapseState.salesOrderStats}
onToggle={(isActive) =>
updateCollapseState('salesOrderStats', isActive)
}
className='no-t-padding-collapse'
collapseKey='salesOrderStats'
canCollapse={false}
>
<Flex
justify='flex-start'
gap='middle'
wrap='wrap'
align='flex-start'
>
<StatsDisplay objectType='salesOrder' />
</Flex>
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
)
}
export default SalesOverview

View File

@ -1,54 +0,0 @@
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import ClientIcon from '../../Icons/ClientIcon'
import SalesIcon from '../../Icons/SalesIcon'
import SalesOrderIcon from '../../Icons/SalesOrderIcon'
const items = [
{
key: 'overview',
label: 'Overview',
icon: <SalesIcon />,
path: '/dashboard/sales/overview'
},
{ type: 'divider' },
{
key: 'clients',
label: 'Clients',
icon: <ClientIcon />,
path: '/dashboard/sales/clients'
},
{
key: 'salesorders',
label: 'Sales Orders',
icon: <SalesOrderIcon />,
path: '/dashboard/sales/salesorders'
}
]
const routeKeyMap = {
'/dashboard/sales/overview': 'overview',
'/dashboard/sales/clients': 'clients',
'/dashboard/sales/salesorders': 'salesorders'
}
const SalesSidebar = (props) => {
const location = useLocation()
const selectedKey = (() => {
const match = Object.keys(routeKeyMap).find((path) => {
const pathSplit = path.split('/')
const locationPathSplit = location.pathname.split('/')
if (pathSplit.length > locationPathSplit.length) return false
for (let i = 0; i < pathSplit.length; i++) {
if (pathSplit[i] !== locationPathSplit[i]) return false
}
return true
})
return match ? routeKeyMap[match] : 'overview'
})()
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
}
export default SalesSidebar

View File

@ -11,7 +11,6 @@ const breadcrumbNameMap = {
management: 'Management',
developer: 'Developer',
finance: 'Finance',
sales: 'Sales',
overview: 'Overview',
info: 'Info',
design: 'Design',

View File

@ -31,7 +31,6 @@ import MenuIcon from '../../Icons/MenuIcon'
import ProductionIcon from '../../Icons/ProductionIcon'
import InventoryIcon from '../../Icons/InventoryIcon'
import FinanceIcon from '../../Icons/FinanceIcon'
import SalesIcon from '../../Icons/SalesIcon'
import PersonIcon from '../../Icons/PersonIcon'
import CloudIcon from '../../Icons/CloudIcon'
import BellIcon from '../../Icons/BellIcon'
@ -72,11 +71,6 @@ const DashboardNavigation = () => {
label: 'Inventory',
icon: <InventoryIcon />
},
{
key: 'sales',
label: 'Sales',
icon: <SalesIcon />
},
{
key: 'finance',
label: 'Finance',
@ -147,8 +141,6 @@ const DashboardNavigation = () => {
navigate('/dashboard/inventory/overview')
} else if (key === 'finance') {
navigate('/dashboard/finance/overview')
} else if (key === 'sales') {
navigate('/dashboard/sales/overview')
} else if (key === 'management') {
navigate('/dashboard/management/filaments')
}

View File

@ -141,10 +141,6 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
const computedValuesObject = buildObjectFromEntries(computedEntries)
const initialFormData = merge({}, defaultValues, computedValuesObject)
form.setFieldsValue(initialFormData)
form
.validateFields({ validateOnly: true })
.then(() => setFormValid(true))
.catch(() => setFormValid(false))
setObjectData((prev) => merge({}, prev, initialFormData))
}
}, [form, defaultValues, calculateComputedValues, model])

View File

@ -1,6 +1,6 @@
import { useMemo, useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import { Table, Skeleton, Card, Button, Flex, Typography } from 'antd'
import { Table, Skeleton, Card, Button, Flex, Form, Typography } from 'antd'
import PlusIcon from '../../Icons/PlusIcon'
import ObjectProperty from './ObjectProperty'
import { LoadingOutlined } from '@ant-design/icons'
@ -21,14 +21,6 @@ const getDefaultWidth = (type) => {
return DEFAULT_COLUMN_WIDTHS[type] || 200
}
const resolveChangeValue = (val, type) => {
if (type === 'bool') return val
if (val?.target && typeof val.target === 'object') {
return val.target.value
}
return val
}
const createSkeletonRows = (rowCount, keyPrefix, keyName) => {
return Array.from({ length: rowCount }).map((_, index) => {
const skeletonKey = `${keyPrefix}-${index}`
@ -59,6 +51,7 @@ const ObjectChildTable = ({
additionalColumns = [],
emptyText = 'No items',
isEditing = false,
formListName,
value = [],
rollups = [],
onChange,
@ -110,44 +103,34 @@ const ObjectChildTable = ({
return value ?? []
}, [value])
// When used with antd Form.List, grab the form instance so we can read
// the latest row values and pass them into ObjectProperty as objectData.
// Assumes this component is rendered within a Form context when editing.
const formInstance = Form.useFormInstance()
const listNamePath = useMemo(() => {
if (!formListName) return null
return Array.isArray(formListName) ? formListName : [formListName]
}, [formListName])
const tableColumns = useMemo(() => {
const propertyColumns = resolvedProperties.map((property) => ({
title: property.label || property.name,
dataIndex: property.name,
key: property.name,
width: property.columnWidth || getDefaultWidth(property.type),
render: (_text, record, index) => {
render: (_text, record) => {
if (record?.isSkeleton) {
return (
<Skeleton.Input active size='small' style={{ width: '100%' }} />
)
}
const handleCellChange = (newVal) => {
const resolved = resolveChangeValue(newVal, property.type)
const currentItems = Array.isArray(itemsSource)
? [...itemsSource]
: []
const updatedItem = {
...currentItems[index],
[property.name]: resolved
}
currentItems[index] = updatedItem
if (typeof onChange === 'function') {
onChange(currentItems)
}
}
return (
<ObjectProperty
{...property}
longId={false}
objectData={record}
isEditing={isEditing}
useFormItem={false}
name={undefined}
value={record[property.name]}
onChange={handleCellChange}
/>
)
}
@ -433,6 +416,122 @@ const ObjectChildTable = ({
</Flex>
)
// When editing and a Form.List name is provided, bind rows via Form.List
// instead of the manual value/onChange mechanism.
if (isEditing === true && formListName) {
return (
<Form.List name={formListName}>
{(fields, { add, remove }) => {
const listDataSource = fields.map((field, index) => ({
_field: field,
_index: index,
key: field.key
}))
const listColumns = resolvedProperties.map((property) => ({
title: property.label || property.name,
dataIndex: property.name,
key: property.name,
width: property.columnWidth || getDefaultWidth(property.type),
render: (_text, record) => {
const field = record?._field
if (!field) return null
// Resolve the most up-to-date row data for this index from the form
let rowObjectData = undefined
if (formInstance && listNamePath) {
const namePath = [...listNamePath, field.name]
rowObjectData = formInstance.getFieldValue(namePath)
}
return (
<ObjectProperty
{...property}
// Bind directly to this list item + property via NamePath
name={[field.name, property.name]}
longId={false}
isEditing={true}
objectData={rowObjectData}
/>
)
}
}))
const deleteColumn = {
title: '',
key: 'delete',
width: 10,
fixed: 'right',
render: (_text, record) => {
const field = record?._field
const index = record?._index
if (!field || index == null) return null
return (
<Button
type='text'
danger
size='small'
icon={<BinIcon />}
onClick={(e) => {
e.stopPropagation()
if (typeof remove === 'function') {
remove(index)
}
}}
/>
)
}
}
const listTable = (
<Flex vertical>
<div ref={mainTableWrapperRef}>
<Table
dataSource={listDataSource}
columns={[...listColumns, ...additionalColumns, deleteColumn]}
pagination={false}
size={size}
loading={loading}
rowKey={(record) => record.key ?? record._index}
scroll={scrollConfig}
locale={{ emptyText }}
className={hasRollups ? 'child-table-rollups' : 'child-table'}
style={{ maxWidth, minWidth: 0 }}
{...tableProps}
/>
</div>
{rollupTable}
</Flex>
)
const handleAddListItem = () => {
const newItem = {}
resolvedProperties.forEach((property) => {
if (property?.name) {
newItem[property.name] = null
}
})
add(newItem)
}
return (
<Card style={{ minWidth: 0 }}>
<Flex vertical gap={'middle'}>
<Flex justify={'space-between'}>
<Button>Actions</Button>
<Button
type='primary'
icon={<PlusIcon />}
onClick={handleAddListItem}
/>
</Flex>
{listTable}
</Flex>
</Card>
)
}}
</Form.List>
)
}
if (isEditing === true) {
return (
<Card>
@ -472,6 +571,7 @@ ObjectChildTable.propTypes = {
additionalColumns: PropTypes.arrayOf(PropTypes.object),
emptyText: PropTypes.node,
isEditing: PropTypes.bool,
formListName: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
value: PropTypes.arrayOf(PropTypes.object),
onChange: PropTypes.func,
maxWidth: PropTypes.string,

View File

@ -14,17 +14,10 @@ import { useMessageContext } from '../context/MessageContext'
import PropTypes from 'prop-types'
import DeleteObjectModal from './DeleteObjectModal'
import merge from 'lodash/merge'
import mergeWith from 'lodash/mergeWith'
import set from 'lodash/set'
import { getModelByName } from '../../../database/ObjectModels'
import { useNavigate } from 'react-router-dom'
const arrayReplaceCustomizer = (objValue, srcValue) => {
if (Array.isArray(srcValue)) {
return srcValue
}
}
const buildObjectFromEntries = (entries = []) => {
return entries.reduce((acc, entry) => {
const { namePath, value } = entry || {}
@ -292,14 +285,14 @@ const ObjectForm = forwardRef(
const lockEvent = await fetchObjectLock(id, type)
setLock(lockEvent)
onStateChangeRef.current({ lock: lockEvent })
setObjectData({ ...data, _isEditing: isEditingRef.current })
serverObjectData.current = data
// Calculate and set computed values on initial load
const computedEntries = calculateComputedValues(data, model)
const computedValuesObject = buildObjectFromEntries(computedEntries)
const initialFormData = merge({}, data, computedValuesObject)
setObjectData({ ...initialFormData, _isEditing: isEditingRef.current })
form.setFieldsValue(initialFormData)
setFetchLoading(false)
onStateChangeRef.current({ loading: false })
@ -325,9 +318,7 @@ const ObjectForm = forwardRef(
// Update event handler
const updateObjectEventHandler = useCallback((value) => {
setObjectData((prev) =>
mergeWith({}, prev, value, arrayReplaceCustomizer)
)
setObjectData((prev) => merge({}, prev, value))
}, [])
// Update event handler
@ -549,22 +540,15 @@ const ObjectForm = forwardRef(
}
const computedValuesObject = buildObjectFromEntries(computedEntries)
const mergedFormValues = mergeWith(
const mergedFormValues = merge(
{},
allFormValues,
arrayReplaceCustomizer
computedValuesObject
)
merge(mergedFormValues, computedValuesObject)
mergedFormValues._isEditing = isEditingRef.current
setObjectData((prev) => {
return mergeWith(
{},
prev,
mergedFormValues,
arrayReplaceCustomizer
)
return merge({}, prev, mergedFormValues)
})
}}
>

View File

@ -61,7 +61,6 @@ const MATERIAL_OPTIONS = [
const ObjectProperty = ({
type = 'text',
prefix,
size,
suffix,
value,
min,
@ -86,7 +85,6 @@ const ObjectProperty = ({
minimal = false,
previewOpen = false,
showPreview = true,
useFormItem = true,
options = [],
roundNumber = false,
fixedNumber = false,
@ -412,7 +410,6 @@ const ObjectProperty = ({
maxWidth={maxWidth}
loading={loading}
rollups={rollups}
size={size}
/>
)
}
@ -616,219 +613,254 @@ const ObjectProperty = ({
mergedFormItemProps.onChange = onChange
}
const inputProps = useFormItem
? {}
: {
value,
onChange,
disabled
}
const renderInput = () => {
switch (type) {
case 'netGross':
return (
switch (type) {
case 'netGross':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<NetGrossInput
difference={difference}
prefix={prefix}
suffix={suffix}
{...inputProps}
/>
)
case 'secret':
return (
</Form.Item>
)
case 'secret':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<Input.Password
placeholder={label}
disabled={disabled}
{...mergedFormItemProps}
iconRender={(visible) =>
visible ? <EyeSlashIcon /> : <EyeIcon />
}
{...mergedFormItemProps}
{...inputProps}
/>
)
case 'wsprotocol':
return (
</Form.Item>
)
case 'wsprotocol':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<Select
defaultValue='ws'
disabled={disabled}
options={[
{ value: 'ws', label: 'Websocket' },
{ value: 'wss', label: 'Websocket Secure' }
]}
{...inputProps}
/>
)
case 'select':
return (
</Form.Item>
)
case 'select':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<CustomSelect
placeholder={'Select a ' + label.toLowerCase() + '...'}
disabled={disabled}
options={Array.isArray(options) ? options : []}
{...inputProps}
/>
)
case 'priceMode':
return (
</Form.Item>
)
case 'priceMode':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<Select
defaultValue='margin'
disabled={disabled}
options={[
{ value: 'margin', label: 'Margin %' },
{ value: 'amount', label: '£ Amount' }
]}
{...inputProps}
/>
)
case 'bool':
return (
<Switch
{...inputProps}
{...(useFormItem ? {} : { checked: value })}
/>
)
case 'dateTime':
return (
</Form.Item>
)
case 'bool':
return (
<Form.Item
name={formItemName}
{...mergedFormItemProps}
valuePropName='checked'
>
<Switch disabled={disabled} />
</Form.Item>
)
case 'dateTime':
return (
<Form.Item
name={formItemName}
{...mergedFormItemProps}
getValueProps={(v) => ({ value: v ? dayjs(v) : null })}
>
<DatePicker
showTime
style={{ width: '100%' }}
{...inputProps}
{...(useFormItem ? {} : { value: value ? dayjs(value) : null })}
disabled={disabled}
/>
)
case 'country':
return <CountrySelect {...inputProps} />
case 'color':
return (
<ColorSelector
required={required}
{...inputProps}
{...(useFormItem ? {} : { value })}
/>
)
case 'weight':
return (
</Form.Item>
)
case 'country':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<CountrySelect disabled={disabled} />
</Form.Item>
)
case 'color':
return (
<Form.Item
name={formItemName}
{...mergedFormItemProps}
valuePropName='value'
getValueFromEvent={(v) => v}
>
<ColorSelector required={required} disabled={disabled} />
</Form.Item>
)
case 'weight':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<InputNumber
suffix='g'
style={{ width: '100%' }}
placeholder={label}
{...inputProps}
disabled={disabled}
/>
)
case 'number':
return (
</Form.Item>
)
case 'number':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<InputNumber
placeholder={label}
disabled={disabled}
prefix={prefix}
suffix={suffix}
min={min}
max={max}
step={step}
{...mergedFormItemProps}
style={{ width: '100%' }}
{...inputProps}
/>
)
case 'text':
return <Input placeholder={label} {...inputProps} />
case 'codeBlock':
return (
</Form.Item>
)
case 'text':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<Input
placeholder={label}
{...mergedFormItemProps}
disabled={disabled}
/>
</Form.Item>
)
case 'codeBlock':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<CodeBlockEditor
code={value}
language={language}
disabled={disabled}
height={height}
minimal={minimal}
{...inputProps}
/>
)
case 'markdown':
return <MarkdownInput {...inputProps} />
case 'material':
return (
</Form.Item>
)
case 'markdown':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<MarkdownInput value={value} />
</Form.Item>
)
case 'material':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<Select
options={MATERIAL_OPTIONS}
placeholder={label}
{...inputProps}
disabled={disabled}
/>
)
case 'id':
// id is not editable, just show view mode
if (value) {
return <IdDisplay id={value} type={objectType} {...rest} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'object':
</Form.Item>
)
case 'id':
// id is not editable, just show view mode
if (value) {
return <IdDisplay id={value} type={objectType} {...rest} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'object':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<ObjectSelect
type={objectType}
disabled={disabled}
masterFilter={masterFilter}
{...inputProps}
/>
)
case 'objectType':
return (
<ObjectTypeSelect masterFilter={masterFilter} {...inputProps} />
)
case 'objectList':
return <ObjectSelect type={objectType} multiple {...inputProps} />
case 'tags':
return <TagsInput {...inputProps} />
case 'file':
return (
</Form.Item>
)
case 'objectType':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<ObjectTypeSelect disabled={disabled} masterFilter={masterFilter} />
</Form.Item>
)
case 'objectList':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<ObjectSelect type={objectType} multiple disabled={disabled} />
</Form.Item>
)
case 'tags':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<TagsInput />
</Form.Item>
)
case 'file':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<FileUpload
value={value}
multiple={false}
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
{...inputProps}
/>
)
case 'fileList':
return (
</Form.Item>
)
case 'fileList':
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<FileUpload
value={value}
multiple={true}
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
{...inputProps}
/>
)
case 'objectChildren': {
return (
<ObjectChildTable
properties={properties}
objectData={objectData}
isEditing={true}
rollups={rollups}
size={size}
{...inputProps}
/>
)
}
default:
return <Input placeholder={label} {...inputProps} />
</Form.Item>
)
case 'objectChildren': {
return (
<ObjectChildTable
value={value}
properties={properties}
objectData={objectData}
isEditing={true}
formListName={formItemName}
rollups={rollups}
/>
)
}
default:
return (
<Form.Item name={formItemName} {...mergedFormItemProps}>
<Input placeholder={label} {...mergedFormItemProps} />
</Form.Item>
)
}
if (!useFormItem) {
return renderInput()
}
return (
<Form.Item
name={formItemName}
{...mergedFormItemProps}
{...(type === 'bool' ? { valuePropName: 'checked' } : {})}
{...(type === 'color'
? { valuePropName: 'value', getValueFromEvent: (v) => v }
: {})}
{...(type === 'dateTime'
? { getValueProps: (v) => ({ value: v ? dayjs(v) : null }) }
: {})}
>
{renderInput()}
</Form.Item>
)
}
const property = renderProperty()
@ -860,7 +892,6 @@ ObjectProperty.propTypes = {
height: PropTypes.string,
previewOpen: PropTypes.bool,
showPreview: PropTypes.bool,
useFormItem: PropTypes.bool,
showHyperlink: PropTypes.bool,
options: PropTypes.array,
showSince: PropTypes.bool,

View File

@ -41,7 +41,6 @@ import { useNavigate } from 'react-router-dom'
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
import { AuthContext } from '../context/AuthContext'
import { ElectronContext } from '../context/ElectronContext'
import ActionsIcon from '../../Icons/ActionsIcon'
const logger = loglevel.getLogger('DasboardTable')
logger.setLevel(config.logLevel)
@ -759,11 +758,11 @@ const ObjectTable = forwardRef(
}
})
if (rowActions.length > 0 && tableData.some((item) => !item.isSkeleton)) {
if (rowActions.length > 0) {
columnsWithSkeleton.push({
title: (
<Flex gap='small' align='center' justify='center'>
<ActionsIcon />
{'Actions'}
</Flex>
),
key: 'actions',

View File

@ -101,13 +101,9 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
text = 'Sent'
break
case 'acknowledged':
status = 'purple'
status = 'processing'
text = 'Acknowledged'
break
case 'confirmed':
status = 'purple'
text = 'Confirmed'
break
case 'ordered':
status = 'cyan'
text = 'Ordered'

View File

@ -8,33 +8,25 @@ const { Text } = Typography
const formatTimeDifference = (dateTime) => {
const now = dayjs()
const target = dayjs(dateTime)
const isFuture = target.isAfter(now)
const diff = dayjs(dateTime)
// If future, calculate from target to now; if past, from now to target
const baseDate = isFuture ? target : now
const compareDate = isFuture ? now : target
const years = baseDate.diff(compareDate, 'year')
const months = baseDate.diff(compareDate.add(years, 'year'), 'month')
const weeks = baseDate.diff(
compareDate.add(years, 'year').add(months, 'month'),
'week'
)
const days = baseDate.diff(
compareDate.add(years, 'year').add(months, 'month').add(weeks, 'week'),
const years = now.diff(diff, 'year')
const months = now.diff(diff.add(years, 'year'), 'month')
const weeks = now.diff(diff.add(years, 'year').add(months, 'month'), 'week')
const days = now.diff(
diff.add(years, 'year').add(months, 'month').add(weeks, 'week'),
'day'
)
const hours = baseDate.diff(
compareDate
const hours = now.diff(
diff
.add(years, 'year')
.add(months, 'month')
.add(weeks, 'week')
.add(days, 'day'),
'hour'
)
const minutes = baseDate.diff(
compareDate
const minutes = now.diff(
diff
.add(years, 'year')
.add(months, 'month')
.add(weeks, 'week')
@ -42,8 +34,8 @@ const formatTimeDifference = (dateTime) => {
.add(hours, 'hour'),
'minute'
)
const seconds = baseDate.diff(
compareDate
const seconds = now.diff(
diff
.add(years, 'year')
.add(months, 'month')
.add(weeks, 'week')
@ -53,24 +45,21 @@ const formatTimeDifference = (dateTime) => {
'second'
)
let timeStr = ''
if (years > 0) {
timeStr = `${years} ${years === 1 ? 'year' : 'years'}`
return `${years}y`
} else if (months > 0) {
timeStr = `${months} ${months === 1 ? 'month' : 'months'}`
return `${months}mo`
} else if (weeks > 0) {
timeStr = `${weeks} ${weeks === 1 ? 'week' : 'weeks'}`
return `${weeks}w`
} else if (days > 0) {
timeStr = `${days} ${days === 1 ? 'day' : 'days'}`
return `${days}d`
} else if (hours > 0) {
timeStr = `${hours} ${hours === 1 ? 'hour' : 'hours'}`
return `${hours}h`
} else if (minutes > 0) {
timeStr = `${minutes} ${minutes === 1 ? 'minute' : 'minutes'}`
return `${minutes}m`
} else {
timeStr = `${seconds} ${seconds === 1 ? 'second' : 'seconds'}`
return `${seconds}s`
}
return isFuture ? `in ${timeStr} time` : `${timeStr} ago`
}
const TimeDisplay = ({
@ -109,7 +98,7 @@ const TimeDisplay = ({
return (
<Flex align={'center'} gap={'small'}>
<Text type={type}>{formattedDate}</Text>
{showSince ? <Tag style={{ margin: 0 }}>{timeAgo}</Tag> : null}
{showSince ? <Tag style={{ margin: 0 }}>{timeAgo + ' ago'}</Tag> : null}
</Flex>
)
}

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/actionsicon.svg?react'
const ActionsIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default ActionsIcon

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/clienticon.svg?react'
const ClientIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default ClientIcon

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/paymenticon.svg?react'
const PaymentIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default PaymentIcon

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/salesicon.svg?react'
const SalesIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default SalesIcon

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/salesordericon.svg?react'
const SalesOrderIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default SalesOrderIcon

View File

@ -31,9 +31,6 @@ import { DocumentJob } from './models/DocumentJob.js'
import { TaxRate } from './models/TaxRate.js'
import { TaxRecord } from './models/TaxRecord.js'
import { Invoice } from './models/Invoice.js'
import { Payment } from './models/Payment.js'
import { Client } from './models/Client.js'
import { SalesOrder } from './models/SalesOrder.js'
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
export const objectModels = [
@ -69,10 +66,7 @@ export const objectModels = [
DocumentJob,
TaxRate,
TaxRecord,
Invoice,
Payment,
Client,
SalesOrder
Invoice
]
// Re-export individual models for direct access
@ -109,10 +103,7 @@ export {
DocumentJob,
TaxRate,
TaxRecord,
Invoice,
Payment,
Client,
SalesOrder
Invoice
}
export function getModelByName(name, ignoreCase = false) {

View File

@ -7,7 +7,7 @@ export const AuditLog = {
icon: AuditLogIcon,
actions: [],
columns: [
'_reference',
'_id',
'owner',
'parent',
'operation',

View File

@ -1,215 +0,0 @@
import ClientIcon from '../../components/Icons/ClientIcon'
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 Client = {
name: 'client',
label: 'Client',
prefix: 'CLI',
icon: ClientIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) => `/dashboard/sales/clients/info?clientId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/sales/clients/info?clientId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',
row: true,
icon: EditIcon,
url: (_id) => `/dashboard/sales/clients/info?clientId=${_id}&action=edit`,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
}
},
{
name: 'finishEdit',
label: 'Save Edits',
icon: CheckIcon,
url: (_id) =>
`/dashboard/sales/clients/info?clientId=${_id}&action=finishEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'cancelEdit',
label: 'Cancel Edits',
icon: XMarkIcon,
url: (_id) =>
`/dashboard/sales/clients/info?clientId=${_id}&action=cancelEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{ type: 'divider' },
{
name: 'delete',
label: 'Delete',
icon: BinIcon,
danger: true,
url: (_id) =>
`/dashboard/sales/clients/info?clientId=${_id}&action=delete`
}
],
columns: [
'name',
'_reference',
'country',
'email',
'phone',
'active',
'createdAt',
'updatedAt'
],
filters: [
'name',
'_id',
'country',
'email',
'phone',
'active',
'createdAt',
'updatedAt'
],
sorters: [
'name',
'country',
'email',
'phone',
'active',
'createdAt',
'updatedAt',
'_id'
],
group: [],
properties: [
{
name: '_id',
label: 'ID',
columnFixed: 'left',
type: 'id',
objectType: 'client',
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: 'active',
label: 'Active',
type: 'bool',
readOnly: false,
required: true
},
{
name: 'country',
label: 'Country',
type: 'country',
readOnly: false,
required: false
},
{
name: 'email',
label: 'Email',
columnWidth: 300,
type: 'email',
readOnly: false,
required: false
},
{
name: 'phone',
label: 'Phone',
type: 'phone',
readOnly: false,
required: false
},
{
name: 'tags',
label: 'Tags',
type: 'array',
readOnly: false,
required: false
},
{
name: 'address.building',
label: 'Building',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.addressLine1',
label: 'Address Line 1',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.addressLine2',
label: 'Address Line 2',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.city',
label: 'City',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.state',
label: 'State',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.postcode',
label: 'Postcode',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.country',
label: 'Country',
type: 'country',
readOnly: false,
required: false
}
]
}

View File

@ -68,7 +68,7 @@ export const Courier = {
`/dashboard/management/couriers/info?courierId=${_id}&action=delete`
}
],
columns: ['name', '_reference', 'country', 'email', 'website', 'createdAt'],
columns: ['name', '_id', 'country', 'email', 'website', 'createdAt'],
filters: ['name', '_id', 'country', 'email'],
sorters: ['name', 'country', 'email', 'createdAt', '_id'],
group: ['country'],

View File

@ -69,14 +69,7 @@ export const CourierService = {
`/dashboard/management/courierservices/info?courierServiceId=${_id}&action=delete`
}
],
columns: [
'name',
'_reference',
'courier',
'tracked',
'deliveryTime',
'active'
],
columns: ['name', '_id', 'courier', 'tracked', 'deliveryTime', 'active'],
filters: ['name', '_id', 'courier', 'active', 'deliveryTime', 'tracked'],
sorters: [
'name',

View File

@ -61,7 +61,7 @@ export const DocumentJob = {
}
}
],
columns: ['name', '_reference', 'state', 'createdAt', 'updatedAt'],
columns: ['name', '_id', 'state', 'createdAt', 'updatedAt'],
filters: ['name', '_id', 'state'],
sorters: ['name', 'state', 'createdAt', 'updatedAt'],
properties: [

View File

@ -60,15 +60,7 @@ export const DocumentPrinter = {
}
}
],
columns: [
'name',
'_reference',
'state',
'host',
'tags',
'connectedAt',
'updatedAt'
],
columns: ['name', '_id', 'state', 'host', 'tags', 'connectedAt', 'updatedAt'],
filters: ['name', '_id'],
sorters: ['name', 'documentSize', 'connectedAt', 'updatedAt'],
properties: [

View File

@ -62,7 +62,7 @@ export const DocumentSize = {
],
columns: [
'name',
'_reference',
'_id',
'width',
'height',
'infiniteHeight',

View File

@ -71,7 +71,7 @@ export const DocumentTemplate = {
],
columns: [
'name',
'_reference',
'_id',
'active',
'global',
'objectType',

View File

@ -59,7 +59,7 @@ export const Filament = {
}
],
columns: [
'_reference',
'_id',
'name',
'type',
'color',

View File

@ -18,7 +18,7 @@ export const FilamentStock = {
}
],
columns: [
'_reference',
'_id',
'state',
'currentWeight',
'startingWeight',

View File

@ -78,7 +78,7 @@ export const File = {
}
],
url: (id) => `/dashboard/management/files/info?fileId=${id}`,
columns: ['name', '_reference', 'type', 'size', 'temp', 'createdAt'],
columns: ['name', '_id', 'type', 'size', 'temp', 'createdAt'],
filters: ['name', '_id', 'type', 'temp'],
sorters: ['name', 'type', 'size', 'createdAt', 'temp'],
group: ['type'],

View File

@ -70,7 +70,7 @@ export const GCodeFile = {
columns: [
'name',
'_reference',
'_id',
'filament',
'gcodeFileInfo.estimatedPrintingTimeNormalMode',
'gcodeFileInfo.sparseInfillDensity',

View File

@ -67,7 +67,7 @@ export const Host = {
}
}
],
columns: ['name', '_reference', 'state', 'tags', 'connectedAt'],
columns: ['name', '_id', 'state', 'tags', 'connectedAt'],
filters: ['name', '_id', 'state', 'tags'],
sorters: ['name', 'state', 'connectedAt'],
group: ['tags'],

View File

@ -4,7 +4,6 @@ import CheckIcon from '../../components/Icons/CheckIcon'
import EditIcon from '../../components/Icons/EditIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
export const Invoice = {
name: 'invoice',
@ -73,38 +72,28 @@ export const Invoice = {
},
{ type: 'divider' },
{
name: 'newPayment',
label: 'New Payment',
type: 'button',
icon: PlusIcon,
url: (_id) =>
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=newPayment`,
disabled: (objectData) => {
const allowedStates = ['acknowledged', 'partiallyPaid', 'overdue']
return !allowedStates.includes(objectData?.state?.type)
}
},
{ type: 'divider' },
{
name: 'post',
label: 'Post',
name: 'send',
label: 'Send',
type: 'button',
icon: CheckIcon,
url: (_id) =>
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=post`,
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=send`,
visible: (objectData) => {
return objectData?.state?.type == 'draft'
}
},
{
name: 'acknowledge',
label: 'Acknowledge',
name: 'markPaid',
label: 'Mark Paid',
type: 'button',
icon: CheckIcon,
url: (_id) =>
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=acknowledge`,
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=markPaid`,
visible: (objectData) => {
return objectData?.state?.type == 'sent'
return (
objectData?.state?.type == 'sent' ||
objectData?.state?.type == 'partiallyPaid'
)
}
},
{
@ -116,28 +105,29 @@ export const Invoice = {
url: (_id) =>
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=cancel`,
disabled: (objectData) => {
const allowedStates = [
'acknowledged',
'partiallyPaid',
'overdue',
'sent'
]
return !allowedStates.includes(objectData?.state?.type)
return (
objectData?.state?.type == 'cancelled' ||
objectData?.state?.type == 'paid'
)
},
visible: (objectData) => {
return objectData?.state?.type != 'draft'
return (
objectData?.state?.type == 'draft' ||
objectData?.state?.type == 'sent'
)
}
}
],
group: ['orderType'],
filters: ['to', 'from', 'orderType'],
group: ['vendor', 'customer', 'invoiceType'],
filters: ['vendor', 'customer', 'invoiceType'],
sorters: ['createdAt', 'state', 'updatedAt', 'invoiceDate', 'dueDate'],
columns: [
'_id',
'_reference',
'state',
'orderType',
'to',
'from',
'invoiceType',
'vendor',
'customer',
'invoiceDate',
'dueDate',
'totalAmount',
@ -169,7 +159,6 @@ export const Invoice = {
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
required: true,
objectType: 'invoice',
showCopy: true,
@ -183,11 +172,27 @@ export const Invoice = {
},
{ name: 'state', label: 'State', type: 'state', readOnly: true },
{
name: 'issuedAt',
label: 'Issued At',
type: 'dateTime',
readOnly: false,
required: true
name: 'invoiceDate',
label: 'Invoice Date',
type: 'date',
readOnly: false
},
{
name: 'dueDate',
label: 'Due Date',
type: 'date',
readOnly: false
},
{
name: 'vendor',
label: 'Vendor',
required: true,
type: 'object',
objectType: 'vendor',
showHyperlink: true,
visible: (objectData) => {
return objectData?.invoiceType === 'purchase' || objectData?.vendor
}
},
{
name: 'orderType',
@ -196,12 +201,6 @@ export const Invoice = {
masterFilter: ['purchaseOrder', 'salesOrder'],
required: true
},
{
name: 'dueAt',
label: 'Due At',
type: 'dateTime',
required: true
},
{
name: 'order',
label: 'Order',
@ -209,62 +208,38 @@ export const Invoice = {
objectType: (objectData) => {
return objectData?.orderType
},
masterFilter: (objectData) => {
return {
vendor: objectData?.vendor?._id
}
},
required: true,
showHyperlink: true
},
{
name: 'postedAt',
label: 'Posted At',
name: 'sentAt',
label: 'Sent At',
type: 'dateTime',
readOnly: true
},
{
name: 'from',
label: 'From',
required: true,
type: 'object',
objectType: 'vendor',
showHyperlink: true,
readOnly: true,
value: (objectData) => {
if (objectData?.orderType == 'purchaseOrder') {
return objectData?.order?.vendor
} else {
return null
}
}
},
{
name: 'acknowledgedAt',
label: 'Acknowledged At',
name: 'paidAt',
label: 'Paid At',
type: 'dateTime',
readOnly: true
},
{
name: 'to',
label: 'To',
required: true,
type: 'object',
objectType: 'client',
showHyperlink: true,
readOnly: true,
value: (objectData) => {
if (objectData?.orderType == 'salesOrder') {
return objectData?.order?.client
} else {
return null
}
}
},
{
name: 'cancelledAt',
label: 'Cancelled At',
type: 'dateTime',
readOnly: true
},
{
name: 'overdueAt',
label: 'Overdue At',
type: 'dateTime',
readOnly: true
},
{
name: 'totalTaxAmount',
label: 'Total Tax Amount',
@ -274,12 +249,6 @@ export const Invoice = {
readOnly: true,
columnWidth: 175
},
{
name: 'paidAt',
label: 'Paid At',
type: 'dateTime',
readOnly: true
},
{
name: 'totalAmountWithTax',
label: 'Total Amount w/ Tax',
@ -324,294 +293,44 @@ export const Invoice = {
roundNumber: 2,
columnWidth: 175,
readOnly: true
},
{
name: 'invoiceOrderItems',
label: 'Invoice Order Items',
type: 'objectChildren',
objectType: 'orderItem',
properties: [
{
name: 'orderItem',
label: 'Order Item',
type: 'object',
objectType: 'orderItem',
required: true,
columnWidth: 300,
showHyperlink: true
},
{
name: 'invoiceQuantity',
label: 'Quantity',
type: 'number',
required: true,
columnWidth: 175
},
{
name: 'invoiceAmount',
label: 'Invoice Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
required: true,
columnWidth: 150
},
{
name: 'taxRate',
label: 'Tax Rate',
type: 'object',
objectType: 'taxRate',
required: false,
showHyperlink: true,
columnWidth: 200
},
{
name: 'invoiceAmountWithTax',
label: 'Invoice Amount w/ Tax',
type: 'number',
prefix: '£',
roundNumber: 2,
required: true,
readOnly: true,
columnWidth: 200,
value: (objectData) => {
const invoiceAmount = objectData?.invoiceAmount || 0
if (objectData?.taxRate?.rateType == 'percentage') {
return (
(invoiceAmount * (1 + objectData?.taxRate?.rate / 100)).toFixed(
2
) || undefined
)
} else if (objectData?.taxRate?.rateType == 'amount') {
return (
(invoiceAmount + objectData?.taxRate?.rate).toFixed(2) ||
undefined
)
} else {
return invoiceAmount || 0
}
}
}
],
rollups: [
{
name: 'totalQuantity',
label: 'Total Quantity',
type: 'number',
property: 'invoiceQuantity',
value: (objectData) => {
return objectData?.invoiceOrderItems?.reduce(
(acc, item) => acc + (item.invoiceQuantity || 0),
0
)
}
},
{
name: 'totalAmount',
label: 'Total Amount',
type: 'number',
property: 'invoiceAmount',
prefix: '£',
fixedNumber: 2,
value: (objectData) => {
return objectData?.invoiceOrderItems
?.reduce(
(acc, item) =>
acc + (Number.parseFloat(item.invoiceAmount) || 0),
0
)
.toFixed(2)
}
},
{
name: 'totalAmountWithTax',
label: 'Total Amount w/ Tax',
type: 'number',
property: 'invoiceAmountWithTax',
prefix: '£',
fixedNumber: 2,
value: (objectData) => {
return objectData?.invoiceOrderItems
?.reduce(
(acc, item) =>
acc + (Number.parseFloat(item.invoiceAmountWithTax) || 0),
0
)
.toFixed(2)
}
}
]
},
{
name: 'invoiceShipments',
label: 'Invoice Shipments',
type: 'objectChildren',
objectType: 'shipment',
properties: [
{
name: 'shipment',
label: 'Shipment',
type: 'object',
objectType: 'shipment',
required: true,
columnWidth: 300,
showHyperlink: true
},
{
name: 'invoiceAmount',
label: 'Invoice Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
columnWidth: 175,
required: true
},
{
name: 'taxRate',
label: 'Tax Rate',
type: 'object',
objectType: 'taxRate',
required: false,
showHyperlink: true,
columnWidth: 200
},
{
name: 'invoiceAmountWithTax',
label: 'Invoice Amount w/ Tax',
type: 'number',
prefix: '£',
roundNumber: 2,
required: true,
readOnly: true,
columnWidth: 200,
value: (objectData) => {
const invoiceAmount = objectData?.invoiceAmount || 0
if (objectData?.taxRate?.rateType == 'percentage') {
return (
(invoiceAmount * (1 + objectData?.taxRate?.rate / 100)).toFixed(
2
) || undefined
)
} else if (objectData?.taxRate?.rateType == 'amount') {
return (
(invoiceAmount + objectData?.taxRate?.rate).toFixed(2) ||
undefined
)
} else {
return invoiceAmount || 0
}
}
}
],
rollups: [
{
name: 'totalAmount',
label: 'Total Amount',
type: 'number',
property: 'invoiceAmount',
prefix: '£',
roundNumber: 2,
value: (objectData) => {
return objectData?.invoiceShipments
?.reduce(
(acc, shipment) => acc + (shipment.invoiceAmount || 0),
0
)
.toFixed(2)
}
},
{
name: 'totalAmountWithTax',
label: 'Total Amount w/ Tax',
type: 'number',
property: 'invoiceAmountWithTax',
prefix: '£',
roundNumber: 2,
value: (objectData) => {
return objectData?.invoiceShipments
?.reduce(
(acc, shipment) =>
acc + (Number.parseFloat(shipment.invoiceAmountWithTax) || 0),
0
)
.toFixed(2)
}
}
]
}
],
stats: [
{
name: 'draft.draftCount.count',
name: 'draft.count',
label: 'Draft',
type: 'number',
color: 'default'
},
{
name: 'draft.draftGrandTotalAmount.sum',
label: 'Draft Grand Total Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'default'
},
{
name: 'sent.sentCount.count',
name: 'sent.count',
label: 'Sent',
type: 'number',
color: 'cyan'
},
{
name: 'acknowledged.acknowledgedCount.count',
label: 'Acknowledged',
type: 'number',
color: 'purple'
},
{
name: 'due.dueCount.count',
label: 'Due',
type: 'number',
color: 'warning',
sum: [
'sent.sentCount.count',
'partiallyPaid.partiallyPaidCount.count',
'overdue.overdueCount.count',
'acknowledged.acknowledgedCount.count'
]
},
{
name: 'due.dueGrandTotalAmount.sum',
label: 'Due Grand Total Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'warning',
sum: [
'sent.sentGrandTotalAmount.sum',
'partiallyPaid.partiallyPaidGrandTotalAmount.sum',
'overdue.overdueGrandTotalAmount.sum',
'acknowledged.acknowledgedGrandTotalAmount.sum'
]
},
{
name: 'partiallyPaid.partiallyPaidCount.count',
name: 'partiallyPaid.count',
label: 'Partially Paid',
type: 'number',
color: 'processing'
},
{
name: 'overdue.overdueCount.count',
name: 'paid.count',
label: 'Paid',
type: 'number',
color: 'success'
},
{
name: 'overdue.count',
label: 'Overdue',
type: 'number',
color: 'error'
},
{
name: 'overdue.overdueGrandTotalAmount.sum',
label: 'Overdue Grand Total Amount',
name: 'cancelled.count',
label: 'Cancelled',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'error'
color: 'default'
}
]
}

View File

@ -37,7 +37,7 @@ export const Job = {
url: (_id) => `/dashboard/production/jobs/info?jobId=${_id}&action=reload`
}
],
columns: ['_reference', 'quantity', 'state', 'gcodeFile', 'createdAt'],
columns: ['_id', 'quantity', 'state', 'gcodeFile', 'createdAt'],
filters: ['state', '_id', 'gcodeFile', 'quantity'],
sorters: ['createdAt', 'state', 'quantity', 'gcodeFile'],
properties: [

View File

@ -34,7 +34,6 @@ export const Note = {
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
objectType: 'note',
showCopy: true,
readOnly: true

View File

@ -58,7 +58,7 @@ export const NoteType = {
}
}
],
columns: ['name', '_reference', 'color', 'active', 'createdAt', 'updatedAt'],
columns: ['name', '_id', 'color', 'active', 'createdAt', 'updatedAt'],
filters: ['name', '_id', 'color', 'active'],
sorters: ['name', 'color', 'active', 'createdAt', 'updatedAt'],
properties: [

View File

@ -81,8 +81,8 @@ export const OrderItem = {
filters: ['itemType', 'item', 'order'],
sorters: ['createdAt', 'updatedAt', 'itemAmount', 'quantity'],
columns: [
'_id',
'_reference',
'name',
'state',
'itemType',
'item',
@ -93,14 +93,6 @@ export const OrderItem = {
'totalAmountWithTax',
'order',
'shipment',
'invoicedAmount',
'invoicedAmountWithTax',
'invoicedQuantity',
'invoicedAmountRemaining',
'invoicedAmountWithTaxRemaining',
'invoicedQuantityRemaining',
'orderedAt',
'receivedAt',
'createdAt',
'updatedAt'
],
@ -124,7 +116,6 @@ export const OrderItem = {
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
objectType: 'orderItem',
showCopy: true,
readOnly: true
@ -135,30 +126,7 @@ export const OrderItem = {
type: 'dateTime',
readOnly: true
},
{
name: 'name',
label: 'Name',
type: 'text',
readOnly: true,
value: (objectData) => {
return objectData?.item?.name
}
},
{
name: 'orderedAt',
label: 'Ordered At',
type: 'dateTime',
required: false,
readOnly: true
},
{ name: 'state', label: 'State', type: 'state', readOnly: true },
{
name: 'receivedAt',
label: 'Received At',
type: 'dateTime',
required: false,
readOnly: true
},
{
name: 'orderType',
label: 'Order Type',
@ -198,7 +166,7 @@ export const OrderItem = {
name: 'itemType',
label: 'Item Type',
type: 'objectType',
masterFilter: ['part', 'packaging', 'filament', 'product'],
masterFilter: ['part', 'packaging', 'filament'],
required: true,
columnWidth: 175
},
@ -233,7 +201,6 @@ export const OrderItem = {
prefix: '£',
min: 0,
step: 0.01,
fixedNumber: 2,
readOnly: (objectData) => {
return objectData?.syncAmount != null
},
@ -264,7 +231,6 @@ export const OrderItem = {
min: 0,
step: 0.01,
columnWidth: 150,
fixedNumber: 2,
readOnly: true,
value: (objectData) => {
if (objectData?.itemAmount && objectData?.quantity) {
@ -306,7 +272,6 @@ export const OrderItem = {
label: 'Total Amount w/ Tax',
type: 'number',
required: true,
fixedNumber: 2,
readOnly: true,
prefix: '£',
min: 0,
@ -333,70 +298,6 @@ export const OrderItem = {
return totalAmount || 0
}
}
},
{
name: 'invoicedAmount',
label: 'Invoiced Amount',
type: 'number',
required: false,
readOnly: true,
prefix: '£',
fixedNumber: 2,
min: 0,
step: 0.01,
columnWidth: 150
},
{
name: 'invoicedAmountWithTax',
label: 'Invoiced Amount w/ Tax',
type: 'number',
required: false,
readOnly: true,
prefix: '£',
fixedNumber: 2,
min: 0,
step: 0.01,
columnWidth: 200
},
{
name: 'invoicedQuantity',
label: 'Invoiced Quantity',
type: 'number',
required: false,
readOnly: true,
columnWidth: 150
},
{
name: 'invoicedAmountRemaining',
label: 'Remaining Invoiced Amount',
type: 'number',
required: false,
readOnly: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 225,
fixedNumber: 2
},
{
name: 'invoicedAmountWithTaxRemaining',
label: 'Remaining Invoiced Amount w/ Tax',
type: 'number',
required: false,
readOnly: true,
prefix: '£',
min: 0,
fixedNumber: 2,
step: 0.01,
columnWidth: 275
},
{
name: 'invoicedQuantityRemaining',
label: 'Remaining Invoiced Quantity',
type: 'number',
required: false,
readOnly: true,
columnWidth: 225
}
]
}

View File

@ -57,7 +57,7 @@ export const Part = {
}
}
],
columns: ['name', '_reference', 'product', 'globalPricing', 'createdAt'],
columns: ['name', '_id', 'product', 'globalPricing', 'createdAt'],
filters: ['name', '_id', 'product', 'globalPricing'],
sorters: ['name', 'email', 'role', 'createdAt', '_id'],
properties: [

View File

@ -20,7 +20,7 @@ export const PartStock = {
filters: ['_id', 'part', 'startingQuantity', 'currentQuantity'],
sorters: ['part', 'startingQuantity', 'currentQuantity'],
columns: [
'_reference',
'_id',
'state',
'startingQuantity',
'currentQuantity',

View File

@ -1,252 +0,0 @@
import PaymentIcon from '../../components/Icons/PaymentIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import EditIcon from '../../components/Icons/EditIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const Payment = {
name: 'payment',
label: 'Payment',
prefix: 'PAY',
icon: PaymentIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) => `/dashboard/finance/payments/info?paymentId=${_id}`
},
{
name: 'edit',
label: 'Edit',
type: 'button',
icon: EditIcon,
url: (_id) =>
`/dashboard/finance/payments/info?paymentId=${_id}&action=edit`,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
},
disabled: (objectData) => {
return objectData?.state?.type != 'draft'
}
},
{
name: 'cancelEdit',
label: 'Cancel Edit',
type: 'button',
icon: XMarkIcon,
url: (_id) =>
`/dashboard/finance/payments/info?paymentId=${_id}&action=cancelEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'finishEdit',
label: 'Finish Edit',
type: 'button',
icon: CheckIcon,
url: (_id) =>
`/dashboard/finance/payments/info?paymentId=${_id}&action=finishEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'delete',
label: 'Delete',
type: 'button',
icon: BinIcon,
danger: true,
url: (_id) =>
`/dashboard/finance/payments/info?paymentId=${_id}&action=delete`,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
},
disabled: (objectData) => {
return objectData?.state?.type != 'draft'
}
},
{ type: 'divider' },
{
name: 'post',
label: 'Post',
type: 'button',
icon: CheckIcon,
url: (_id) =>
`/dashboard/finance/payments/info?paymentId=${_id}&action=post`,
visible: (objectData) => {
return objectData?.state?.type == 'draft'
}
},
{
name: 'cancel',
label: 'Cancel',
type: 'button',
icon: XMarkIcon,
danger: true,
url: (_id) =>
`/dashboard/finance/payments/info?paymentId=${_id}&action=cancel`,
disabled: (objectData) => {
return objectData?.state?.type == 'cancelled'
},
visible: (objectData) => {
return (
objectData?.state?.type == 'draft' ||
objectData?.state?.type == 'posted'
)
}
}
],
group: ['vendor', 'client', 'invoice'],
filters: ['vendor', 'client', 'invoice'],
sorters: ['createdAt', 'state', 'updatedAt', 'paymentDate'],
columns: [
'_reference',
'state',
'invoice',
'vendor',
'client',
'paymentDate',
'amount',
'paymentMethod',
'createdAt',
'updatedAt'
],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
columnFixed: 'left',
objectType: 'payment',
columnWidth: 140,
showCopy: true
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
required: true,
objectType: 'payment',
showCopy: true,
readOnly: true
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true
},
{ name: 'state', label: 'State', type: 'state', readOnly: true },
{
name: 'invoice',
label: 'Invoice',
type: 'object',
objectType: 'invoice',
required: true,
showHyperlink: true
},
{
name: 'useRemainingAmount',
label: 'Use Remaining Amount',
type: 'boolean',
required: true
},
{
name: 'vendor',
label: 'Vendor',
type: 'object',
objectType: 'vendor',
showHyperlink: true,
readOnly: true
},
{
name: 'client',
label: 'Client',
type: 'object',
objectType: 'client',
showHyperlink: true,
readOnly: true
},
{
name: 'paymentDate',
label: 'Payment Date',
type: 'dateTime',
required: true
},
{
name: 'postedAt',
label: 'Posted At',
type: 'dateTime',
readOnly: true
},
{
name: 'cancelledAt',
label: 'Cancelled At',
type: 'dateTime',
readOnly: true
},
{
name: 'amount',
label: 'Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
required: true,
columnWidth: 150
},
{
name: 'paymentMethod',
label: 'Payment Method',
type: 'string',
required: false
},
{
name: 'notes',
label: 'Notes',
type: 'text',
required: false
}
],
stats: [
{
name: 'draft.draftCount.count',
label: 'Draft',
type: 'number',
color: 'default'
},
{
name: 'draft.draftAmount.sum',
label: 'Draft Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'default'
},
{
name: 'posted.postedCount.count',
label: 'Posted',
type: 'number',
color: 'cyan'
},
{
name: 'posted.postedAmount.sum',
label: 'Posted Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'cyan'
}
]
}

View File

@ -212,15 +212,7 @@ export const Printer = {
]
}
],
columns: [
'name',
'_reference',
'state',
'host',
'tags',
'connectedAt',
'updatedAt'
],
columns: ['name', '_id', 'state', 'host', 'tags', 'connectedAt', 'updatedAt'],
filters: ['name', '_id', 'state', 'tags'],
sorters: ['name', 'state', 'connectedAt'],
group: ['tags'],

View File

@ -58,7 +58,7 @@ export const Product = {
}
}
],
columns: ['_reference', 'name', 'tags', 'vendor', 'price', 'createdAt', 'updatedAt'],
columns: ['_id', 'name', 'tags', 'vendor', 'price', 'createdAt', 'updatedAt'],
filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor'],
sorters: ['name', 'createdAt', 'type', 'vendor', 'cost', 'updatedAt'],
properties: [

View File

@ -103,13 +103,7 @@ export const PurchaseOrder = {
url: (_id) =>
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=newInvoice`,
disabled: (objectData) => {
const allowedStates = [
'received',
'shipped',
'partiallyReceived',
'partiallyShipped'
]
return !allowedStates.includes(objectData?.state?.type)
return objectData?.state?.type != 'received'
}
},
{
@ -175,6 +169,7 @@ export const PurchaseOrder = {
filters: ['vendor'],
sorters: ['createdAt', 'state', 'updatedAt'],
columns: [
'_id',
'_reference',
'state',
'vendor',
@ -208,7 +203,6 @@ export const PurchaseOrder = {
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
required: true,
objectType: 'purchaseOrder',
showCopy: true,
@ -246,7 +240,7 @@ export const PurchaseOrder = {
columnWidth: 175
},
{
name: 'completedAt',
name: 'CompletedAt',
label: 'Completed At',
type: 'dateTime',
readOnly: true
@ -314,7 +308,7 @@ export const PurchaseOrder = {
name: 'acknowledged.count',
label: 'Acknowledged',
type: 'number',
color: 'purple'
color: 'processing'
},
{
name: 'partiallyShipped.count',

View File

@ -1,343 +0,0 @@
import SalesOrderIcon from '../../components/Icons/SalesOrderIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import EditIcon from '../../components/Icons/EditIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const SalesOrder = {
name: 'salesOrder',
label: 'Sales Order',
prefix: 'SOR',
icon: SalesOrderIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) => `/dashboard/sales/salesorders/info?salesOrderId=${_id}`
},
{
name: 'edit',
label: 'Edit',
type: 'button',
icon: EditIcon,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=edit`,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
},
disabled: (objectData) => {
return objectData?.state?.type != 'draft'
}
},
{
name: 'cancelEdit',
label: 'Cancel Edit',
type: 'button',
icon: XMarkIcon,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=cancelEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'finishEdit',
label: 'Finish Edit',
type: 'button',
icon: CheckIcon,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=finishEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'delete',
label: 'Delete',
type: 'button',
icon: BinIcon,
danger: true,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=delete`,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
},
disabled: (objectData) => {
return objectData?.state?.type != 'draft'
}
},
{ type: 'divider' },
{
name: 'New Order Item',
label: 'New Order Item',
type: 'button',
icon: PlusIcon,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=newOrderItem`,
disabled: (objectData) => {
return objectData?.state?.type != 'draft'
}
},
{
name: 'New Shipment',
label: 'New Shipment',
type: 'button',
icon: PlusIcon,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=newShipment`,
disabled: (objectData) => {
return objectData?.state?.type != 'draft'
}
},
{
name: 'New Invoice',
label: 'New Invoice',
type: 'button',
icon: PlusIcon,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=newInvoice`,
disabled: (objectData) => {
const allowedStates = [
'delivered',
'sent',
'confirmed',
'shipped',
'partiallyDelivered',
'partiallyShipped'
]
return !allowedStates.includes(objectData?.state?.type)
}
},
{ type: 'divider' },
{
name: 'post',
label: 'Post',
type: 'button',
icon: CheckIcon,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=post`,
visible: (objectData) => {
return objectData?.state?.type == 'draft'
}
},
{
name: 'confirm',
label: 'Confirm',
type: 'button',
icon: CheckIcon,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=confirm`,
visible: (objectData) => {
return objectData?.state?.type == 'sent'
}
},
{
name: 'complete',
label: 'Complete',
type: 'button',
icon: CheckIcon,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=complete`,
disabled: (objectData) => {
return objectData?.state?.type != 'delivered'
},
visible: (objectData) => {
return objectData?.state?.type == 'delivered'
}
},
{
name: 'cancel',
label: 'Cancel',
type: 'button',
icon: XMarkIcon,
danger: true,
url: (_id) =>
`/dashboard/sales/salesorders/info?salesOrderId=${_id}&action=cancel`,
disabled: (objectData) => {
return objectData?.state?.type == 'cancelled'
},
visible: (objectData) => {
return (
objectData?.state?.type != 'draft' &&
objectData?.state?.type != 'completed' &&
objectData?.state?.type != 'delivered'
)
}
}
],
group: ['client'],
filters: ['client'],
sorters: ['createdAt', 'state', 'updatedAt'],
columns: [
'_reference',
'state',
'client',
'totalAmount',
'totalAmountWithTax',
'totalTaxAmount',
'shippingAmount',
'shippingAmountWithTax',
'grandTotalAmount',
'createdAt',
'updatedAt',
'client'
],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
columnFixed: 'left',
objectType: 'salesOrder',
columnWidth: 140,
showCopy: true
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
required: true,
objectType: 'salesOrder',
showCopy: true,
readOnly: true
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true
},
{ name: 'state', label: 'State', type: 'state', readOnly: true },
{ name: 'postedAt', label: 'Posted At', type: 'dateTime', readOnly: true },
{
name: 'client',
label: 'Client',
required: true,
type: 'object',
objectType: 'client',
showHyperlink: true
},
{
name: 'confirmedAt',
label: 'Confirmed At',
type: 'dateTime',
readOnly: true
},
{
name: 'totalTaxAmount',
label: 'Total Tax Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
readOnly: true,
columnWidth: 175
},
{
name: 'completedAt',
label: 'Completed At',
type: 'dateTime',
readOnly: true
},
{
name: 'totalAmountWithTax',
label: 'Total Amount w/ Tax',
type: 'number',
prefix: '£',
readOnly: true,
columnWidth: 175,
roundNumber: 2
},
{
name: 'shippingAmount',
label: 'Shipping Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
readOnly: true,
columnWidth: 150
},
{
name: 'shippingAmountWithTax',
label: 'Shipping Amount w/ Tax',
type: 'number',
prefix: '£',
readOnly: true,
roundNumber: 2,
columnWidth: 200
},
{
name: 'totalAmount',
label: 'Total Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
readOnly: true,
columnWidth: 150
},
{
name: 'grandTotalAmount',
label: 'Grand Total Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
columnWidth: 175,
readOnly: true
}
],
stats: [
{
name: 'draft.count',
label: 'Draft',
type: 'number',
color: 'default'
},
{
name: 'sent.count',
label: 'Sent',
type: 'number',
color: 'cyan'
},
{
name: 'confirmed.count',
label: 'Confirmed',
type: 'number',
color: 'purple'
},
{
name: 'partiallyShipped.count',
label: 'Partially Shipped',
type: 'number',
color: 'processing'
},
{
name: 'shipped.count',
label: 'Shipped',
type: 'number',
color: 'processing'
},
{
name: 'partiallyDelivered.count',
label: 'Partially Delivered',
type: 'number',
color: 'success'
},
{
name: 'delivered.count',
label: 'Delivered',
type: 'number',
color: 'success'
}
]
}

View File

@ -123,6 +123,7 @@ export const Shipment = {
'deliveredAt'
],
columns: [
'_id',
'_reference',
'state',
'orderType',
@ -131,10 +132,6 @@ export const Shipment = {
'amountWithTax',
'taxRate',
'taxAmount',
'invoicedAmount',
'invoicedAmountWithTax',
'invoicedAmountRemaining',
'invoicedAmountWithTaxRemaining',
'trackingNumber',
'shippedAt',
'expectedAt',
@ -162,7 +159,6 @@ export const Shipment = {
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
objectType: 'shipment',
showCopy: true,
readOnly: true
@ -292,54 +288,6 @@ export const Shipment = {
}
return 0
}
},
{
name: 'invoicedAmount',
label: 'Invoiced Amount',
type: 'number',
required: false,
readOnly: true,
prefix: '£',
fixedNumber: 2,
min: 0,
step: 0.01,
columnWidth: 150
},
{
name: 'invoicedAmountWithTax',
label: 'Invoiced Amount w/ Tax',
type: 'number',
required: false,
readOnly: true,
prefix: '£',
fixedNumber: 2,
min: 0,
step: 0.01,
columnWidth: 200
},
{
name: 'invoicedAmountRemaining',
label: 'Remaining Invoiced Amount',
type: 'number',
required: false,
readOnly: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 225,
fixedNumber: 2
},
{
name: 'invoicedAmountWithTaxRemaining',
label: 'Remaining Invoiced Amount w/ Tax',
type: 'number',
required: false,
readOnly: true,
prefix: '£',
min: 0,
fixedNumber: 2,
step: 0.01,
columnWidth: 275
}
]
}

View File

@ -17,7 +17,7 @@ export const StockAudit = {
}
],
url: (id) => `/dashboard/inventory/stockaudits/info?stockAuditId=${id}`,
columns: ['_reference', 'state', 'createdAt', 'updatedAt'],
columns: ['_id', 'state', 'createdAt', 'updatedAt'],
filters: ['_id'],
sorters: ['createdAt', 'updatedAt'],
group: ['state'],

View File

@ -6,7 +6,7 @@ export const StockEvent = {
prefix: 'SEV',
icon: StockEventIcon,
actions: [],
columns: ['_reference', 'owner', 'parent', 'value', 'createdAt'],
columns: ['_id', 'owner', 'parent', 'value', 'createdAt'],
filters: ['_id', 'owner', 'parent'],
sorters: ['createdAt'],
properties: [
@ -26,16 +26,6 @@ export const StockEvent = {
value: null,
readOnly: true
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
objectType: 'stockEvent',
value: null,
showCopy: true,
readOnly: true
},
{
name: 'updatedAt',
label: 'Updated At',
@ -51,7 +41,6 @@ export const StockEvent = {
return objectData.ownerType
},
columnFixed: 'left',
columnWidth: 200,
value: null,
showCopy: true,
showHyperlink: true
@ -64,7 +53,6 @@ export const StockEvent = {
return objectData?.parentType
},
value: null,
columnWidth: 200,
showCopy: true,
showHyperlink: true
},

View File

@ -29,7 +29,7 @@ export const SubJob = {
}
}
],
columns: ['_reference', 'printer', 'job', 'state', 'createdAt'],
columns: ['_id', 'printer', 'job', 'state', 'createdAt'],
filters: ['state', '_id', 'job', 'printer'],
sorters: ['createdAt', 'state'],
group: ['job'],

View File

@ -70,7 +70,7 @@ export const TaxRate = {
],
columns: [
'name',
'_reference',
'_id',
'rate',
'rateType',
'active',

View File

@ -69,7 +69,7 @@ export const TaxRecord = {
}
],
columns: [
'_reference',
'_id',
'taxRate',
'transactionType',
'transaction',

View File

@ -24,7 +24,7 @@ export const User = {
`/dashboard/management/users/info?userId=${_id}&action=reload`
}
],
columns: ['name', '_reference', 'username', 'email', 'role', 'createdAt'],
columns: ['name', '_id', 'username', 'email', 'role', 'createdAt'],
filters: ['name', '_id', 'email', 'role'],
sorters: ['name', 'email', 'role', 'createdAt', '_id'],
properties: [

View File

@ -70,7 +70,7 @@ export const Vendor = {
],
columns: [
'name',
'_reference',
'_id',
'country',
'email',
'website',

View File

@ -7,12 +7,6 @@ const Invoices = lazy(
const InvoiceInfo = lazy(
() => import('../components/Dashboard/Finance/Invoices/InvoiceInfo.jsx')
)
const Payments = lazy(
() => import('../components/Dashboard/Finance/Payments.jsx')
)
const PaymentInfo = lazy(
() => import('../components/Dashboard/Finance/Payments/PaymentInfo.jsx')
)
const FinanceOverview = lazy(
() => import('../components/Dashboard/Finance/FinanceOverview.jsx')
)
@ -28,12 +22,6 @@ const FinanceRoutes = [
key='invoices-info'
path='finance/invoices/info'
element={<InvoiceInfo />}
/>,
<Route key='payments' path='finance/payments' element={<Payments />} />,
<Route
key='payments-info'
path='finance/payments/info'
element={<PaymentInfo />}
/>
]

View File

@ -1,41 +0,0 @@
import { lazy } from 'react'
import { Route } from 'react-router-dom'
const Clients = lazy(
() => import('../components/Dashboard/Sales/Clients.jsx')
)
const ClientInfo = lazy(
() => import('../components/Dashboard/Sales/Clients/ClientInfo.jsx')
)
const SalesOrders = lazy(
() => import('../components/Dashboard/Sales/SalesOrders.jsx')
)
const SalesOrderInfo = lazy(
() => import('../components/Dashboard/Sales/SalesOrders/SalesOrderInfo.jsx')
)
const SalesOverview = lazy(
() => import('../components/Dashboard/Sales/SalesOverview.jsx')
)
const SalesRoutes = [
<Route
key='overview'
path='sales/overview'
element={<SalesOverview />}
/>,
<Route key='clients' path='sales/clients' element={<Clients />} />,
<Route
key='clients-info'
path='sales/clients/info'
element={<ClientInfo />}
/>,
<Route key='salesorders' path='sales/salesorders' element={<SalesOrders />} />,
<Route
key='salesorders-info'
path='sales/salesorders/info'
element={<SalesOrderInfo />}
/>
]
export default SalesRoutes

View File

@ -1,6 +1,5 @@
export { default as ProductionRoutes } from './ProductionRoutes'
export { default as InventoryRoutes } from './InventoryRoutes'
export { default as FinanceRoutes } from './FinanceRoutes'
export { default as SalesRoutes } from './SalesRoutes'
export { default as ManagementRoutes } from './ManagementRoutes'
export { default as DeveloperRoutes } from './DeveloperRoutes'