Compare commits
No commits in common. "main" and "windows-auth-fix" have entirely different histories.
main
...
windows-au
32
Jenkinsfile
vendored
@ -1,7 +1,3 @@
|
||||
properties([
|
||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||
])
|
||||
|
||||
def deploy() {
|
||||
node('ubuntu') {
|
||||
try {
|
||||
@ -24,7 +20,7 @@ def deploy() {
|
||||
|
||||
stage('Build (Ubuntu)') {
|
||||
nodejs(nodeJSInstallationName: 'Node23') {
|
||||
sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production pnpm build:cloudflare"
|
||||
sh 'NODE_ENV=production pnpm build:cloudflare'
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,41 +70,21 @@ def buildOnLabel(label, buildCommand) {
|
||||
stage("Build (${label})") {
|
||||
nodejs(nodeJSInstallationName: 'Node23') {
|
||||
if (isUnix()) {
|
||||
sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production ${buildCommand}"
|
||||
sh "NODE_ENV=production ${buildCommand}"
|
||||
} else {
|
||||
bat "set VITE_BUILD_NUMBER=${env.BUILD_NUMBER} && set NODE_ENV=production && ${buildCommand}"
|
||||
bat "set NODE_ENV=production && ${buildCommand}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("Archive Artifacts (${label})") {
|
||||
archiveArtifacts artifacts: 'app_dist/**/farmcontrol-*.dmg, app_dist/**/farmcontrol-*.exe, app_dist/**/farmcontrol-*.pkg, app_dist/**/farmcontrol-*.msi', fingerprint: true
|
||||
archiveArtifacts artifacts: 'app_dist/**/*.dmg, app_dist/**/*.exe', fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def setBuildNameFromPackageVersion() {
|
||||
node('ubuntu') {
|
||||
stage('Set Build Name') {
|
||||
checkout scm
|
||||
def version
|
||||
nodejs(nodeJSInstallationName: 'Node23') {
|
||||
version = sh(
|
||||
script: "node -p \"require('./package.json').version\"",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
}
|
||||
def buildName = "v${version}-b${env.BUILD_NUMBER}"
|
||||
currentBuild.displayName = buildName
|
||||
echo "Build name set to: ${buildName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
setBuildNameFromPackageVersion()
|
||||
|
||||
parallel(
|
||||
'Windows Build': buildOnLabel('windows', 'pnpm build:electron'),
|
||||
'MacOS Build': buildOnLabel('macos', 'pnpm build:electron'),
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 29 KiB |
@ -1,10 +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 72 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1.093797,0,0,1.093797,0.640357,-0.002979)">
|
||||
<path d="M39.703,63.992C33.273,63.965 32.923,63.847 31.428,63.333C28.308,62.274 25.677,60.059 24.093,57.166L23.605,56.273L22.819,56.189C21.521,56.058 19.247,55.63 17.735,55.249C11.032,53.558 6.21,50.355 4.507,46.45C4.352,46.116 4.138,45.39 4.031,44.83C3.459,41.997 4.555,39.068 7.079,36.734C7.626,36.222 7.876,35.877 8.031,35.424C8.138,35.091 8.341,34.638 8.46,34.436C8.686,34.067 8.686,34.055 8.341,33.305C7.4,31.269 7.388,29.197 8.329,27.244L8.686,26.506L8.4,25.947C7.698,24.577 7.436,22.732 7.757,21.386L7.9,20.767L7.103,20.041C4.519,17.636 3.4,14.6 4.066,11.718C4.781,8.539 7.198,5.896 11.389,3.681C15.104,1.728 19.473,0.585 25.236,0.073C26.51,-0.046 31.999,0.109 33.488,0.3C40.929,1.252 46.93,3.824 50.157,7.467C53.669,11.432 53.36,16.338 49.371,20.041L48.573,20.767L48.716,21.386C49.038,22.744 48.776,24.565 48.061,25.97C47.775,26.554 47.775,26.566 48.002,26.935C48.537,27.792 48.799,28.876 48.799,30.245C48.799,30.465 48.795,30.668 48.786,30.858C47.381,30.973 46.02,31.246 44.722,31.661C45.016,31.051 45.191,29.981 45.025,29.697C44.93,29.518 44.858,29.53 44.43,29.828C41.763,31.638 38.048,32.948 33.547,33.65C32.059,33.876 31.285,33.924 28.237,33.924C25.189,33.924 24.415,33.876 22.926,33.65C18.545,32.959 14.878,31.709 12.294,29.995C11.484,29.459 11.496,29.459 11.413,29.804C11.222,30.554 11.639,31.709 12.425,32.686C13.687,34.257 16.783,35.865 20.152,36.734C25.553,38.129 31.889,38.015 36.956,36.571C35.707,37.901 34.654,39.418 33.845,41.075C32.625,41.258 31.365,41.384 30.082,41.449L28.499,41.532L29.237,42.33C29.654,42.771 30.308,43.58 30.689,44.128C31.344,45.069 31.404,45.128 31.785,45.069C31.904,45.049 32.16,45.014 32.465,44.973C32.161,46.294 32,47.67 32,49.083C32,51.584 32.504,53.969 33.415,56.141C33.042,56.019 32.813,55.848 32.404,55.606C31.63,55.141 30.725,54.118 30.344,53.26C30.213,52.963 29.951,52.058 29.749,51.26C28.82,47.45 26.891,44.664 23.688,42.509C22.272,41.556 21.39,41.163 18.414,40.187C15.759,39.306 13.496,38.317 12.294,37.496C11.484,36.948 11.508,36.948 11.413,37.424C11.175,38.71 12.294,40.437 14.223,41.747C15.342,42.497 17.259,43.366 19.176,43.997C21.188,44.652 22.2,45.176 23.224,46.069C24.927,47.545 25.546,48.76 26.189,51.808C26.76,54.522 27.57,56.082 29.213,57.642C30.356,58.725 31.416,59.38 32.797,59.845L33.797,60.19L35.768,60.196C36.883,61.649 38.21,62.93 39.703,63.992ZM10.186,43.271C9.71,42.794 9.115,42.08 8.841,41.675L8.365,40.949L8.019,41.687C7.329,43.175 7.662,44.997 8.889,46.462C10.913,48.867 15.223,50.962 20.152,51.927C22.724,52.427 22.498,52.415 22.414,52.022C22.105,50.605 21.605,49.617 20.926,49.01C20.283,48.45 19.438,48.057 17.366,47.355C16.366,47.009 15.068,46.509 14.497,46.235C13.008,45.545 11.198,44.295 10.186,43.271ZM12.818,23.601C12.187,23.327 11.603,23.065 11.532,23.041C11.425,22.994 11.413,23.089 11.472,23.387C12.079,26.066 16.437,28.649 22.081,29.673C24.224,30.066 25.534,30.173 28.237,30.173C30.94,30.173 32.249,30.066 34.392,29.673C40.036,28.649 44.394,26.066 45.001,23.387C45.061,23.089 45.049,22.994 44.942,23.041C44.87,23.065 44.287,23.327 43.656,23.601C40.751,24.875 37.274,25.744 32.987,26.268C31.261,26.482 25.212,26.482 23.486,26.268C19.2,25.744 15.723,24.875 12.818,23.601ZM31.19,3.872C23.391,3.241 15.259,5.098 10.663,8.563C9.448,9.48 8.71,10.337 8.138,11.48C7.722,12.302 7.674,12.516 7.674,13.361C7.674,14.635 8.138,15.623 9.305,16.85C13.115,20.886 22.414,23.327 31.309,22.613C38.429,22.041 44.287,19.922 47.168,16.85C48.335,15.623 48.799,14.635 48.799,13.361C48.799,12.516 48.752,12.302 48.335,11.48C47.764,10.337 47.025,9.48 45.811,8.563C42.37,5.979 37.345,4.36 31.19,3.872ZM25.796,7.932C29.999,7.265 34.869,8.527 36.595,10.718C37.357,11.694 37.548,12.218 37.548,13.361C37.548,14.516 37.357,15.04 36.595,16.016C34.488,18.683 28.249,19.791 23.677,18.302C21.355,17.54 20.057,16.576 19.223,14.992C18.795,14.183 18.795,12.552 19.223,11.742C20.259,9.765 22.533,8.444 25.796,7.932ZM30.118,11.67C27.32,11.206 23.915,11.873 22.807,13.099C22.581,13.361 22.581,13.373 22.807,13.635C23.605,14.504 26.082,15.242 28.237,15.242C30.392,15.242 32.868,14.504 33.666,13.635C33.892,13.373 33.892,13.361 33.666,13.099C33.083,12.468 31.797,11.944 30.118,11.67Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,35.1075,-13.675)">
|
||||
<path d="M19.178,83.675L21.86,83.675C22.896,83.675 23.714,83.056 23.949,82.006L24.677,79.011C25.075,78.891 25.513,78.706 25.906,78.51L28.52,80.101C29.389,80.672 30.396,80.644 31.191,79.849L33.035,77.979C33.748,77.216 33.821,76.234 33.251,75.277L31.691,72.637C31.913,72.276 32.021,71.92 32.166,71.528L35.186,70.826C36.217,70.564 36.892,69.747 36.892,68.705L36.892,66.06C36.892,65.038 36.191,64.272 35.186,64.011L32.197,63.289C32.038,62.825 31.867,62.484 31.722,62.148L33.37,59.446C33.904,58.571 33.804,57.481 33.086,56.8L31.18,54.956C30.427,54.209 29.411,54.031 28.562,54.596L25.906,56.269C25.436,56.028 25.075,55.914 24.683,55.769L23.949,52.722C23.714,51.712 22.896,51.042 21.86,51.042L19.178,51.042C18.131,51.042 17.304,51.737 17.063,52.728L16.381,55.737C15.886,55.903 15.519,56.028 15.101,56.269L12.444,54.596C11.633,54.068 10.585,54.204 9.852,54.956L7.997,56.8C7.279,57.501 7.077,58.571 7.642,59.446L9.316,62.148C9.166,62.484 8.981,62.876 8.81,63.289L5.852,64.011C4.841,64.272 4.177,65.064 4.177,66.06L4.177,68.705C4.177,69.747 4.847,70.564 5.852,70.826L8.847,71.528C9.037,71.92 9.182,72.276 9.342,72.637L7.782,75.302C7.211,76.234 7.311,77.267 8.023,77.979L9.847,79.849C10.585,80.638 11.613,80.646 12.487,80.101L15.158,78.51C15.596,78.757 15.989,78.891 16.381,79.011L17.063,82.006C17.299,83.005 18.131,83.675 19.178,83.675ZM20.491,72.507C17.681,72.507 15.371,70.197 15.371,67.387C15.371,64.551 17.681,62.236 20.491,62.236C23.326,62.236 25.642,64.551 25.642,67.387C25.642,70.197 23.326,72.507 20.491,72.507Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
@ -1,8 +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.989624,0,0,0.989624,3,3.012667)">
|
||||
<path d="M32.232,55.642C32.232,57.251 30.901,58.583 29.291,58.583C27.682,58.583 26.351,57.251 26.351,55.642C26.351,54.032 27.682,52.701 29.291,52.701C30.901,52.701 32.232,54.032 32.232,55.642ZM32.232,46.867C32.232,48.476 30.901,49.807 29.291,49.807C27.682,49.807 26.351,48.476 26.351,46.867C26.351,45.258 27.682,43.926 29.291,43.926C30.901,43.926 32.232,45.258 32.232,46.867ZM32.232,38.066C32.232,39.676 30.901,41.007 29.291,41.007C28.398,41.007 27.591,40.597 27.05,39.956C27.916,38.466 28.763,36.862 29.604,35.143C31.07,35.301 32.232,36.562 32.232,38.066ZM58.608,29.291C58.608,30.901 57.277,32.232 55.667,32.232C54.058,32.232 52.727,30.901 52.727,29.291C52.727,27.682 54.058,26.351 55.667,26.351C57.277,26.351 58.608,27.682 58.608,29.291ZM5.881,29.291C5.881,30.901 4.55,32.232 2.941,32.232C1.331,32.232 0,30.901 0,29.291C0,27.682 1.331,26.351 2.941,26.351C4.55,26.351 5.881,27.682 5.881,29.291ZM14.656,29.291C14.656,30.901 13.351,32.232 11.716,32.232C10.106,32.232 8.775,30.901 8.775,29.291C8.775,27.682 10.106,26.351 11.716,26.351C13.351,26.351 14.656,27.682 14.656,29.291ZM23.457,29.291C23.457,30.901 22.126,32.232 20.516,32.232C18.907,32.232 17.576,30.901 17.576,29.291C17.576,27.682 18.907,26.351 20.516,26.351C22.126,26.351 23.457,27.682 23.457,29.291ZM41.032,29.291C41.032,30.901 39.701,32.232 38.092,32.232C36.483,32.232 35.151,30.901 35.151,29.291C35.151,27.682 36.483,26.351 38.092,26.351C39.701,26.351 41.032,27.682 41.032,29.291ZM49.807,29.291C49.807,30.901 48.476,32.232 46.867,32.232C45.258,32.232 43.926,30.901 43.926,29.291C43.926,27.682 45.258,26.351 46.867,26.351C48.476,26.351 49.807,27.682 49.807,29.291ZM31.442,31.281C31.58,30.996 31.718,30.691 31.857,30.384C31.977,30.118 32.096,29.855 32.214,29.607C32.145,30.248 31.864,30.83 31.442,31.281ZM26.771,28.065L26.377,28.924C26.451,28.32 26.715,27.77 27.108,27.338L26.771,28.065ZM31.523,18.59C30.658,20.084 29.813,21.691 28.972,23.413C27.51,23.255 26.351,22.016 26.351,20.491C26.351,18.881 27.682,17.55 29.291,17.55C30.179,17.55 30.983,17.955 31.523,18.59ZM32.232,11.716C32.232,13.325 30.901,14.656 29.291,14.656C27.682,14.656 26.351,13.325 26.351,11.716C26.351,10.106 27.682,8.775 29.291,8.775C30.901,8.775 32.232,10.106 32.232,11.716ZM32.232,2.941C32.232,4.55 30.901,5.881 29.291,5.881C27.682,5.881 26.351,4.55 26.351,2.941C26.351,1.331 27.682,0 29.291,0C30.901,0 32.232,1.331 32.232,2.941Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M2.803,52.396C1.307,52.643 0.237,54.108 0.504,55.597C0.751,57.093 2.216,58.169 3.711,57.928C17.696,55.598 24.556,46.57 31.857,30.384C38.113,16.508 43.608,8.067 55.805,6.052C57.295,5.806 58.371,4.341 58.129,2.851C57.851,1.356 56.392,0.28 54.897,0.521C40.932,2.825 34.078,11.879 26.771,28.065C20.515,41.941 15,50.382 2.803,52.396Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
@ -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.824327,0,0,0.976141,2.488276,-2.712014)">
|
||||
<path d="M56.78,22.995C64.017,23.632 69.667,28.805 69.667,35.084L69.667,42.696C69.667,49.405 63.217,54.851 55.273,54.851L52.514,54.851L39.525,64.685C37.406,66.289 34.196,66.289 32.077,64.685L19.088,54.851L16.329,54.851C8.385,54.851 1.935,49.405 1.935,42.696L1.935,35.084C1.935,28.805 7.585,23.632 14.822,22.995L14.822,8.007C14.822,6.476 16.294,5.233 18.107,5.233C19.921,5.233 21.393,6.476 21.393,8.007L21.393,22.929L50.209,22.929L50.209,8.007C50.209,6.476 51.681,5.233 53.495,5.233C55.308,5.233 56.78,6.476 56.78,8.007L56.78,22.995ZM28.932,54.851L35.801,60.052L42.67,54.851L28.932,54.851ZM55.273,49.302C59.591,49.302 63.096,46.342 63.096,42.696L63.096,35.084C63.096,31.438 59.591,28.478 55.273,28.478L16.329,28.478C12.011,28.478 8.506,31.438 8.506,35.084L8.506,42.696C8.506,46.342 12.011,49.302 16.329,49.302L55.273,49.302Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@ -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.881796,0,0,0.881796,2.413866,6)">
|
||||
<path d="M8.144,48.624L11.105,53.718L33.55,42.441L55.994,53.718L58.987,48.624L36.537,37.341L36.537,10.769L30.594,10.769L30.594,37.341L8.144,48.624ZM0.384,53.413C-0.592,55.654 0.33,57.211 2.725,57.489L15.69,58.924C18.959,59.318 20.849,57.171 19.138,54.231L11.408,40.828C9.765,37.956 6.931,38.488 5.654,41.467L0.384,53.413ZM66.721,53.413L61.477,41.467C60.174,38.488 57.366,37.956 55.717,40.828L47.962,54.231C46.256,57.171 48.172,59.318 51.415,58.924L64.38,57.489C66.77,57.211 67.697,55.654 66.721,53.413ZM31.172,1.489L23.477,11.964C21.527,14.591 22.46,17.332 25.814,17.332L41.248,17.332C44.665,17.332 45.558,14.639 43.592,11.97L35.87,1.484C34.395,-0.486 32.642,-0.506 31.172,1.489Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -2,9 +2,6 @@
|
||||
<!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.069972,0,0,0.069972,-2.986098,-11.802594)">
|
||||
<path d="M242,226C206,232 174.833,248.333 148.5,275C122.167,301.667 106,333 100,369L100,884C106,919.333 122,950 148,976C174,1002 204.333,1018.333 239,1025L243,1026L758,1026L761,1025C796.333,1018.333 826.833,1001.833 852.5,975.5C878.167,949.167 894,918.333 900,883L900,368C894,332.667 877.667,301.667 851,275C824.333,248.333 793,232 757,226L242,226Z" style="fill:rgb(255,152,0);fill-rule:nonzero;"/>
|
||||
<g transform="matrix(14.291431,0,0,14.291431,42.675613,168.675956)">
|
||||
<path d="M35.778,26.682C38.344,26.682 42.193,26.705 47.324,26.752L53.061,26.752L53.061,32.07L33.259,32.14L33.259,26.682L35.778,26.682ZM53.131,10.938L53.131,16.256C48.187,16.303 41.563,16.303 33.259,16.256L33.329,11.008L53.131,10.938ZM30.74,18.845L30.74,24.163L11.008,24.093L11.008,18.915L30.74,18.845ZM30.67,11.008C30.717,12.175 30.74,13.924 30.74,16.256L11.008,16.256L11.008,11.008L30.67,11.008ZM18.775,37.178C19.941,37.178 21.073,37.423 22.169,37.913C23.265,38.402 24.186,39.09 24.933,39.977C25.959,41.143 26.542,42.531 26.682,44.14C26.822,45.749 26.542,47.289 25.842,48.758C25.143,50.228 24.093,51.312 22.694,52.012C21.387,52.758 19.93,53.073 18.32,52.956C16.711,52.84 15.265,52.315 13.982,51.382C12.699,50.449 11.848,49.236 11.428,47.744C10.868,46.391 10.752,44.968 11.078,43.475C11.405,41.983 12.081,40.688 13.107,39.592C14.134,38.496 15.37,37.784 16.816,37.458C17.469,37.271 18.122,37.178 18.775,37.178ZM33.259,18.845L53.131,18.845L53.131,24.163L33.259,24.163L33.259,18.845ZM30.74,26.752L30.74,32.14L11.008,32.14L11.008,26.752L30.74,26.752ZM33.259,34.729L53.061,34.729C53.108,35.568 53.108,36.851 53.061,38.577L53.061,39.977L33.329,39.977L33.259,34.729Z" style="fill:white;"/>
|
||||
</g>
|
||||
<path d="M242,226C206,232 174.833,248.333 148.5,275C122.167,301.667 106,333 100,369L100,884C106,919.333 122,950 148,976C174,1002 204.333,1018.333 239,1025L243,1026L758,1026L761,1025C796.333,1018.333 826.833,1001.833 852.5,975.5C878.167,949.167 894,918.333 900,883L900,368C894,332.667 877.667,301.667 851,275C824.333,248.333 793,232 757,226L242,226ZM802,325L802,401C731.333,401.667 636.667,401.667 518,401L519,326L802,325ZM481,326C481.667,342.667 482,367.667 482,401L200,401L200,326L481,326ZM518,438L802,438L802,514L518,514L518,438ZM482,438L482,514L200,513L200,439L482,438ZM554,550C590.667,550 645.667,550.333 719,551L801,551L801,627L518,628L518,550L554,550ZM482,550L482,628L200,628L200,551L482,551L482,550ZM518,665L801,665C801.667,677 801.667,695.333 801,720L801,740L519,740L518,665ZM311,700C327.667,700 343.833,703.5 359.5,710.5C375.167,717.5 388.333,727.333 399,740C413.667,756.667 422,776.5 424,799.5C426,822.5 422,844.5 412,865.5C402,886.5 387,902 367,912C348.333,922.667 327.5,927.167 304.5,925.5C281.5,923.833 260.833,916.333 242.5,903C224.167,889.667 212,872.333 206,851C198,831.667 196.333,811.333 201,790C205.667,768.667 215.333,750.167 230,734.5C244.667,718.833 262.333,708.667 283,704C292.333,701.333 301.667,700 311,700Z" style="fill:rgb(255,152,0);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.8 KiB |
@ -1,26 +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(1,0,0,1,-82,0)">
|
||||
<g id="Artboard1" transform="matrix(1,0,0,1,82,0)">
|
||||
<rect x="0" y="0" width="64" height="64" style="fill:none;"/>
|
||||
<clipPath id="_clip1">
|
||||
<rect x="0" y="0" width="64" height="64"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip1)">
|
||||
<g transform="matrix(1,0,0,1,-82,0)">
|
||||
<path d="M114.235,58.85L101,58.85C93.825,58.85 88,53.025 88,45.85L88,18.367C88,11.192 93.825,5.367 101,5.367L127,5.367C134.175,5.367 140,11.192 140,18.367L140,31.684C138.344,30.666 136.522,29.892 134.583,29.414L134.583,18.367C134.583,14.182 131.185,10.784 127,10.784L101,10.784C96.815,10.784 93.417,14.182 93.417,18.367L93.417,45.85C93.417,50.036 96.815,53.434 101,53.434L111.638,53.434C112.218,55.387 113.102,57.211 114.235,58.85Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.121769,0,0,0.121769,14.308464,17.834503)">
|
||||
<path d="M8.945,187.103L8.945,45C8.945,41.153 10.395,37.687 13.54,34.715C16.57,31.856 19.983,30.533 23.684,30.533L101.541,30.533C105.244,30.533 108.657,31.856 111.685,34.716C114.831,37.688 116.28,41.153 116.28,45L116.28,63.239C116.28,67.084 114.831,70.551 111.685,73.523C108.657,76.383 105.244,77.706 101.541,77.706L59.112,77.706L59.112,99.952L95.825,99.952C99.527,99.952 102.939,101.273 105.967,104.134C109.115,107.105 110.563,110.572 110.563,114.418L110.563,132.658C110.563,136.503 109.116,139.968 105.968,142.941C102.939,145.803 99.527,147.124 95.825,147.124L59.112,147.123L59.112,187.103C59.112,190.807 57.79,194.219 54.928,197.248C51.956,200.394 48.491,201.842 44.646,201.842L23.412,201.842C19.565,201.842 16.1,200.393 13.128,197.247C10.268,194.219 8.946,190.807 8.945,187.103Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.121769,0,0,0.121769,-40.771914,17.834503)">
|
||||
<path d="M591.369,171.626C580.987,156.298 575.724,137.883 575.724,116.331C575.724,91.129 582.921,70.216 597.085,53.512C611.534,36.472 631.157,27.818 656.07,27.818C677.329,27.818 694.356,32.953 707.26,42.842C720.259,52.803 729.209,67.651 733.791,87.574C734,88.48 734.021,89.418 733.855,90.333C733.839,90.422 733.822,90.511 733.804,90.6C732.171,90.549 730.531,90.524 728.886,90.524C713.323,90.524 698.283,92.793 684.08,97.019C683.789,96.57 683.51,96.096 683.244,95.596C683,95.139 682.809,94.657 682.674,94.157C680.956,87.783 678.047,82.918 673.679,79.732C669.187,76.455 663.286,74.99 656.07,74.99C646.542,74.99 639.4,78.26 634.849,85.227C629.672,93.155 627.252,103.55 627.252,116.331C627.252,120.193 627.479,123.842 627.944,127.277C613.205,139.652 600.767,154.681 591.369,171.626Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.485113,0,0,0.485113,32,32.108844)">
|
||||
<path d="M66.016,33C66.016,51.203 51.234,66 33,66C14.797,66 0,51.203 0,33C0,14.781 14.797,0 33,0C51.234,0 66.016,14.781 66.016,33ZM27.094,19.5C24.859,19.5 23.438,20.75 23.438,22.734C23.438,24.734 24.859,25.969 27.078,25.969L31.812,25.969L35.847,25.339L31.234,29.375L20.781,39.828C20.031,40.562 19.609,41.578 19.609,42.547C19.609,44.75 21.234,46.219 23.281,46.219C24.375,46.219 25.312,45.844 26.156,45.016L36.516,34.656L40.507,30.084L39.969,34.312L39.969,38.75C39.969,40.953 41.219,42.391 43.234,42.391C45.188,42.391 46.438,40.922 46.438,38.75L46.438,23.797C46.438,20.984 44.812,19.5 42.094,19.5L27.094,19.5Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
@ -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;">
|
||||
<rect id="Artboard1" x="0" y="0" width="64" height="64" style="fill:none;"/>
|
||||
<g id="Artboard11" serif:id="Artboard1">
|
||||
<g transform="matrix(0.787693,0,0,0.787693,6,6.006144)">
|
||||
<path d="M32.595,65.998C14.563,65.781 0,51.084 0,33C0,14.781 14.781,0 33,0C51.1,0 65.797,14.564 66.013,32.597C64.003,31.361 61.8,30.409 59.459,29.798C58.946,25.474 57.39,21.467 55.038,18.044C53.252,19.386 50.843,20.54 47.998,21.441C48.59,23.904 48.996,26.56 49.179,29.359C44.485,30.163 40.247,32.317 36.882,35.406L35.437,35.406L35.437,36.848C32.698,39.819 30.69,43.476 29.7,47.53C22.786,47.922 16.949,49.55 13.541,52.117C13.713,51.988 13.892,51.861 14.082,51.738C17.087,54.794 20.831,57.118 25.018,58.434C22.762,56.224 20.842,53.084 19.416,49.278C20.904,48.804 22.542,48.417 24.297,48.124C25.593,51.493 27.274,54.199 29.137,55.808C29.348,57.858 29.815,59.833 30.505,61.701C28.928,61.293 27.428,60.491 26.037,59.349C27.43,60.505 28.934,61.317 30.515,61.729C31.07,63.227 31.769,64.655 32.595,65.998ZM6.474,35.406C6.886,40.076 8.511,44.4 11.044,48.053C12.829,46.735 15.216,45.6 18.025,44.712C17.325,41.847 16.882,38.72 16.753,35.406L6.474,35.406ZM52.484,14.02C52.335,14.133 52.181,14.244 52.014,14.35C48.978,11.231 45.176,8.866 40.916,7.541C43.22,9.772 45.175,12.973 46.615,16.866C45.134,17.339 43.502,17.726 41.755,18.022C40.131,13.743 37.888,10.527 35.437,9.228L35.437,18.669C42.758,18.365 48.953,16.691 52.484,14.02ZM30.697,4.271C28.855,4.713 27.116,5.696 25.532,7.13C27.115,5.709 28.851,4.737 30.688,4.299L30.697,4.271ZM21.697,30.562L30.594,30.562L30.594,23.513C27.88,23.402 25.274,23.103 22.853,22.645C22.247,25.113 21.847,27.793 21.697,30.562ZM40.497,7.15C38.915,5.712 37.177,4.726 35.335,4.279L35.343,4.307C37.181,4.749 38.916,5.726 40.497,7.15ZM10.972,18.051C8.486,21.673 6.889,25.948 6.478,30.562L16.757,30.562C16.893,27.318 17.327,24.253 18.005,21.438C15.168,20.539 12.761,19.388 10.972,18.051ZM25.091,7.542C20.832,8.867 17.03,11.234 13.995,14.355C13.823,14.245 13.663,14.131 13.509,14.015C17.058,16.692 23.268,18.369 30.594,18.67L30.594,9.212C28.138,10.505 25.887,13.729 24.26,18.022C22.509,17.726 20.874,17.338 19.389,16.863C20.831,12.972 22.788,9.772 25.091,7.542ZM22.875,43.507C25.29,43.051 27.889,42.754 30.594,42.643L30.594,35.406L21.687,35.406C21.835,38.238 22.247,40.982 22.875,43.507ZM43.162,22.644C40.747,23.101 38.146,23.399 35.437,23.512L35.437,30.562L44.319,30.562C44.169,27.793 43.769,25.113 43.162,22.644Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.485113,0,0,0.485113,31.974958,31.982526)">
|
||||
<path d="M66.016,33C66.016,51.203 51.234,66 33,66C14.797,66 0,51.203 0,33C0,14.781 14.797,0 33,0C51.234,0 66.016,14.781 66.016,33ZM27.094,19.5C24.859,19.5 23.438,20.75 23.438,22.734C23.438,24.734 24.859,25.969 27.078,25.969L31.812,25.969L35.847,25.339L31.234,29.375L20.781,39.828C20.031,40.562 19.609,41.578 19.609,42.547C19.609,44.75 21.234,46.219 23.281,46.219C24.375,46.219 25.312,45.844 26.156,45.016L36.516,34.656L40.507,30.084L39.969,34.312L39.969,38.75C39.969,40.953 41.219,42.391 43.234,42.391C45.188,42.391 46.438,40.922 46.438,38.75L46.438,23.797C46.438,20.984 44.812,19.5 42.094,19.5L27.094,19.5Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@ -1,8 +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.783639,0,0,0.783639,3,9.136381)">
|
||||
<path d="M10.966,55.715L61.631,55.715C67.901,55.715 71.026,52.48 70.761,46.429L52.249,29.059C50.874,27.784 49.224,27.139 47.553,27.139C45.84,27.139 44.303,27.727 42.877,29.003L28.763,41.616L22.999,36.414C21.686,35.233 20.243,34.639 18.78,34.639C17.334,34.639 16.041,35.201 14.754,36.383L2.961,46.953C3.114,52.711 5.792,55.715 10.966,55.715ZM10.251,58.341L63.763,58.341C70.533,58.341 74.014,54.886 74.014,48.218L74.014,10.16C74.014,3.486 70.533,0.011 63.763,0.011L10.251,0.011C3.506,0.011 0,3.486 0,10.16L0,48.218C0,54.886 3.506,58.341 10.251,58.341ZM10.606,52.219C7.701,52.219 6.122,50.714 6.122,47.678L6.122,10.694C6.122,7.658 7.701,6.134 10.606,6.134L63.407,6.134C66.287,6.134 67.891,7.658 67.891,10.694L67.891,47.678C67.891,50.714 66.287,52.219 63.407,52.219L10.606,52.219Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M24.209,29.743C28.302,29.743 31.677,26.369 31.677,22.239C31.677,18.146 28.302,14.74 24.209,14.74C20.079,14.74 16.705,18.146 16.705,22.239C16.705,26.369 20.079,29.743 24.209,29.743Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,8 +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 72 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M38.645,64.222L17.549,64.222C11.671,64.198 8.506,61.129 8.506,55.228L8.506,13.858C8.506,7.958 11.939,4.864 17.619,4.864L54.008,4.864C59.907,4.864 63.096,7.958 63.096,13.858L63.096,35.121C61.386,34.433 59.559,33.974 57.653,33.783L57.653,22.31L54.347,22.31L54.347,23.05C54.347,27.911 52.199,30.851 48.168,31.749L45.342,34.575C44.524,35.393 43.197,35.393 42.379,34.575L39.549,31.745C35.516,30.843 33.374,27.904 33.374,23.05L33.374,22.31L13.973,22.31L13.973,55.44C13.973,57.721 15.098,58.965 17.297,59.072C17.269,58.754 17.255,58.42 17.255,58.07L17.255,54.215C17.255,49.957 19.434,48.069 23.402,48.069L36.442,48.069C35.975,49.668 35.702,51.349 35.651,53.085L24.624,53.085C23.237,53.085 22.516,53.749 22.516,55.165L22.516,57.102C22.516,58.518 23.237,59.21 24.624,59.21L36.417,59.21C36.93,60.995 37.685,62.679 38.645,64.222ZM57.653,17.418L57.653,13.652C57.653,11.264 56.396,10.001 54.013,10.001L17.613,10.001C15.206,10.001 13.973,11.264 13.973,13.652L13.973,17.418L33.807,17.418C34.894,13.675 37.985,11.731 42.487,11.731L45.258,11.731C49.934,11.731 52.908,13.675 53.94,17.418L57.653,17.418ZM42.481,16.868C40.074,16.868 38.841,18.13 38.841,20.518L38.841,23.262C38.841,25.65 40.074,26.902 42.481,26.902L45.264,26.902C47.647,26.902 48.904,25.65 48.904,23.262L48.904,20.518C48.904,18.149 47.666,16.888 45.32,16.868L42.481,16.868Z"/>
|
||||
<g transform="matrix(1,0,0,1,35.1075,-13.675)">
|
||||
<path d="M19.178,83.675L21.86,83.675C22.896,83.675 23.714,83.056 23.949,82.006L24.677,79.011C25.075,78.891 25.513,78.706 25.906,78.51L28.52,80.101C29.389,80.672 30.396,80.644 31.191,79.849L33.035,77.979C33.748,77.216 33.821,76.234 33.251,75.277L31.691,72.637C31.913,72.276 32.021,71.92 32.166,71.528L35.186,70.826C36.217,70.564 36.892,69.747 36.892,68.705L36.892,66.06C36.892,65.038 36.191,64.272 35.186,64.011L32.197,63.289C32.038,62.825 31.867,62.484 31.722,62.148L33.37,59.446C33.904,58.571 33.804,57.481 33.086,56.8L31.18,54.956C30.427,54.209 29.411,54.031 28.562,54.596L25.906,56.269C25.436,56.028 25.075,55.914 24.683,55.769L23.949,52.722C23.714,51.712 22.896,51.042 21.86,51.042L19.178,51.042C18.131,51.042 17.304,51.737 17.063,52.728L16.381,55.737C15.886,55.903 15.519,56.028 15.101,56.269L12.444,54.596C11.633,54.068 10.585,54.204 9.852,54.956L7.997,56.8C7.279,57.501 7.077,58.571 7.642,59.446L9.316,62.148C9.166,62.484 8.981,62.876 8.81,63.289L5.852,64.011C4.841,64.272 4.177,65.064 4.177,66.06L4.177,68.705C4.177,69.747 4.847,70.564 5.852,70.826L8.847,71.528C9.037,71.92 9.182,72.276 9.342,72.637L7.782,75.302C7.211,76.234 7.311,77.267 8.023,77.979L9.847,79.849C10.585,80.638 11.613,80.646 12.487,80.101L15.158,78.51C15.596,78.757 15.989,78.891 16.381,79.011L17.063,82.006C17.299,83.005 18.131,83.675 19.178,83.675ZM20.491,72.507C17.681,72.507 15.371,70.197 15.371,67.387C15.371,64.551 17.681,62.236 20.491,62.236C23.326,62.236 25.642,64.551 25.642,67.387C25.642,70.197 23.326,72.507 20.491,72.507Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@ -1,18 +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.069972,0,0,0.069972,-2.986098,-11.802594)">
|
||||
<path d="M242,226C206,232 174.833,248.333 148.5,275C122.167,301.667 106,333 100,369L100,884C106,919.333 122,950 148,976C174,1002 204.333,1018.333 239,1025L243,1026L758,1026L761,1025C796.333,1018.333 826.833,1001.833 852.5,975.5C878.167,949.167 894,918.333 900,883L900,368C894,332.667 877.667,301.667 851,275C824.333,248.333 793,232 757,226L242,226Z" style="fill:rgb(204,93,21);fill-rule:nonzero;"/>
|
||||
<g transform="matrix(1,0,0,1,0.001401,0)">
|
||||
<g transform="matrix(3.343963,0,0,3.343963,60.268889,206.334409)">
|
||||
<circle cx="68" cy="189" r="24" style="fill:white;"/>
|
||||
</g>
|
||||
<g transform="matrix(3.343963,0,0,3.343963,60.268889,206.334409)">
|
||||
<path d="M160,213L126,213C126,168.016 88.984,131 44,131L44,97C107.636,97 160,149.364 160,213Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(3.343963,0,0,3.343963,60.268889,206.334409)">
|
||||
<path d="M184,213C184,136.198 120.802,73 44,73L44,38C140.002,38 219,116.998 219,213L184,213Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@ -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.781552,0,0,0.781552,1,17.941979)">
|
||||
<path d="M79.329,9.044C79.329,3.244 76.045,0.021 70.183,0.021L9.093,0.021C3.221,0.021 0,3.244 0,9.044L0,26.911C0,32.701 3.263,35.953 9.125,35.953L70.225,35.953C76.077,35.953 79.329,32.701 79.329,26.911L79.329,9.044ZM72.826,10.089L72.826,25.876C72.826,28.201 71.564,29.461 69.323,29.461L9.964,29.461C7.775,29.461 6.482,28.201 6.482,25.876L6.482,10.089C6.482,7.763 7.785,6.503 9.995,6.503L69.354,6.503C71.564,6.503 72.826,7.763 72.826,10.089ZM13.04,4.664L10.112,4.664L10.112,20.338C10.112,21.156 10.774,21.776 11.602,21.776C12.461,21.776 13.04,21.134 13.04,20.297L13.04,4.664ZM18.661,4.664L15.732,4.664L15.732,15.295C15.732,16.144 16.404,16.744 17.211,16.744C18.092,16.744 18.661,16.123 18.661,15.264L18.661,4.664ZM24.27,4.664L21.32,4.664L21.32,15.295C21.32,16.144 22.014,16.744 22.81,16.744C23.67,16.744 24.27,16.123 24.27,15.264L24.27,4.664ZM29.849,4.664L26.92,4.664L26.92,15.295C26.92,16.144 27.592,16.744 28.42,16.744C29.279,16.744 29.849,16.123 29.849,15.264L29.849,4.664ZM35.479,4.664L32.55,4.664L32.55,15.295C32.55,16.144 33.222,16.744 34.019,16.744C34.9,16.744 35.479,16.123 35.479,15.264L35.479,4.664ZM41.11,4.664L38.181,4.664L38.181,20.338C38.181,21.156 38.843,21.776 39.65,21.776C40.53,21.776 41.11,21.134 41.11,20.297L41.11,4.664ZM46.709,4.664L43.781,4.664L43.781,15.295C43.781,16.144 44.452,16.744 45.259,16.744C46.118,16.744 46.709,16.123 46.709,15.264L46.709,4.664ZM52.319,4.664L49.39,4.664L49.39,15.295C49.39,16.144 50.062,16.744 50.88,16.744C51.739,16.744 52.319,16.123 52.319,15.264L52.319,4.664ZM57.918,4.664L54.999,4.664L54.999,15.295C54.999,16.144 55.661,16.744 56.468,16.744C57.348,16.744 57.918,16.123 57.918,15.264L57.918,4.664ZM63.548,4.664L60.62,4.664L60.62,15.295C60.62,16.144 61.292,16.744 62.088,16.744C62.948,16.744 63.548,16.123 63.548,15.264L63.548,4.664ZM69.158,4.664L66.229,4.664L66.229,20.338C66.229,21.156 66.891,21.776 67.719,21.776C68.578,21.776 69.158,21.134 69.158,20.297L69.158,4.664Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,8 +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.800063,0,0,0.800063,1,5.597937)">
|
||||
<path d="M75.036,26.688L59.489,26.688C57.02,26.688 56.427,28.797 57.739,30.578L65.442,40.938C66.427,42.266 68.099,42.281 69.083,40.938L76.802,30.594C78.145,28.797 77.52,26.688 75.036,26.688ZM38.755,8.219C52.458,8.219 63.536,19.297 63.536,33.031C63.536,35.281 65.38,37.125 67.661,37.125C69.911,37.125 71.739,35.297 71.755,33.047C71.739,14.75 56.974,0 38.755,0C30.599,0 23.161,3.078 17.583,8.094C15.364,9.812 15.552,12.531 16.974,14.109C18.239,15.547 20.395,16.141 22.802,14.391C26.974,10.547 32.583,8.219 38.755,8.219ZM2.458,39.312L18.02,39.312C20.474,39.312 21.067,37.188 19.77,35.422L12.052,25.047C11.067,23.734 9.411,23.719 8.427,25.047L0.692,35.406C-0.651,37.188 -0.026,39.312 2.458,39.312ZM38.755,57.781C25.052,57.781 13.958,46.703 13.958,32.969C13.958,30.703 12.114,28.859 9.833,28.859C7.583,28.859 5.755,30.688 5.739,32.938C5.755,51.234 20.52,66 38.755,66C46.911,66 54.349,62.922 59.911,57.906C62.13,56.156 61.942,53.469 60.52,51.875C59.255,50.453 57.099,49.844 54.692,51.594C50.52,55.422 44.911,57.781 38.755,57.781Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M37.411,48.156L39.974,48.156C40.927,48.156 41.661,47.578 41.864,46.641L42.552,43.812C42.942,43.656 43.317,43.516 43.661,43.344L46.145,44.875C46.942,45.375 47.911,45.297 48.552,44.625L50.317,42.844C50.989,42.172 51.083,41.188 50.567,40.391L49.067,37.938C49.224,37.594 49.38,37.234 49.489,36.875L52.349,36.203C53.27,35.984 53.864,35.25 53.864,34.266L53.864,31.781C53.864,30.828 53.27,30.094 52.349,29.891L49.52,29.219C49.395,28.812 49.224,28.438 49.083,28.109L50.599,25.625C51.114,24.812 51.036,23.859 50.349,23.188L48.536,21.406C47.88,20.797 46.989,20.641 46.192,21.109L43.661,22.672C43.317,22.516 42.974,22.359 42.567,22.219L41.864,19.344C41.645,18.406 40.927,17.828 39.974,17.828L37.411,17.828C36.458,17.828 35.739,18.406 35.52,19.359L34.833,22.188C34.442,22.328 34.052,22.484 33.692,22.656L31.192,21.109C30.38,20.641 29.505,20.766 28.833,21.406L27.02,23.188C26.349,23.859 26.27,24.812 26.77,25.625L28.286,28.109C28.145,28.438 28.005,28.812 27.849,29.219L25.02,29.891C24.099,30.094 23.52,30.828 23.52,31.781L23.52,34.266C23.52,35.25 24.099,35.984 25.02,36.203L27.88,36.875C28.02,37.234 28.145,37.594 28.302,37.938L26.833,40.391C26.302,41.188 26.395,42.172 27.067,42.844L28.817,44.625C29.489,45.297 30.442,45.375 31.239,44.875L33.708,43.344C34.067,43.516 34.442,43.656 34.833,43.812L35.52,46.641C35.739,47.578 36.458,48.156 37.411,48.156ZM38.692,37.797C36.02,37.797 33.849,35.641 33.849,32.969C33.849,30.312 36.02,28.125 38.692,28.125C41.349,28.125 43.52,30.312 43.52,32.969C43.52,35.641 41.349,37.797 38.692,37.797Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@ -39,9 +39,6 @@
|
||||
.ant-splitter,
|
||||
.ant-steps-item-title,
|
||||
.ant-steps-icon,
|
||||
.ant-color-picker-presets-label,
|
||||
.ant-select-selection-item,
|
||||
.ant-color-picker-trigger-text,
|
||||
[class*=' ant-radio'] {
|
||||
font-family: 'DM Sans';
|
||||
}
|
||||
@ -83,23 +80,6 @@
|
||||
font-size: 95%;
|
||||
}
|
||||
|
||||
.ant-select-selection-item .country-display {
|
||||
margin-left: 1px !important;
|
||||
}
|
||||
|
||||
.object-display-tag,
|
||||
.object-type-display-tag,
|
||||
.country-display {
|
||||
opacity: 1;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.object-select.ant-select-focused .object-display-tag,
|
||||
.ant-select-focused .object-type-display-tag,
|
||||
.ant-select-focused .country-display {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.flag {
|
||||
line-height: 0.7;
|
||||
}
|
||||
@ -139,8 +119,6 @@
|
||||
|
||||
:root {
|
||||
--unit-100vh: 100vh;
|
||||
--scrollbox-vertical-right-padding: 16px;
|
||||
--scrollbox-horizontal-bottom-padding: 16px;
|
||||
}
|
||||
@supports (height: 100dvh) {
|
||||
:root {
|
||||
@ -218,7 +196,6 @@ code {
|
||||
/* --- Start of src/index.css --- */
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ant-modal-mask {
|
||||
@ -281,11 +258,6 @@ body {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.farmcontrol-splitter.large > .ant-splitter-bar {
|
||||
margin: 11px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
--sb-track-color: #1d1d1d;
|
||||
--sb-thumb-color: #848484;
|
||||
@ -359,36 +331,15 @@ body {
|
||||
margin-left: 1px !important;
|
||||
}
|
||||
|
||||
.tags-input.ant-select-multiple .ant-select-selection-item,
|
||||
.tags-input.ant-tree-select.ant-select-multiple .ant-select-selection-item {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
margin-inline-end: 0 !important;
|
||||
height: auto !important;
|
||||
line-height: normal !important;
|
||||
}
|
||||
|
||||
.tags-input.ant-select-multiple .ant-select-selection-item-remove,
|
||||
.tags-input.ant-tree-select.ant-select-multiple
|
||||
.ant-select-selection-item-remove {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-badge.ant-badge-status {
|
||||
line-height: 18.5px;
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-vertical {
|
||||
right: calc(-1 * var(--scrollbox-vertical-right-padding));
|
||||
right: -16px;
|
||||
width: 8px !important;
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-horizontal {
|
||||
bottom: calc(-1 * var(--scrollbox-horizontal-bottom-padding));
|
||||
height: 8px !important;
|
||||
}
|
||||
|
||||
.simplebar-scrollbar:before {
|
||||
background: #78787854 !important;
|
||||
}
|
||||
@ -418,7 +369,7 @@ body {
|
||||
margin-right: 1px !important;
|
||||
}
|
||||
|
||||
.object-select .ant-select-selection-item .object-display-tag {
|
||||
.ant-select-selection-item .object-display-tag {
|
||||
top: 1.5px;
|
||||
}
|
||||
|
||||
@ -580,7 +531,7 @@ body {
|
||||
.markdown p {
|
||||
font-size: 14px;
|
||||
margin: 10px 0;
|
||||
line-height: 1.6;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
@ -603,10 +554,6 @@ body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.property-changes .markdown {
|
||||
width: unset;
|
||||
}
|
||||
|
||||
.markdown ul,
|
||||
.markdown ol {
|
||||
padding-left: 20px;
|
||||
@ -640,7 +587,6 @@ body {
|
||||
|
||||
.h-100 {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.ant-table.ant-table-middle .ant-table-filter-trigger {
|
||||
@ -650,195 +596,3 @@ body {
|
||||
.ant-table-wrapper .ant-table-filter-column {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
||||
width: 50px;
|
||||
min-width: 0;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.sidebar-scroll .simplebar-track.simplebar-vertical {
|
||||
right: -1px;
|
||||
width: 0px !important;
|
||||
transition:
|
||||
width 0.15s ease-in-out,
|
||||
right 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.sidebar-scroll .simplebar-track.simplebar-vertical {
|
||||
border-radius: 0 0 6.5px 0;
|
||||
}
|
||||
|
||||
.sidebar-scroll.dark .simplebar-track.simplebar-vertical {
|
||||
background-color: #141414;
|
||||
border-inline-end: 1px solid rgba(253, 253, 253, 0.12);
|
||||
border-bottom: 1px solid rgba(253, 253, 253, 0.12);
|
||||
}
|
||||
|
||||
.sidebar-scroll.light .simplebar-track.simplebar-vertical {
|
||||
background-color: #ffffff;
|
||||
border-inline-end: 1px solid rgba(5, 5, 5, 0.06);
|
||||
border-bottom: 1px solid rgba(5, 5, 5, 0.06);
|
||||
}
|
||||
|
||||
.sidebar-scroll.simplebar-scrolling .simplebar-track.simplebar-vertical {
|
||||
width: 12px !important;
|
||||
right: -13px;
|
||||
}
|
||||
|
||||
.sidebar-scroll
|
||||
.simplebar-track.simplebar-vertical
|
||||
.simplebar-scrollbar:before {
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.ant-color-picker
|
||||
.ant-color-picker-inner
|
||||
.ant-color-picker-panel
|
||||
.ant-color-picker-select
|
||||
.ant-color-picker-palette {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.ant-color-picker
|
||||
.ant-collapse
|
||||
.ant-collapse-item:last-child
|
||||
.ant-collapse-content-box {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.ant-color-picker .ant-collapse .ant-collapse-expand-icon {
|
||||
height: 12px !important;
|
||||
padding-inline-end: 8px !important;
|
||||
}
|
||||
|
||||
.ant-color-picker .ant-color-picker-inner .ant-color-picker-presets-label {
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
.ant-color-picker
|
||||
.ant-collapse
|
||||
.ant-collapse-item:first-child
|
||||
.ant-collapse-header {
|
||||
margin: 0 0 5.5px 0;
|
||||
}
|
||||
|
||||
.ant-color-picker .ant-collapse .ant-collapse-item .ant-collapse-header {
|
||||
margin: 5.5px 0;
|
||||
}
|
||||
|
||||
.subSteps.ant-timeline .ant-timeline-item-last > .ant-timeline-item-content {
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
.subSteps.ant-timeline .ant-timeline-item-last {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.subSteps.ant-timeline {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.ant-steps.ant-steps-vertical
|
||||
> .ant-steps-item
|
||||
> .ant-steps-item-container
|
||||
> .ant-steps-item-tail::after {
|
||||
width: 1.5px;
|
||||
}
|
||||
|
||||
.subSteps.ant-timeline .ant-timeline-item-head {
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
.subSteps.ant-timeline .subSteps-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.subSteps.ant-timeline .subSteps-dot svg path {
|
||||
stroke: var(--layout-modal-bg);
|
||||
stroke-width: 10px;
|
||||
}
|
||||
|
||||
.subSteps.ant-timeline .subSteps-dot span {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
margin-top: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.subSteps.ant-timeline .subSteps-dot.subSteps-dot-disabled {
|
||||
background-color: var(--color-primary-disabled);
|
||||
}
|
||||
|
||||
.subSteps.ant-timeline .subSteps-dot.subSteps-dot-disabled svg path {
|
||||
stroke: var(--color-primary);
|
||||
}
|
||||
|
||||
.ant-steps .ant-steps-item-finish .ant-steps-item-icon svg path {
|
||||
stroke: var(--color-primary);
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.ant-descriptions .ant-descriptions-item-label::after {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.ant-descriptions-small .ant-descriptions-row > td {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.minimal-code-block-editor .ant-typography pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ant-splitter-panel:not(:first-child) .info-collapse .info-collapse-label {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.farmcontrol-splitter .ant-splitter-panel {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.farmcontrol-splitter.ant-splitter-horizontal .ant-splitter-panel {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.farmcontrol-splitter
|
||||
.ant-splitter-panel:has(~ .ant-splitter-panel)
|
||||
.info-collapse.ant-collapse.ant-collapse-icon-position-end
|
||||
> .ant-collapse-item
|
||||
> .ant-collapse-header
|
||||
.ant-collapse-expand-icon {
|
||||
padding-inline-end: 2px;
|
||||
}
|
||||
|
||||
.objectTableCards {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.objectTableCardsContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.objectTableCardsContainer .ant-spin-nested-loading {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.objectTableCardsContainer .ant-spin-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
73
package.json
@ -9,6 +9,7 @@
|
||||
"private": true,
|
||||
"homepage": "./",
|
||||
"dependencies": {
|
||||
"@ant-design/charts": "^2.6.5",
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
|
||||
"@codemirror/lang-cpp": "^6.0.3",
|
||||
@ -25,11 +26,7 @@
|
||||
"@codemirror/lang-sql": "^6.9.1",
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/commands": "6.10.4",
|
||||
"@codemirror/language": "6.12.1",
|
||||
"@codemirror/state": "6.7.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "6.39.12",
|
||||
"@simplewebauthn/browser": "^13.1.2",
|
||||
"@tanstack/react-query": "^5.90.10",
|
||||
"@tiptap/extension-link": "^3.20.1",
|
||||
@ -42,23 +39,18 @@
|
||||
"@tsparticles/react": "^3.0.0",
|
||||
"@tsparticles/slim": "^3.9.1",
|
||||
"@uiw/react-codemirror": "^4.25.1",
|
||||
"@vscode/sudo-prompt": "^9.3.2",
|
||||
"antd": "^5.27.1",
|
||||
"antd-style": "^3.7.1",
|
||||
"axios": "^1.11.0",
|
||||
"blurhash": "^2.0.5",
|
||||
"codemirror": "^6.0.2",
|
||||
"country-list": "^2.4.1",
|
||||
"cross-env": "^10.0.0",
|
||||
"dayjs": "^1.11.18",
|
||||
"dotenv": "^17.2.1",
|
||||
"electron-store": "^11.0.2",
|
||||
"gcode-preview": "^2.18.0",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"keycloak-js": "^26.2.0",
|
||||
"keytar": "^7.9.0",
|
||||
"lodash": "^4.17.23",
|
||||
"loglevel": "^1.9.2",
|
||||
"nanoid": "^5.1.14",
|
||||
"online-3d-viewer": "^0.16.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^19.1.1",
|
||||
@ -67,7 +59,6 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-responsive": "^10.0.1",
|
||||
"react-router-dom": "^7.8.2",
|
||||
"recharts": "^3.8.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"simplebar-react": "^3.3.2",
|
||||
"socket.io-client": "*",
|
||||
@ -86,7 +77,7 @@
|
||||
"electron": "cross-env ELECTRON_START_URL=http://0.0.0.0:5780 && cross-env NODE_ENV=development && electron .",
|
||||
"start": "serve -s build",
|
||||
"build": "vite build",
|
||||
"dev:electron": "concurrently \"cross-env NODE_ENV=development vite --port 5780 --no-open\" \"cross-env ELECTRON_START_URL=http://localhost:5780 NODE_ENV=development electron public/electron.js\"",
|
||||
"dev:electron": "concurrently \"cross-env NODE_ENV=development vite --no-open\" \"cross-env ELECTRON_START_URL=http://localhost:5780 cross-env NODE_ENV=development electron public/electron.js\"",
|
||||
"build:electron": "vite build && electron-builder",
|
||||
"build:cloudflare": "cross-env VITE_DEPLOY_TARGET=cloudflare vite build",
|
||||
"deploy": "npm run build:cloudflare && wrangler pages deploy --branch main"
|
||||
@ -140,7 +131,6 @@
|
||||
"build": {
|
||||
"appId": "com.tombutcher.farmcontrol",
|
||||
"productName": "Farm Control",
|
||||
"artifactName": "farmcontrol-${version}-${arch}.${ext}",
|
||||
"icon": "assets/logos/farmcontrolicon.png",
|
||||
"directories": {
|
||||
"output": "app_dist"
|
||||
@ -157,23 +147,11 @@
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "pkg",
|
||||
"arch": [
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "pkg",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"mergeASARs": true,
|
||||
@ -190,45 +168,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"dmg": {
|
||||
"background": "assets/dmg/background.png",
|
||||
"iconSize": 100,
|
||||
"window": {
|
||||
"width": 540,
|
||||
"height": 380
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pkg": {
|
||||
"installLocation": "/Applications",
|
||||
"mustClose": [
|
||||
"com.tombutcher.farmcontrol"
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"msiWrapped"
|
||||
],
|
||||
"protocols": [
|
||||
{
|
||||
"name": "Farm Control Protocol",
|
||||
"schemes": [
|
||||
"farmcontrol"
|
||||
]
|
||||
}
|
||||
]
|
||||
"target": "nsis"
|
||||
},
|
||||
"linux": {
|
||||
"target": "AppImage"
|
||||
@ -238,12 +179,6 @@
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"include": "scripts/installer.nsh",
|
||||
"perMachine": true
|
||||
},
|
||||
"msiWrapped": {
|
||||
"upgradeCode": "{735812DB-E33B-57A0-8FBC-5FC3155925AA}",
|
||||
"perMachine": true,
|
||||
"impersonate": false,
|
||||
"wrappedInstallerArgs": "/S"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1646
pnpm-lock.yaml
generated
@ -2,15 +2,8 @@ allowBuilds:
|
||||
electron: true
|
||||
esbuild: true
|
||||
sharp: true
|
||||
keytar: true
|
||||
workerd: true
|
||||
core-js: true
|
||||
electron-winstaller: true
|
||||
'@tsparticles/engine': true
|
||||
|
||||
overrides:
|
||||
baseline-browser-mapping: 2.11.4
|
||||
'@codemirror/state': 6.7.1
|
||||
'@codemirror/view': 6.39.12
|
||||
'@codemirror/language': 6.12.1
|
||||
'@codemirror/commands': 6.10.4
|
||||
'@codemirror/autocomplete': 6.20.0
|
||||
@ -1,270 +0,0 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { createWriteStream, promises as fs } from 'fs'
|
||||
import http from 'http'
|
||||
import https from 'https'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import process from 'process'
|
||||
import { launchMacInstaller } from './macappupdate.js'
|
||||
import { launchWindowsInstaller } from './winappupdate.js'
|
||||
|
||||
const UPDATE_PROGRESS_CHANNEL = 'app-update-progress'
|
||||
const SUPPORTED_TARGETS = {
|
||||
darwin: {
|
||||
extension: '.pkg',
|
||||
osMatchers: ['darwin', 'mac', 'macos', 'osx']
|
||||
},
|
||||
win32: {
|
||||
extension: '.msi',
|
||||
osMatchers: ['win32', 'win', 'windows']
|
||||
}
|
||||
}
|
||||
|
||||
let runningUpdate = null
|
||||
|
||||
const getArtifactName = (artifact) =>
|
||||
String(artifact?.fileName || artifact?.relativePath || artifact?.url || '')
|
||||
|
||||
const isCefInstallerArtifact = (artifact) => {
|
||||
const name = getArtifactName(artifact).toLowerCase()
|
||||
// CEF builds are named like farmcontrol-0.1.1-x64-cef.msi / -cef.pkg / -cef.exe
|
||||
return /-cef\.[a-z0-9]+$/.test(name)
|
||||
}
|
||||
|
||||
const normalizeArch = (arch) => {
|
||||
if (arch === 'x64' || arch === 'amd64') return 'x64'
|
||||
if (arch === 'arm64' || arch === 'aarch64') return 'arm64'
|
||||
return arch
|
||||
}
|
||||
|
||||
const artifactMatchesPlatform = (artifact, target, platform, arch) => {
|
||||
const name = getArtifactName(artifact).toLowerCase()
|
||||
const normalizedArch = normalizeArch(arch)
|
||||
const artifactArch = normalizeArch(String(artifact?.arch || '').toLowerCase())
|
||||
const artifactPlatform = String(
|
||||
artifact?.platform || artifact?.os || artifact?.target || ''
|
||||
).toLowerCase()
|
||||
|
||||
if (!name.endsWith(target.extension)) return false
|
||||
if (!artifact?.url) return false
|
||||
|
||||
const matchesArch =
|
||||
artifactArch === normalizedArch ||
|
||||
name.includes(`-${normalizedArch}`) ||
|
||||
name.includes(`_${normalizedArch}`) ||
|
||||
name.includes(`.${normalizedArch}.`) ||
|
||||
name.includes(normalizedArch)
|
||||
|
||||
const matchesOs =
|
||||
!artifactPlatform ||
|
||||
target.osMatchers.includes(artifactPlatform) ||
|
||||
target.osMatchers.some((matcher) => name.includes(matcher)) ||
|
||||
(platform === 'darwin' && name.includes('mac')) ||
|
||||
(platform === 'win32' && name.includes('win'))
|
||||
|
||||
return matchesArch && matchesOs
|
||||
}
|
||||
|
||||
const selectUpdateArtifact = (
|
||||
update,
|
||||
platform = process.platform,
|
||||
arch = process.arch
|
||||
) => {
|
||||
const target = SUPPORTED_TARGETS[platform]
|
||||
if (!target) {
|
||||
throw new Error(`App updates are not supported on ${platform}.`)
|
||||
}
|
||||
|
||||
const artifacts = (Array.isArray(update?.artifacts) ? update.artifacts : []).filter(
|
||||
(artifact) => !isCefInstallerArtifact(artifact)
|
||||
)
|
||||
const matchingArtifact = artifacts.find((artifact) =>
|
||||
artifactMatchesPlatform(artifact, target, platform, arch)
|
||||
)
|
||||
const fallbackArtifact = artifacts.find((artifact) => {
|
||||
const name = getArtifactName(artifact).toLowerCase()
|
||||
return artifact?.url && name.endsWith(target.extension)
|
||||
})
|
||||
|
||||
if (!matchingArtifact && !fallbackArtifact) {
|
||||
throw new Error(
|
||||
`No ${target.extension} update artifact found for ${platform}/${arch}.`
|
||||
)
|
||||
}
|
||||
|
||||
return matchingArtifact || fallbackArtifact
|
||||
}
|
||||
|
||||
const sendProgress = (webContents, payload) => {
|
||||
if (!webContents || webContents.isDestroyed()) return
|
||||
webContents.send(UPDATE_PROGRESS_CHANNEL, {
|
||||
timestamp: new Date().toISOString(),
|
||||
...payload
|
||||
})
|
||||
}
|
||||
|
||||
const getInstallErrorMessage = (error, output = '') => {
|
||||
const combined = `${output}\n${error?.message || ''}`.trim()
|
||||
|
||||
if (
|
||||
/cancel/i.test(combined) ||
|
||||
/did not grant permission/i.test(combined) ||
|
||||
/user canceled/i.test(combined)
|
||||
) {
|
||||
return 'Update installation was cancelled.'
|
||||
}
|
||||
|
||||
if (/incorrect/i.test(combined)) {
|
||||
return 'The administrator password was incorrect.'
|
||||
}
|
||||
|
||||
return combined || 'Failed to install update.'
|
||||
}
|
||||
|
||||
const installerHelpers = { sendProgress, getInstallErrorMessage }
|
||||
|
||||
const getDownloadUrl = (url, redirectCount = 0) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (redirectCount > 5) {
|
||||
reject(new Error('Too many redirects while downloading update.'))
|
||||
return
|
||||
}
|
||||
|
||||
const parsedUrl = new URL(url)
|
||||
const client = parsedUrl.protocol === 'https:' ? https : http
|
||||
const request = client.get(parsedUrl, (response) => {
|
||||
const location = response.headers.location
|
||||
|
||||
if (response.statusCode >= 300 && response.statusCode < 400 && location) {
|
||||
response.resume()
|
||||
resolve(
|
||||
getDownloadUrl(
|
||||
new URL(location, parsedUrl).toString(),
|
||||
redirectCount + 1
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
resolve({ response, url: parsedUrl.toString() })
|
||||
})
|
||||
|
||||
request.on('error', reject)
|
||||
})
|
||||
|
||||
const downloadArtifact = async (artifact, destinationPath, webContents) => {
|
||||
const { response } = await getDownloadUrl(artifact.url)
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
response.resume()
|
||||
throw new Error(`Update download failed with HTTP ${response.statusCode}.`)
|
||||
}
|
||||
|
||||
const totalBytes =
|
||||
Number.parseInt(response.headers['content-length'], 10) || 0
|
||||
let downloadedBytes = 0
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const output = createWriteStream(destinationPath)
|
||||
|
||||
response.on('data', (chunk) => {
|
||||
downloadedBytes += chunk.length
|
||||
const percent = totalBytes
|
||||
? Math.round((downloadedBytes / totalBytes) * 100)
|
||||
: null
|
||||
|
||||
sendProgress(webContents, {
|
||||
phase: 'downloading',
|
||||
percent,
|
||||
downloadedBytes,
|
||||
totalBytes,
|
||||
message: totalBytes
|
||||
? `Downloading update (${percent}%)`
|
||||
: 'Downloading update'
|
||||
})
|
||||
})
|
||||
|
||||
response.on('error', reject)
|
||||
output.on('error', reject)
|
||||
output.on('finish', resolve)
|
||||
response.pipe(output)
|
||||
})
|
||||
}
|
||||
|
||||
const restartApp = (app) => {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
}
|
||||
|
||||
const launchInstallerAndQuit = async (app, installerPath, webContents) => {
|
||||
if (process.platform === 'darwin') {
|
||||
await launchMacInstaller(app, installerPath, webContents, installerHelpers)
|
||||
restartApp(app)
|
||||
return
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
await launchWindowsInstaller(
|
||||
app,
|
||||
installerPath,
|
||||
webContents,
|
||||
installerHelpers
|
||||
)
|
||||
restartApp(app)
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error(`App updates are not supported on ${process.platform}.`)
|
||||
}
|
||||
|
||||
const runAppUpdate = async (app, update, webContents) => {
|
||||
const artifact = selectUpdateArtifact(update)
|
||||
const tempDirectory = await fs.mkdtemp(
|
||||
path.join(os.tmpdir(), 'farmcontrol-update-')
|
||||
)
|
||||
const artifactName = path.basename(getArtifactName(artifact))
|
||||
const installerPath = path.join(tempDirectory, artifactName)
|
||||
|
||||
sendProgress(webContents, {
|
||||
phase: 'preparing',
|
||||
percent: 0,
|
||||
artifact,
|
||||
message: 'Preparing update download'
|
||||
})
|
||||
|
||||
await downloadArtifact(artifact, installerPath, webContents)
|
||||
|
||||
sendProgress(webContents, {
|
||||
phase: 'downloaded',
|
||||
percent: 100,
|
||||
downloadedBytes: null,
|
||||
totalBytes: null,
|
||||
artifact,
|
||||
message: 'Update downloaded'
|
||||
})
|
||||
|
||||
await launchInstallerAndQuit(app, installerPath, webContents)
|
||||
}
|
||||
|
||||
export function setupAppUpdateIPC(app) {
|
||||
ipcMain.handle('app-update-start', async (event, update) => {
|
||||
if (runningUpdate) return runningUpdate
|
||||
|
||||
const webContents = event.sender
|
||||
runningUpdate = runAppUpdate(app, update, webContents)
|
||||
.then(() => ({ ok: true }))
|
||||
.catch((error) => {
|
||||
sendProgress(webContents, {
|
||||
phase: 'error',
|
||||
percent: null,
|
||||
message: error?.message || 'Failed to update app.'
|
||||
})
|
||||
throw error
|
||||
})
|
||||
.finally(() => {
|
||||
runningUpdate = null
|
||||
})
|
||||
|
||||
return runningUpdate
|
||||
})
|
||||
}
|
||||
@ -1,7 +1,5 @@
|
||||
import { app, ipcMain, shell, globalShortcut, safeStorage } from 'electron'
|
||||
import Store from 'electron-store'
|
||||
import { Buffer } from 'buffer'
|
||||
import process from 'process'
|
||||
import { app, ipcMain, shell, globalShortcut } from 'electron'
|
||||
import { createRequire } from 'module'
|
||||
import {
|
||||
registerGlobalShortcuts,
|
||||
setupSpotlightIPC
|
||||
@ -14,66 +12,22 @@ import {
|
||||
setupSingleInstanceLock,
|
||||
handleDeepLinkFromArgv
|
||||
} from './mainWindow.js'
|
||||
import { setupAppUpdateIPC } from './appupdate.js'
|
||||
|
||||
// --- Auth session storage (main process) ---
|
||||
const authStore = new Store({
|
||||
name: 'auth-session'
|
||||
})
|
||||
const AUTH_SESSION_KEY = 'authSession'
|
||||
const appSettingsStore = new Store({
|
||||
name: 'settings'
|
||||
})
|
||||
const APP_SETTINGS_KEY = 'appSettings'
|
||||
|
||||
const serializeAuthSession = (session) => {
|
||||
const sessionJson = JSON.stringify(session)
|
||||
|
||||
if (safeStorage.isEncryptionAvailable()) {
|
||||
const encrypted = safeStorage.encryptString(sessionJson).toString('base64')
|
||||
return {
|
||||
encrypted: true,
|
||||
value: encrypted
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
encrypted: false,
|
||||
value: sessionJson
|
||||
}
|
||||
// --- Keytar-backed auth session storage (main process) ---
|
||||
const require = createRequire(import.meta.url)
|
||||
let keytar = null
|
||||
try {
|
||||
// keytar is a native module; in some dev environments it may not be built yet.
|
||||
keytar = require('keytar')
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
'[keytar] Not available; auth session persistence will be disabled.',
|
||||
e?.message || e
|
||||
)
|
||||
}
|
||||
|
||||
const deserializeAuthSession = (storedValue) => {
|
||||
if (!storedValue) return null
|
||||
|
||||
if (typeof storedValue === 'object' && storedValue.encrypted === true) {
|
||||
if (!safeStorage.isEncryptionAvailable()) {
|
||||
console.warn(
|
||||
'[auth-session] Encrypted auth session exists but encryption is unavailable on this system.'
|
||||
)
|
||||
return null
|
||||
}
|
||||
const decrypted = safeStorage.decryptString(
|
||||
Buffer.from(storedValue.value, 'base64')
|
||||
)
|
||||
return JSON.parse(decrypted)
|
||||
}
|
||||
|
||||
if (typeof storedValue === 'object' && typeof storedValue.value === 'string') {
|
||||
return JSON.parse(storedValue.value)
|
||||
}
|
||||
|
||||
if (typeof storedValue === 'string') {
|
||||
return JSON.parse(storedValue)
|
||||
}
|
||||
|
||||
// Legacy safety net if the object shape already matches the session structure.
|
||||
if (typeof storedValue === 'object' && storedValue.token) {
|
||||
return storedValue
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
const KEYTAR_SERVICE = app.name || 'Farm Control'
|
||||
const KEYTAR_ACCOUNT = 'authSession'
|
||||
|
||||
const gotTheLock = setupSingleInstanceLock(app)
|
||||
|
||||
@ -83,7 +37,6 @@ if (gotTheLock) {
|
||||
registerGlobalShortcuts()
|
||||
setupSpotlightIPC()
|
||||
setupMainWindowIPC()
|
||||
setupAppUpdateIPC(app)
|
||||
setupMainWindowAppEvents(app)
|
||||
setupDevAuthServer()
|
||||
handleDeepLinkFromArgv()
|
||||
@ -103,52 +56,38 @@ ipcMain.handle('os-info', () => {
|
||||
|
||||
ipcMain.handle('auth-session-get', async () => {
|
||||
try {
|
||||
const storedValue = authStore.get(AUTH_SESSION_KEY)
|
||||
return deserializeAuthSession(storedValue)
|
||||
if (!keytar) return null
|
||||
const raw = await keytar.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT)
|
||||
if (!raw) return null
|
||||
return JSON.parse(raw)
|
||||
} catch (e) {
|
||||
console.warn('[auth-session] Failed to read auth session.', e?.message || e)
|
||||
console.warn('[keytar] Failed to read auth session.', e?.message || e)
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('auth-session-set', async (event, session) => {
|
||||
try {
|
||||
if (!keytar) return false
|
||||
if (!session || typeof session !== 'object') return false
|
||||
authStore.set(AUTH_SESSION_KEY, serializeAuthSession(session))
|
||||
await keytar.setPassword(
|
||||
KEYTAR_SERVICE,
|
||||
KEYTAR_ACCOUNT,
|
||||
JSON.stringify(session)
|
||||
)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.warn('[auth-session] Failed to write auth session.', e?.message || e)
|
||||
console.warn('[keytar] Failed to write auth session.', e?.message || e)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('auth-session-clear', async () => {
|
||||
try {
|
||||
authStore.delete(AUTH_SESSION_KEY)
|
||||
return true
|
||||
if (!keytar) return false
|
||||
return await keytar.deletePassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT)
|
||||
} catch (e) {
|
||||
console.warn('[auth-session] Failed to clear auth session.', e?.message || e)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('app-settings-get', async () => {
|
||||
try {
|
||||
const settings = appSettingsStore.get(APP_SETTINGS_KEY)
|
||||
return settings && typeof settings === 'object' ? settings : {}
|
||||
} catch (e) {
|
||||
console.warn('[app-settings] Failed to read settings.', e?.message || e)
|
||||
return {}
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('app-settings-set', async (event, settings) => {
|
||||
try {
|
||||
if (!settings || typeof settings !== 'object') return false
|
||||
appSettingsStore.set(APP_SETTINGS_KEY, settings)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.warn('[app-settings] Failed to write settings.', e?.message || e)
|
||||
console.warn('[keytar] Failed to clear auth session.', e?.message || e)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,172 +0,0 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import { createRequire } from 'module'
|
||||
import path from 'path'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const sudo = require('@vscode/sudo-prompt')
|
||||
|
||||
const quoteShellArg = (value) => `'${String(value).replaceAll("'", "'\\''")}'`
|
||||
|
||||
const parseMacInstallerProgress = (output) => {
|
||||
const lines = String(output || '').split('\n')
|
||||
let percent = null
|
||||
let message = 'Installing update...'
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('installer:PHASE:')) {
|
||||
message = line.slice('installer:PHASE:'.length).trim() || message
|
||||
} else if (line.startsWith('installer:STATUS:')) {
|
||||
const status = line.slice('installer:STATUS:'.length).trim()
|
||||
if (status) message = status
|
||||
} else if (line.startsWith('installer:%')) {
|
||||
const value = Number.parseFloat(line.slice('installer:%'.length))
|
||||
if (Number.isFinite(value)) {
|
||||
percent = Math.min(100, Math.round(value <= 1 ? value * 100 : value))
|
||||
}
|
||||
} else if (
|
||||
line.startsWith('installer: ') &&
|
||||
!line.startsWith('installer:PHASE:') &&
|
||||
!line.startsWith('installer:STATUS:') &&
|
||||
!line.startsWith('installer:%')
|
||||
) {
|
||||
const text = line.slice('installer: '.length).trim()
|
||||
if (text) message = text
|
||||
}
|
||||
}
|
||||
|
||||
return { percent, message }
|
||||
}
|
||||
|
||||
const isMacInstallSuccessful = (output) =>
|
||||
/installer: The (install|upgrade) was successful\./i.test(output)
|
||||
|
||||
const isMacInstallFailed = (output) =>
|
||||
/installer: The install failed/i.test(output)
|
||||
|
||||
const buildMacInstallScript = (installerPath, logPath) =>
|
||||
`sleep 2 && /usr/sbin/installer -pkg ${quoteShellArg(
|
||||
installerPath
|
||||
)} -target / -verboseR 2>&1 | /usr/bin/tee ${quoteShellArg(logPath)}`
|
||||
|
||||
const startMacInstallerProgressWatch = (logPath, webContents, sendProgress) => {
|
||||
let installerOutput = ''
|
||||
let offset = 0
|
||||
let lastPercent = null
|
||||
let lastMessage = null
|
||||
|
||||
const poll = async () => {
|
||||
try {
|
||||
const stat = await fs.stat(logPath)
|
||||
if (stat.size <= offset) return
|
||||
|
||||
const handle = await fs.open(logPath, 'r')
|
||||
try {
|
||||
const buffer = Buffer.alloc(stat.size - offset)
|
||||
await handle.read(buffer, 0, buffer.length, offset)
|
||||
offset = stat.size
|
||||
installerOutput += buffer.toString('utf8')
|
||||
|
||||
const { percent, message } = parseMacInstallerProgress(installerOutput)
|
||||
const resolvedMessage = message || 'Installing update...'
|
||||
|
||||
if (percent !== lastPercent || resolvedMessage !== lastMessage) {
|
||||
lastPercent = percent
|
||||
lastMessage = resolvedMessage
|
||||
sendProgress(webContents, {
|
||||
phase: 'installing',
|
||||
percent,
|
||||
message: resolvedMessage
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
await handle.close()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.code !== 'ENOENT') {
|
||||
console.error('[app-update] installer log poll error:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
poll().catch((error) => {
|
||||
console.error('[app-update] installer log poll error:', error)
|
||||
})
|
||||
}, 300)
|
||||
|
||||
return async () => {
|
||||
clearInterval(intervalId)
|
||||
await poll()
|
||||
return installerOutput
|
||||
}
|
||||
}
|
||||
|
||||
export const launchMacInstaller = (
|
||||
app,
|
||||
installerPath,
|
||||
webContents,
|
||||
{ sendProgress, getInstallErrorMessage }
|
||||
) => {
|
||||
const logPath = path.join(path.dirname(installerPath), 'install.log')
|
||||
const installScript = buildMacInstallScript(installerPath, logPath)
|
||||
const promptName = 'farmcontrol'
|
||||
|
||||
sendProgress(webContents, {
|
||||
phase: 'installing',
|
||||
percent: 0,
|
||||
message: 'Enter your Mac password when prompted.'
|
||||
})
|
||||
|
||||
app.focus({ steal: true })
|
||||
app.dock?.show()
|
||||
|
||||
const stopProgressWatch = startMacInstallerProgressWatch(
|
||||
logPath,
|
||||
webContents,
|
||||
sendProgress
|
||||
)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
sudo.exec(installScript, { name: promptName }, async (error, stdout, stderr) => {
|
||||
const watchedOutput = await stopProgressWatch()
|
||||
const output = `${stdout || ''}${stderr || ''}` || watchedOutput
|
||||
|
||||
await fs.unlink(logPath).catch(() => {})
|
||||
|
||||
if (stderr) console.error('[app-update] installer stderr:', stderr)
|
||||
|
||||
if (error) {
|
||||
console.error('[app-update] installer error:', error)
|
||||
const message = getInstallErrorMessage(error, output)
|
||||
sendProgress(webContents, {
|
||||
phase: 'error',
|
||||
percent: null,
|
||||
message
|
||||
})
|
||||
reject(new Error(message))
|
||||
return
|
||||
}
|
||||
|
||||
if (isMacInstallFailed(output) || !isMacInstallSuccessful(output)) {
|
||||
const message = getInstallErrorMessage(null, output)
|
||||
sendProgress(webContents, {
|
||||
phase: 'error',
|
||||
percent: null,
|
||||
message
|
||||
})
|
||||
reject(new Error(message))
|
||||
return
|
||||
}
|
||||
|
||||
const { percent, message } = parseMacInstallerProgress(output)
|
||||
|
||||
sendProgress(webContents, {
|
||||
phase: 'installing',
|
||||
percent: percent ?? 100,
|
||||
message: message || 'Installation complete. Restarting Farm Control...'
|
||||
})
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -6,7 +6,6 @@ const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
let win
|
||||
let sidebarViewMenuSections = []
|
||||
|
||||
const PROTOCOL_PREFIX = 'farmcontrol://'
|
||||
|
||||
@ -41,76 +40,6 @@ function sendNavigateToRenderer(redirectPath) {
|
||||
deliver()
|
||||
}
|
||||
|
||||
function toElectronSidebarMenuItems(items = []) {
|
||||
return items
|
||||
.map((item) => {
|
||||
if (item?.type === 'divider') {
|
||||
return { type: 'separator' }
|
||||
}
|
||||
|
||||
const menuItem = {
|
||||
label: item.label
|
||||
}
|
||||
|
||||
if (item?.children && Array.isArray(item.children) && item.children.length) {
|
||||
menuItem.submenu = toElectronSidebarMenuItems(item.children)
|
||||
} else if (item?.path) {
|
||||
menuItem.click = () => sendNavigateToRenderer(item.path)
|
||||
} else {
|
||||
menuItem.enabled = false
|
||||
}
|
||||
|
||||
return menuItem
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function buildApplicationMenuTemplate() {
|
||||
const env = (process.env.NODE_ENV || 'development').trim()
|
||||
const viewSubmenu = sidebarViewMenuSections.map((section) => ({
|
||||
label: section.label,
|
||||
submenu: toElectronSidebarMenuItems(section.items || [])
|
||||
}))
|
||||
|
||||
if (viewSubmenu.length === 0) {
|
||||
viewSubmenu.push({ label: 'No sidebar items available', enabled: false })
|
||||
}
|
||||
|
||||
if (env === 'development') {
|
||||
viewSubmenu.push(
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator:
|
||||
process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
click: () => {
|
||||
if (win && !win.isDestroyed()) {
|
||||
win.webContents.toggleDevTools()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const template = [
|
||||
{ role: 'fileMenu' },
|
||||
{ role: 'editMenu' },
|
||||
{ label: 'View', submenu: viewSubmenu },
|
||||
{ role: 'windowMenu' }
|
||||
]
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
template.unshift({ role: 'appMenu' })
|
||||
}
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
function applyApplicationMenu() {
|
||||
const menu = Menu.buildFromTemplate(buildApplicationMenuTemplate())
|
||||
Menu.setApplicationMenu(menu)
|
||||
}
|
||||
|
||||
export function handleDeepLink(url) {
|
||||
if (!url?.startsWith(`${PROTOCOL_PREFIX}app`)) return
|
||||
const redirectPath = url.replace(`${PROTOCOL_PREFIX}app`, '') || '/'
|
||||
@ -149,33 +78,6 @@ function attachKeyboardShortcuts(browserWindow) {
|
||||
// Keyboard shortcuts for the main window can be added here if needed
|
||||
}
|
||||
|
||||
function sendNavigationGesture(browserWindow, direction) {
|
||||
if (!browserWindow || browserWindow.isDestroyed()) return
|
||||
browserWindow.webContents.send('navigation-gesture', direction)
|
||||
}
|
||||
|
||||
function setupNavigationGestures(browserWindow) {
|
||||
if (!browserWindow) return
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
browserWindow.on('swipe', (_event, direction) => {
|
||||
if (direction === 'left') {
|
||||
sendNavigationGesture(browserWindow, 'back')
|
||||
} else if (direction === 'right') {
|
||||
sendNavigationGesture(browserWindow, 'forward')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
browserWindow.on('app-command', (_event, command) => {
|
||||
if (command === 'browser-backward') {
|
||||
sendNavigationGesture(browserWindow, 'back')
|
||||
} else if (command === 'browser-forward') {
|
||||
sendNavigationGesture(browserWindow, 'forward')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setupWindowEvents() {
|
||||
if (!win) return
|
||||
win.on('maximize', () => {
|
||||
@ -184,6 +86,7 @@ function setupWindowEvents() {
|
||||
})
|
||||
})
|
||||
win.on('enter-full-screen', () => {
|
||||
console.log('Entered fullscreen')
|
||||
win.webContents.send('window-state', {
|
||||
isFullScreen: true
|
||||
})
|
||||
@ -215,7 +118,29 @@ export function createWindow() {
|
||||
}
|
||||
})
|
||||
|
||||
applyApplicationMenu()
|
||||
// Set up custom menu bar
|
||||
const env = (process.env.NODE_ENV || 'development').trim()
|
||||
if (env === 'development') {
|
||||
const devMenu = [
|
||||
{
|
||||
label: 'Developer',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator:
|
||||
process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
click: () => {
|
||||
win.webContents.toggleDevTools()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
const menu = Menu.buildFromTemplate(devMenu)
|
||||
Menu.setApplicationMenu(menu)
|
||||
} else {
|
||||
Menu.setApplicationMenu(null)
|
||||
}
|
||||
|
||||
// For development, load from localhost; for production, load the built index.html
|
||||
if (process.env.ELECTRON_START_URL) {
|
||||
@ -226,17 +151,12 @@ export function createWindow() {
|
||||
|
||||
setupWindowEvents()
|
||||
attachKeyboardShortcuts(win)
|
||||
setupNavigationGestures(win)
|
||||
}
|
||||
|
||||
export function getWindow() {
|
||||
return win
|
||||
}
|
||||
|
||||
export function getElectronVersion() {
|
||||
return process.versions.electron
|
||||
}
|
||||
|
||||
export function setupMainWindowIPC() {
|
||||
// IPC handler to get window state
|
||||
ipcMain.handle('window-state', () => {
|
||||
@ -288,18 +208,6 @@ export function setupMainWindowIPC() {
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
ipcMain.handle('set-sidebar-view-menu', (event, sidebarSections) => {
|
||||
if (!Array.isArray(sidebarSections)) {
|
||||
return false
|
||||
}
|
||||
|
||||
sidebarViewMenuSections = sidebarSections
|
||||
applyApplicationMenu()
|
||||
return true
|
||||
})
|
||||
|
||||
ipcMain.handle('electron-version', () => getElectronVersion())
|
||||
}
|
||||
|
||||
export function setupMainWindowAppEvents(app) {
|
||||
@ -320,6 +228,7 @@ export function setupMainWindowAppEvents(app) {
|
||||
export function setupDevAuthServer() {
|
||||
const env = (process.env.NODE_ENV || 'development').trim()
|
||||
if (env == 'development') {
|
||||
console.log('Starting development auth web server...')
|
||||
import('express').then(({ default: express }) => {
|
||||
const app = express()
|
||||
const port = 3500
|
||||
@ -336,7 +245,11 @@ export function setupDevAuthServer() {
|
||||
}
|
||||
})
|
||||
|
||||
app.listen(port, () => {})
|
||||
app.listen(port, () => {
|
||||
console.log(`Dev auth server running on http://localhost:${port}`)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
console.log('Will use url scheme instead of auth server.')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,442 +0,0 @@
|
||||
import { spawn } from 'child_process'
|
||||
import { promises as fs } from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import process from 'process'
|
||||
|
||||
const MSI_OLE_HEADER = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1])
|
||||
const DEBUG_PREFIX = '[app-update][win-progress]'
|
||||
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const debugLog = () => {}
|
||||
|
||||
const decodeMsiLogBuffer = (buffer) => {
|
||||
if (!buffer?.length) return ''
|
||||
|
||||
if (buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe) {
|
||||
debugLog('decoded MSI log as UTF-16 LE (BOM)')
|
||||
return buffer.subarray(2).toString('utf16le')
|
||||
}
|
||||
|
||||
const sample = buffer.subarray(0, Math.min(buffer.length, 64))
|
||||
const looksUtf16 =
|
||||
sample.length >= 4 &&
|
||||
sample.filter((byte) => byte === 0).length > sample.length / 4
|
||||
|
||||
if (looksUtf16) {
|
||||
debugLog('decoded MSI log as UTF-16 LE (heuristic)')
|
||||
return buffer.toString('utf16le')
|
||||
}
|
||||
|
||||
debugLog('decoded MSI log as UTF-8')
|
||||
return buffer.toString('utf8')
|
||||
}
|
||||
|
||||
const formatMsiActionName = (actionName) => {
|
||||
const humanized = String(actionName)
|
||||
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
.replace(/_/g, ' ')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
|
||||
if (!humanized) return 'Installing update...'
|
||||
|
||||
return `${humanized.charAt(0).toUpperCase()}${humanized.slice(1)}...`
|
||||
}
|
||||
|
||||
const parseWindowsInstallerProgress = (output) => {
|
||||
const lines = String(output || '').split(/\r?\n/)
|
||||
let percent = null
|
||||
let message = 'Installing update...'
|
||||
let totalTicks = 0
|
||||
let currentTicks = 0
|
||||
let actionStarts = 0
|
||||
let actionEnds = 0
|
||||
const matchedLines = []
|
||||
|
||||
for (const line of lines) {
|
||||
const actionStart = line.match(/^Action start \d{2}:\d{2}:\d{2}: (.+?)\./)
|
||||
if (actionStart) {
|
||||
actionStarts += 1
|
||||
message = formatMsiActionName(actionStart[1])
|
||||
matchedLines.push(`action-start:${actionStart[1]}`)
|
||||
}
|
||||
|
||||
const doingAction = line.match(/Doing action:\s*(.+)$/)
|
||||
if (doingAction && !actionStart) {
|
||||
message = formatMsiActionName(doingAction[1])
|
||||
matchedLines.push(`doing-action:${doingAction[1]}`)
|
||||
}
|
||||
|
||||
if (/^Action ended \d{2}:\d{2}:\d{2}: .+?\. Return value \d+\./.test(line)) {
|
||||
actionEnds += 1
|
||||
matchedLines.push('action-ended')
|
||||
}
|
||||
|
||||
const progressReset = line.match(/^\s*0\s+(\d+)\s+0(?:\s+\d+)?\s*$/)
|
||||
if (progressReset) {
|
||||
totalTicks = Number.parseInt(progressReset[1], 10) || 0
|
||||
currentTicks = 0
|
||||
matchedLines.push(`progress-reset:${totalTicks}`)
|
||||
}
|
||||
|
||||
const progressIncrement = line.match(/^\s*2\s+(\d+)\s*$/)
|
||||
if (progressIncrement) {
|
||||
currentTicks += Number.parseInt(progressIncrement[1], 10) || 0
|
||||
matchedLines.push(`progress-increment:${progressIncrement[1]}`)
|
||||
}
|
||||
|
||||
const progressAddition = line.match(/^\s*3\s+(\d+)\s*$/)
|
||||
if (progressAddition) {
|
||||
totalTicks += Number.parseInt(progressAddition[1], 10) || 0
|
||||
matchedLines.push(`progress-addition:${progressAddition[1]}`)
|
||||
}
|
||||
|
||||
if (/Installation success or error status:\s*0\b/.test(line)) {
|
||||
percent = 100
|
||||
message = 'Installation complete. Restarting Farm Control...'
|
||||
matchedLines.push('install-success')
|
||||
}
|
||||
}
|
||||
|
||||
if (percent !== 100) {
|
||||
if (totalTicks > 0) {
|
||||
percent = Math.min(99, Math.round((currentTicks / totalTicks) * 100))
|
||||
} else if (actionStarts > 0) {
|
||||
percent = Math.min(
|
||||
95,
|
||||
Math.max(5, Math.round((actionEnds / actionStarts) * 90))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
percent,
|
||||
message,
|
||||
stats: {
|
||||
lineCount: lines.length,
|
||||
actionStarts,
|
||||
actionEnds,
|
||||
totalTicks,
|
||||
currentTicks,
|
||||
matchedLines: matchedLines.slice(-8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isWindowsInstallSuccessful = (output) =>
|
||||
/Installation success or error status:\s*0\b/.test(output) ||
|
||||
/MainEngineThread is returning 0\b/.test(output)
|
||||
|
||||
const isWindowsInstallFailed = (output) =>
|
||||
/Installation success or error status:\s*[1-9]\d*\b/.test(output) ||
|
||||
/MainEngineThread is returning [1-9]\d*\b/.test(output)
|
||||
|
||||
const isValidMsiPackage = async (filePath) => {
|
||||
const handle = await fs.open(filePath, 'r')
|
||||
try {
|
||||
const header = Buffer.alloc(MSI_OLE_HEADER.length)
|
||||
await handle.read(header, 0, header.length, 0)
|
||||
return header.equals(MSI_OLE_HEADER)
|
||||
} finally {
|
||||
await handle.close()
|
||||
}
|
||||
}
|
||||
|
||||
const prepareInstallerPath = async (installerPath) => {
|
||||
const fileName = path.basename(installerPath)
|
||||
const updateDir = path.join(
|
||||
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
|
||||
'FarmControl',
|
||||
'Updates'
|
||||
)
|
||||
await fs.mkdir(updateDir, { recursive: true })
|
||||
|
||||
const stablePath = path.join(updateDir, fileName)
|
||||
await fs.copyFile(installerPath, stablePath)
|
||||
|
||||
// Resolve to a canonical long path. Short 8.3 paths (e.g. ADMINI~1) break msiexec.
|
||||
const resolvedPath = await fs.realpath(stablePath)
|
||||
const stats = await fs.stat(resolvedPath)
|
||||
|
||||
if (!stats.isFile() || stats.size === 0) {
|
||||
throw new Error('Update installer file is missing or empty.')
|
||||
}
|
||||
|
||||
if (!(await isValidMsiPackage(resolvedPath))) {
|
||||
throw new Error(
|
||||
'Downloaded update is not a valid Windows Installer package. The file may be corrupted or incomplete.'
|
||||
)
|
||||
}
|
||||
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
const startWindowsInstallerProgressWatch = (
|
||||
logPath,
|
||||
webContents,
|
||||
sendProgress
|
||||
) => {
|
||||
let installerOutput = ''
|
||||
let lastLogSize = 0
|
||||
let lastPercent = null
|
||||
let lastMessage = null
|
||||
let pollCount = 0
|
||||
|
||||
const poll = async () => {
|
||||
pollCount += 1
|
||||
|
||||
try {
|
||||
const stat = await fs.stat(logPath)
|
||||
if (stat.size === 0) {
|
||||
debugLog(`poll #${pollCount}: log exists but is empty`, { logPath })
|
||||
return
|
||||
}
|
||||
|
||||
if (stat.size === lastLogSize) {
|
||||
debugLog(`poll #${pollCount}: no new log data`, {
|
||||
logPath,
|
||||
size: stat.size
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const buffer = Buffer.alloc(stat.size)
|
||||
const handle = await fs.open(logPath, 'r')
|
||||
try {
|
||||
await handle.read(buffer, 0, stat.size, 0)
|
||||
} finally {
|
||||
await handle.close()
|
||||
}
|
||||
|
||||
lastLogSize = stat.size
|
||||
installerOutput = decodeMsiLogBuffer(buffer)
|
||||
|
||||
const { percent, message, stats } =
|
||||
parseWindowsInstallerProgress(installerOutput)
|
||||
const resolvedPercent = percent ?? lastPercent ?? 0
|
||||
const resolvedMessage = message || 'Installing update...'
|
||||
|
||||
debugLog(`poll #${pollCount}: parsed installer log`, {
|
||||
logPath,
|
||||
size: stat.size,
|
||||
textLength: installerOutput.length,
|
||||
preview: installerOutput.slice(0, 240).replace(/\s+/g, ' '),
|
||||
parsed: stats,
|
||||
resolvedPercent,
|
||||
resolvedMessage
|
||||
})
|
||||
|
||||
if (
|
||||
resolvedPercent !== lastPercent ||
|
||||
resolvedMessage !== lastMessage
|
||||
) {
|
||||
debugLog(`poll #${pollCount}: sending progress update`, {
|
||||
percent: resolvedPercent,
|
||||
message: resolvedMessage
|
||||
})
|
||||
|
||||
lastPercent = resolvedPercent
|
||||
lastMessage = resolvedMessage
|
||||
sendProgress(webContents, {
|
||||
phase: 'installing',
|
||||
percent: resolvedPercent,
|
||||
message: resolvedMessage
|
||||
})
|
||||
} else {
|
||||
debugLog(`poll #${pollCount}: progress unchanged, skipping UI update`, {
|
||||
percent: resolvedPercent,
|
||||
message: resolvedMessage
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.code === 'ENOENT') {
|
||||
debugLog(`poll #${pollCount}: log file not created yet`, { logPath })
|
||||
return
|
||||
}
|
||||
|
||||
console.error(`${DEBUG_PREFIX} installer log poll error:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
poll().catch((error) => {
|
||||
console.error(`${DEBUG_PREFIX} installer log poll error:`, error)
|
||||
})
|
||||
}, 300)
|
||||
|
||||
return async () => {
|
||||
clearInterval(intervalId)
|
||||
await poll()
|
||||
debugLog('stopped progress watch', {
|
||||
logPath,
|
||||
finalSize: lastLogSize,
|
||||
textLength: installerOutput.length,
|
||||
pollCount
|
||||
})
|
||||
return installerOutput
|
||||
}
|
||||
}
|
||||
|
||||
export const launchWindowsInstaller = async (
|
||||
app,
|
||||
installerPath,
|
||||
webContents,
|
||||
{ sendProgress, getInstallErrorMessage }
|
||||
) => {
|
||||
const resolvedPath = await prepareInstallerPath(installerPath)
|
||||
const logPath = path.join(path.dirname(resolvedPath), 'install.log')
|
||||
|
||||
debugLog('prepared installer', {
|
||||
installerPath,
|
||||
resolvedPath,
|
||||
logPath
|
||||
})
|
||||
|
||||
sendProgress(webContents, {
|
||||
phase: 'installing',
|
||||
percent: 0,
|
||||
message: 'Installing update...'
|
||||
})
|
||||
|
||||
await fs.unlink(logPath).catch(() => {})
|
||||
|
||||
// Allow file handles from the download/copy to settle before msiexec opens the MSI.
|
||||
await sleep(2000)
|
||||
|
||||
const stopProgressWatch = startWindowsInstallerProgressWatch(
|
||||
logPath,
|
||||
webContents,
|
||||
sendProgress
|
||||
)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let processOutput = ''
|
||||
const startedAt = Date.now()
|
||||
|
||||
const installerArgs = [
|
||||
'/i',
|
||||
resolvedPath,
|
||||
'/qn',
|
||||
'/norestart',
|
||||
'/L*v!',
|
||||
logPath
|
||||
]
|
||||
|
||||
debugLog('spawning msiexec', {
|
||||
args: installerArgs,
|
||||
elapsedMs: Date.now() - startedAt
|
||||
})
|
||||
|
||||
const installerProcess = spawn('msiexec.exe', installerArgs, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
windowsHide: true
|
||||
})
|
||||
|
||||
installerProcess.stdout?.on('data', (data) => {
|
||||
const text = data.toString('utf16le')
|
||||
processOutput += text
|
||||
debugLog('msiexec stdout chunk', {
|
||||
length: text.length,
|
||||
preview: text.slice(0, 200)
|
||||
})
|
||||
})
|
||||
|
||||
installerProcess.stderr?.on('data', (data) => {
|
||||
const text = data.toString('utf16le')
|
||||
processOutput += text
|
||||
debugLog('msiexec stderr chunk', {
|
||||
length: text.length,
|
||||
preview: text.slice(0, 200)
|
||||
})
|
||||
})
|
||||
|
||||
installerProcess.on('spawn', () => {
|
||||
debugLog('msiexec spawned', {
|
||||
pid: installerProcess.pid,
|
||||
elapsedMs: Date.now() - startedAt
|
||||
})
|
||||
})
|
||||
|
||||
installerProcess.on('error', async (error) => {
|
||||
console.error(`${DEBUG_PREFIX} installer spawn error:`, error)
|
||||
const watchedOutput = await stopProgressWatch()
|
||||
|
||||
debugLog('installer spawn failed', {
|
||||
watchedOutputLength: watchedOutput.length,
|
||||
processOutputLength: processOutput.length
|
||||
})
|
||||
|
||||
const message = error?.message || 'Failed to start update installer.'
|
||||
sendProgress(webContents, {
|
||||
phase: 'error',
|
||||
percent: null,
|
||||
message
|
||||
})
|
||||
reject(error)
|
||||
})
|
||||
|
||||
installerProcess.on('exit', async (code, signal) => {
|
||||
const watchedOutput = await stopProgressWatch()
|
||||
const output = watchedOutput || processOutput
|
||||
const finalParse = parseWindowsInstallerProgress(output)
|
||||
|
||||
debugLog('msiexec exited', {
|
||||
code,
|
||||
signal,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
watchedOutputLength: watchedOutput.length,
|
||||
processOutputLength: processOutput.length,
|
||||
parsed: finalParse.stats,
|
||||
outputPreview: output.slice(0, 500).replace(/\s+/g, ' ')
|
||||
})
|
||||
|
||||
debugLog('keeping install log', { logPath })
|
||||
|
||||
if (code !== 0) {
|
||||
const message = getInstallErrorMessage(null, output)
|
||||
sendProgress(webContents, {
|
||||
phase: 'error',
|
||||
percent: null,
|
||||
message
|
||||
})
|
||||
reject(new Error(message))
|
||||
return
|
||||
}
|
||||
|
||||
const succeeded =
|
||||
isWindowsInstallSuccessful(output) ||
|
||||
(code === 0 && !isWindowsInstallFailed(output))
|
||||
|
||||
debugLog('install success evaluation', {
|
||||
succeeded,
|
||||
isSuccessful: isWindowsInstallSuccessful(output),
|
||||
isFailed: isWindowsInstallFailed(output),
|
||||
exitCode: code
|
||||
})
|
||||
|
||||
if (!succeeded) {
|
||||
const message = getInstallErrorMessage(null, output)
|
||||
sendProgress(webContents, {
|
||||
phase: 'error',
|
||||
percent: null,
|
||||
message
|
||||
})
|
||||
reject(new Error(message))
|
||||
return
|
||||
}
|
||||
|
||||
const { percent, message } = finalParse
|
||||
|
||||
sendProgress(webContents, {
|
||||
phase: 'installing',
|
||||
percent: percent ?? 100,
|
||||
message: message || 'Installation complete. Restarting Farm Control...'
|
||||
})
|
||||
|
||||
debugLog('installer completed successfully')
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
160
src/App.jsx
@ -6,7 +6,6 @@ import {
|
||||
Navigate
|
||||
} from 'react-router-dom'
|
||||
import { App, ConfigProvider } from 'antd'
|
||||
import { lazy } from 'react'
|
||||
|
||||
import Dashboard from './components/Dashboard/Dashboard.jsx'
|
||||
import PrivateRoute from './components/PrivateRoute'
|
||||
@ -18,7 +17,6 @@ import {
|
||||
ElectronSpotlightContentPage
|
||||
} from './components/Dashboard/context/SpotlightContext.jsx'
|
||||
import { ActionsModalProvider } from './components/Dashboard/context/ActionsModalContext.jsx'
|
||||
import { ActionsProvider } from './components/Dashboard/context/ActionsContext.jsx'
|
||||
import MissingPlaceholder from './components/Dashboard/common/MissingPlaceholder.jsx'
|
||||
|
||||
import {
|
||||
@ -30,16 +28,9 @@ import { ApiServerProvider } from './components/Dashboard/context/ApiServerConte
|
||||
import { NotificationProvider } from './components/Dashboard/context/NotificationContext.jsx'
|
||||
import { ElectronProvider } from './components/Dashboard/context/ElectronContext.jsx'
|
||||
import { MessageProvider } from './components/Dashboard/context/MessageContext.jsx'
|
||||
import { AppUpdateProvider } from './components/Dashboard/context/AppUpdateContext.jsx'
|
||||
import AuthCallback from './components/App/AuthCallback.jsx'
|
||||
import EmailNotificationTemplate from './components/Email/EmailNotificationTemplate.jsx'
|
||||
import MarketplaceAuthCallback from './components/Dashboard/Sales/Marketplaces/MarketplaceAuthCallback.jsx'
|
||||
import AuthLaunch from './components/App/AppLaunch.jsx'
|
||||
import { TableStateProvider } from './components/Dashboard/context/TableStateContext.jsx'
|
||||
const SlicerIntegration = lazy(
|
||||
() =>
|
||||
import('./components/Dashboard/Production/Printers/SlicerIntegration.jsx')
|
||||
)
|
||||
|
||||
import {
|
||||
ProductionRoutes,
|
||||
@ -47,8 +38,7 @@ import {
|
||||
FinanceRoutes,
|
||||
SalesRoutes,
|
||||
ManagementRoutes,
|
||||
DeveloperRoutes,
|
||||
ModelRoutes
|
||||
DeveloperRoutes
|
||||
} from './routes'
|
||||
|
||||
const getRouter = () => {
|
||||
@ -85,89 +75,71 @@ const AppContent = () => {
|
||||
<PrintServerProvider>
|
||||
<ApiServerProvider>
|
||||
<MessageProvider>
|
||||
<TableStateProvider>
|
||||
<AppUpdateProvider>
|
||||
<NotificationProvider>
|
||||
<SpotlightProvider>
|
||||
<ActionsModalProvider>
|
||||
<ActionsProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path='/applaunch'
|
||||
element={<AuthLaunch />}
|
||||
/>
|
||||
<Route
|
||||
path='/dashboard/electron/spotlightcontent'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => (
|
||||
<ElectronSpotlightContentPage />
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => (
|
||||
<Navigate
|
||||
to='/dashboard/production/overview'
|
||||
replace
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/auth/callback'
|
||||
element={<AuthCallback />}
|
||||
/>
|
||||
<Route
|
||||
path='/auth/marketplace/callback'
|
||||
element={<MarketplaceAuthCallback />}
|
||||
/>
|
||||
<Route
|
||||
path='/email/notification'
|
||||
element={<EmailNotificationTemplate />}
|
||||
/>
|
||||
<Route
|
||||
path='/slicer'
|
||||
element={<SlicerIntegration />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/dashboard'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => <Dashboard />}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{ProductionRoutes}
|
||||
{InventoryRoutes}
|
||||
{FinanceRoutes}
|
||||
{SalesRoutes}
|
||||
{ManagementRoutes}
|
||||
{ModelRoutes}
|
||||
{DeveloperRoutes}
|
||||
</Route>
|
||||
<Route
|
||||
path='*'
|
||||
element={
|
||||
<AppError
|
||||
message='Error 404. Page not found.'
|
||||
showRefresh={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</ActionsProvider>
|
||||
</ActionsModalProvider>
|
||||
</SpotlightProvider>
|
||||
</NotificationProvider>
|
||||
</AppUpdateProvider>
|
||||
</TableStateProvider>
|
||||
<NotificationProvider>
|
||||
<SpotlightProvider>
|
||||
<ActionsModalProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path='/dashboard/electron/spotlightcontent'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => (
|
||||
<ElectronSpotlightContentPage />
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => (
|
||||
<Navigate
|
||||
to='/dashboard/production/overview'
|
||||
replace
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/auth/callback'
|
||||
element={<AuthCallback />}
|
||||
/>
|
||||
<Route
|
||||
path='/auth/marketplace/callback'
|
||||
element={<MarketplaceAuthCallback />}
|
||||
/>
|
||||
<Route
|
||||
path='/email/notification'
|
||||
element={<EmailNotificationTemplate />}
|
||||
/>
|
||||
<Route
|
||||
path='/dashboard'
|
||||
element={
|
||||
<PrivateRoute component={() => <Dashboard />} />
|
||||
}
|
||||
>
|
||||
{ProductionRoutes}
|
||||
{InventoryRoutes}
|
||||
{FinanceRoutes}
|
||||
{SalesRoutes}
|
||||
{ManagementRoutes}
|
||||
{DeveloperRoutes}
|
||||
</Route>
|
||||
<Route
|
||||
path='*'
|
||||
element={
|
||||
<AppError
|
||||
message='Error 404. Page not found.'
|
||||
showRefresh={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</ActionsModalProvider>
|
||||
</SpotlightProvider>
|
||||
</NotificationProvider>
|
||||
</MessageProvider>
|
||||
</ApiServerProvider>
|
||||
</PrintServerProvider>
|
||||
|
||||
@ -1,204 +0,0 @@
|
||||
import { useContext, useEffect, useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Flex, Card, Alert, Button } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { customAlphabet } from 'nanoid'
|
||||
import AuthParticles from './AppParticles'
|
||||
import FarmControlLogo from '../Logos/FarmControlLogo'
|
||||
import ExclamationOctagonIcon from '../Icons/ExclamationOctagonIcon'
|
||||
import CheckIcon from '../Icons/CheckIcon'
|
||||
import ReloadIcon from '../Icons/ReloadIcon'
|
||||
import { ApiServerContext } from '../Dashboard/context/ApiServerContext'
|
||||
|
||||
const createLaunchSession = customAlphabet(
|
||||
'01abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
32
|
||||
)
|
||||
|
||||
const AuthLaunch = () => {
|
||||
const location = useLocation()
|
||||
const hasRedirected = useRef(false)
|
||||
const startTimeoutRef = useRef(null)
|
||||
const pollTimeoutRef = useRef(null)
|
||||
const { getAppLaunchSession } = useContext(ApiServerContext)
|
||||
const [launchError, setLaunchError] = useState(false)
|
||||
const [launchErrorMessage, setLaunchErrorMessage] = useState('')
|
||||
const [launchSuccess, setLaunchSuccess] = useState(false)
|
||||
|
||||
const handleRefresh = () => {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
const redirect = new URLSearchParams(location.search).get('redirect')
|
||||
const redirectType = new URLSearchParams(location.search).get(
|
||||
'redirectType'
|
||||
)
|
||||
|
||||
if (!redirect) {
|
||||
setLaunchError(true)
|
||||
setLaunchErrorMessage('No redirect provided!')
|
||||
return
|
||||
}
|
||||
|
||||
if (!redirect || hasRedirected.current) {
|
||||
return
|
||||
}
|
||||
|
||||
startTimeoutRef.current = setTimeout(() => {
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
hasRedirected.current = true
|
||||
const launchSession = createLaunchSession()
|
||||
let launchCheckCount = 0
|
||||
|
||||
setLaunchError(false)
|
||||
setLaunchErrorMessage('')
|
||||
setLaunchSuccess(false)
|
||||
|
||||
let redirectWithLaunchSession = redirect
|
||||
try {
|
||||
const redirectUrl = new URL(redirect, window.location.origin)
|
||||
redirectUrl.searchParams.set('launchSession', launchSession)
|
||||
redirectWithLaunchSession = redirectUrl.toString()
|
||||
} catch {
|
||||
const hasQuery = redirect.includes('?')
|
||||
const separator = hasQuery ? '&' : '?'
|
||||
redirectWithLaunchSession = `${redirect}${separator}launchSession=${encodeURIComponent(
|
||||
launchSession
|
||||
)}`
|
||||
}
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = redirectWithLaunchSession
|
||||
link.style.display = 'none'
|
||||
|
||||
if (redirectType === 'app-localhost') {
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault()
|
||||
window.open(
|
||||
redirectWithLaunchSession,
|
||||
'farmcontrol-launch',
|
||||
'width=480,height=640,menubar=no,toolbar=no,location=yes,status=no,resizable=yes,scrollbars=yes'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
const checkLaunchSession = async () => {
|
||||
launchCheckCount += 1
|
||||
|
||||
let launchComplete = false
|
||||
|
||||
try {
|
||||
const launchStatus = await getAppLaunchSession(launchSession)
|
||||
launchComplete = launchStatus?.complete === true
|
||||
} catch {
|
||||
launchComplete = false
|
||||
}
|
||||
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (launchComplete) {
|
||||
setLaunchSuccess(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (launchCheckCount >= 10) {
|
||||
setLaunchError(true)
|
||||
setLaunchErrorMessage('Failed to open Farm Control.')
|
||||
return
|
||||
}
|
||||
|
||||
pollTimeoutRef.current = setTimeout(() => {
|
||||
checkLaunchSession()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
checkLaunchSession()
|
||||
}, 0)
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
hasRedirected.current = false
|
||||
if (startTimeoutRef.current) {
|
||||
clearTimeout(startTimeoutRef.current)
|
||||
}
|
||||
if (pollTimeoutRef.current) {
|
||||
clearTimeout(pollTimeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [getAppLaunchSession, location.search])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'black'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'black',
|
||||
minHeight: '100vh',
|
||||
transition: 'opacity 0.5s ease-in-out',
|
||||
opacity: 1
|
||||
}}
|
||||
>
|
||||
<AuthParticles />
|
||||
<Flex
|
||||
align='center'
|
||||
justify='center'
|
||||
vertical
|
||||
style={{ height: '100vh' }}
|
||||
gap={'large'}
|
||||
>
|
||||
<Card style={{ borderRadius: 20 }}>
|
||||
<Flex vertical align='center'>
|
||||
<FarmControlLogo style={{ fontSize: '500px', height: '40px' }} />
|
||||
</Flex>
|
||||
</Card>
|
||||
{!launchError && !launchSuccess && (
|
||||
<Alert
|
||||
message='Launching Farm Control please wait...'
|
||||
icon={<LoadingOutlined />}
|
||||
showIcon
|
||||
/>
|
||||
)}
|
||||
{launchError && (
|
||||
<>
|
||||
<Alert
|
||||
message={launchErrorMessage}
|
||||
icon={<ExclamationOctagonIcon />}
|
||||
type='error'
|
||||
showIcon
|
||||
/>
|
||||
<Button
|
||||
icon={<ReloadIcon />}
|
||||
onClick={handleRefresh}
|
||||
size='large'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{launchSuccess && (
|
||||
<Alert
|
||||
message='Launch successful! You may now close this window.'
|
||||
icon={<CheckIcon />}
|
||||
type='success'
|
||||
showIcon
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthLaunch
|
||||
@ -31,16 +31,16 @@ const ApiContextDebug = () => {
|
||||
connecting,
|
||||
connected,
|
||||
fetchLoading,
|
||||
setObjectActivity,
|
||||
clearObjectActivity,
|
||||
fetchObjectActivities,
|
||||
lockObject,
|
||||
unlockObject,
|
||||
fetchObjectLock,
|
||||
updateObject,
|
||||
createObject,
|
||||
deleteObject,
|
||||
subscribeToObjectUpdates,
|
||||
subscribeToObjectEvent,
|
||||
subscribeToObjectTypeUpdates,
|
||||
subscribeToObjectActivity,
|
||||
subscribeToObjectLock,
|
||||
fetchObject,
|
||||
fetchObjects,
|
||||
fetchObjectsByProperty,
|
||||
@ -169,40 +169,40 @@ const ApiContextDebug = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const testSetObjectActivity = () => {
|
||||
const testLockObject = () => {
|
||||
try {
|
||||
setObjectActivity(testInputs.objectId, testInputs.objectType, 'viewing')
|
||||
msgApi.success('setActivity command sent')
|
||||
lockObject(testInputs.objectId, testInputs.objectType)
|
||||
msgApi.success('Lock command sent')
|
||||
} catch (err) {
|
||||
msgApi.error('setActivity command failed')
|
||||
logger.error('setActivity error:', err)
|
||||
msgApi.error('Lock command failed')
|
||||
logger.error('Lock error:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const testClearObjectActivity = () => {
|
||||
const testUnlockObject = () => {
|
||||
try {
|
||||
clearObjectActivity(testInputs.objectId, testInputs.objectType)
|
||||
msgApi.success('clearActivity command sent')
|
||||
unlockObject(testInputs.objectId, testInputs.objectType)
|
||||
msgApi.success('Unlock command sent')
|
||||
} catch (err) {
|
||||
msgApi.error('clearActivity command failed')
|
||||
logger.error('clearActivity error:', err)
|
||||
msgApi.error('Unlock command failed')
|
||||
logger.error('Unlock error:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const testFetchObjectActivities = async () => {
|
||||
const testFetchObjectLock = async () => {
|
||||
try {
|
||||
msgApi.loading('Testing fetchObjectActivities...', 0)
|
||||
const result = await fetchObjectActivities(
|
||||
msgApi.loading('Testing fetchObjectLock...', 0)
|
||||
const result = await fetchObjectLock(
|
||||
testInputs.objectId,
|
||||
testInputs.objectType
|
||||
)
|
||||
msgApi.destroy()
|
||||
msgApi.success('fetchObjectActivities test completed')
|
||||
logger.debug('fetchObjectActivities test completed', result)
|
||||
msgApi.success('fetchObjectLock test completed')
|
||||
logger.debug('fetchObjectLock test completed', result)
|
||||
} catch (err) {
|
||||
msgApi.destroy()
|
||||
msgApi.error('fetchObjectActivities test failed')
|
||||
logger.error('fetchObjectActivities error:', err)
|
||||
msgApi.error('fetchObjectLock test failed')
|
||||
logger.error('fetchObjectLock error:', err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -477,27 +477,28 @@ const ApiContextDebug = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const testSubscribeToObjectActivity = () => {
|
||||
const testSubscribeToObjectLock = () => {
|
||||
try {
|
||||
const callback = (activity) => {
|
||||
logger.debug('Object activity update received:', activity)
|
||||
const callback = (lockData) => {
|
||||
logger.debug('Object lock update received:', lockData)
|
||||
}
|
||||
const unsubscribe = subscribeToObjectActivity(
|
||||
const unsubscribe = subscribeToObjectLock(
|
||||
testInputs.objectId,
|
||||
testInputs.objectType,
|
||||
callback
|
||||
)
|
||||
msgApi.success('Subscribed to object activity updates')
|
||||
logger.debug('Subscribed to object activity updates for test-id')
|
||||
msgApi.success('Subscribed to object lock updates')
|
||||
logger.debug('Subscribed to object lock updates for test-id')
|
||||
|
||||
// Store unsubscribe function for cleanup
|
||||
setTimeout(() => {
|
||||
if (unsubscribe) {
|
||||
unsubscribe()
|
||||
logger.debug('Unsubscribed from object activity updates')
|
||||
logger.debug('Unsubscribed from object lock updates')
|
||||
}
|
||||
}, 10000)
|
||||
}, 10000) // Auto-unsubscribe after 10 seconds
|
||||
} catch (err) {
|
||||
msgApi.error('Subscribe to object activity updates failed')
|
||||
msgApi.error('Subscribe to object lock updates failed')
|
||||
logger.error('Subscribe error:', err)
|
||||
}
|
||||
}
|
||||
@ -718,8 +719,8 @@ const ApiContextDebug = () => {
|
||||
<Button onClick={testFetchObjects} block>
|
||||
Test fetchObjects
|
||||
</Button>
|
||||
<Button onClick={testFetchObjectActivities} block>
|
||||
Test fetchObjectActivities
|
||||
<Button onClick={testFetchObjectLock} block>
|
||||
Test fetchObjectLock
|
||||
</Button>
|
||||
<Button onClick={testFetchObjectsByProperty} block>
|
||||
Test fetchObjectsByProperty
|
||||
@ -745,11 +746,11 @@ const ApiContextDebug = () => {
|
||||
collapseKey='crudTests'
|
||||
>
|
||||
<Space direction='vertical' style={{ width: '100%' }}>
|
||||
<Button onClick={testSetObjectActivity} block>
|
||||
Test Set Activity
|
||||
<Button onClick={testLockObject} block>
|
||||
Test Lock Object
|
||||
</Button>
|
||||
<Button onClick={testClearObjectActivity} block>
|
||||
Test Clear Activity
|
||||
<Button onClick={testUnlockObject} block>
|
||||
Test Unlock Object
|
||||
</Button>
|
||||
<Button onClick={testUpdateObject} block>
|
||||
Test updateObject
|
||||
@ -802,8 +803,8 @@ const ApiContextDebug = () => {
|
||||
<Button onClick={testSubscribeToObjectTypeUpdates} block>
|
||||
Test Subscribe Object Type Updates
|
||||
</Button>
|
||||
<Button onClick={testSubscribeToObjectActivity} block>
|
||||
Test Subscribe Object Activity
|
||||
<Button onClick={testSubscribeToObjectLock} block>
|
||||
Test Subscribe Object Lock
|
||||
</Button>
|
||||
</Space>
|
||||
</InfoCollapse>
|
||||
|
||||
@ -1,14 +1,50 @@
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import DashboardSidebar from '../common/DashboardSidebar'
|
||||
import { getSidebarItems, getSidebarSelectedKey } from '../../../database/Sidebars'
|
||||
import { Typography } from 'antd'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'sessionstorage',
|
||||
label: 'Session Storage',
|
||||
icon: <Text>🗃️</Text>,
|
||||
path: '/dashboard/developer/sessionstorage'
|
||||
},
|
||||
{
|
||||
key: 'authcontextdebug',
|
||||
label: 'Auth Debug',
|
||||
icon: <Text>🔐</Text>,
|
||||
path: '/dashboard/developer/authcontextdebug'
|
||||
},
|
||||
{
|
||||
key: 'apicontextdebug',
|
||||
label: 'API Debug',
|
||||
icon: <Text>🌐</Text>,
|
||||
path: '/dashboard/developer/apicontextdebug'
|
||||
}
|
||||
]
|
||||
|
||||
const routeKeyMap = {
|
||||
'/dashboard/developer/sessionstorage': 'sessionstorage',
|
||||
'/dashboard/developer/authcontextdebug': 'authcontextdebug',
|
||||
'/dashboard/developer/apicontextdebug': 'apicontextdebug'
|
||||
}
|
||||
|
||||
const DeveloperSidebar = (props) => {
|
||||
const location = useLocation()
|
||||
const includeDev = import.meta.env.MODE === 'development'
|
||||
const items = getSidebarItems('developer', { includeDev })
|
||||
const selectedKey = getSidebarSelectedKey('developer', location.pathname, {
|
||||
includeDev
|
||||
})
|
||||
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] : 'sessionstorage'
|
||||
})()
|
||||
|
||||
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
|
||||
}
|
||||
|
||||
@ -1,14 +1,51 @@
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import DashboardSidebar from '../common/DashboardSidebar'
|
||||
import { getSidebarItems, getSidebarSelectedKey } from '../../../database/Sidebars'
|
||||
import InvoiceIcon from '../../Icons/InvoiceIcon'
|
||||
import PaymentIcon from '../../Icons/PaymentIcon'
|
||||
import FinanceIcon from '../../Icons/FinanceIcon'
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'overview',
|
||||
label: 'Overview',
|
||||
icon: <FinanceIcon />,
|
||||
path: '/dashboard/finance/overview'
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'invoices',
|
||||
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'
|
||||
}
|
||||
|
||||
const FinanceSidebar = (props) => {
|
||||
const location = useLocation()
|
||||
const includeDev = import.meta.env.MODE === 'development'
|
||||
const items = getSidebarItems('finance', { includeDev })
|
||||
const selectedKey = getSidebarSelectedKey('finance', location.pathname, {
|
||||
includeDev
|
||||
})
|
||||
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} />
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ const Invoices = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -80,15 +80,6 @@ const Invoices = () => {
|
||||
type='invoice'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -16,10 +16,8 @@ import PaymentIcon from '../../../Icons/PaymentIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||
import EditButtons from '../../common/EditButtons.jsx'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import { useActions } from '../../context/ActionsContext'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
@ -32,6 +30,9 @@ import {
|
||||
} 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'
|
||||
|
||||
const log = loglevel.getLogger('InvoiceInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -40,15 +41,6 @@ const InvoiceInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
|
||||
|
||||
const { setOnModalOk } = useActions()
|
||||
useEffect(() => {
|
||||
setOnModalOk(() => () => {
|
||||
objectFormRef.current?.handleFetchObject?.()
|
||||
})
|
||||
return () => setOnModalOk(null)
|
||||
}, [setOnModalOk])
|
||||
const invoiceId = new URLSearchParams(location.search).get('invoiceId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('InvoiceInfo', {
|
||||
info: true,
|
||||
@ -56,7 +48,7 @@ const InvoiceInfo = () => {
|
||||
invoiceShipments: true,
|
||||
payments: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
})
|
||||
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
@ -67,6 +59,9 @@ const InvoiceInfo = () => {
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
const [postInvoiceOpen, setPostInvoiceOpen] = useState(false)
|
||||
const [acknowledgeInvoiceOpen, setAcknowledgeInvoiceOpen] = useState(false)
|
||||
const [newPaymentOpen, setNewPaymentOpen] = useState(false)
|
||||
|
||||
const actions = {
|
||||
edit: () => {
|
||||
@ -85,6 +80,18 @@ const InvoiceInfo = () => {
|
||||
objectFormRef?.current?.handleDelete?.()
|
||||
return true
|
||||
},
|
||||
post: () => {
|
||||
setPostInvoiceOpen(true)
|
||||
return true
|
||||
},
|
||||
acknowledge: () => {
|
||||
setAcknowledgeInvoiceOpen(true)
|
||||
return true
|
||||
},
|
||||
newPayment: () => {
|
||||
setNewPaymentOpen(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const editDisabled =
|
||||
@ -93,6 +100,7 @@ const InvoiceInfo = () => {
|
||||
?.disabled(objectFormState.objectData) ?? false
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
@ -102,7 +110,7 @@ const InvoiceInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='invoice'
|
||||
@ -134,10 +142,7 @@ const InvoiceInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -154,18 +159,12 @@ const InvoiceInfo = () => {
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther ||
|
||||
objectFormState.lock?.locked ||
|
||||
objectFormState.loading ||
|
||||
editDisabled
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={invoiceId}
|
||||
objectType='invoice'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -181,7 +180,6 @@ const InvoiceInfo = () => {
|
||||
type='invoice'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -298,6 +296,65 @@ const InvoiceInfo = () => {
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
<Modal
|
||||
open={postInvoiceOpen}
|
||||
onCancel={() => {
|
||||
setPostInvoiceOpen(false)
|
||||
}}
|
||||
width={500}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<PostInvoice
|
||||
onOk={() => {
|
||||
setPostInvoiceOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
open={acknowledgeInvoiceOpen}
|
||||
onCancel={() => {
|
||||
setAcknowledgeInvoiceOpen(false)
|
||||
}}
|
||||
width={515}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<AcknowledgeInvoice
|
||||
onOk={() => {
|
||||
setAcknowledgeInvoiceOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
open={newPaymentOpen}
|
||||
styles={{ content: { paddingBottom: '24px' } }}
|
||||
footer={null}
|
||||
width={800}
|
||||
onCancel={() => {
|
||||
setNewPaymentOpen(false)
|
||||
}}
|
||||
destroyOnHidden={true}
|
||||
>
|
||||
<NewPayment
|
||||
onOk={() => {
|
||||
setNewPaymentOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
reset={newPaymentOpen}
|
||||
defaultValues={{
|
||||
invoice: { ...objectFormState.objectData },
|
||||
amount: objectFormState.objectData?.grandTotalAmount
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -34,8 +34,6 @@ const NewInvoice = ({ onOk, reset, defaultValues }) => {
|
||||
order: true,
|
||||
to: true,
|
||||
from: true,
|
||||
toType: true,
|
||||
fromType: true,
|
||||
issuedAt: true,
|
||||
dueAt: true
|
||||
}}
|
||||
@ -66,9 +64,7 @@ const NewInvoice = ({ onOk, reset, defaultValues }) => {
|
||||
sentAt: false,
|
||||
paidAt: false,
|
||||
cancelledAt: false,
|
||||
overdueAt: false,
|
||||
acknowledgedAt: false,
|
||||
postedAt: false
|
||||
overdueAt: false
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
|
||||
@ -50,7 +50,7 @@ const Payments = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -80,15 +80,6 @@ const Payments = () => {
|
||||
type='payment'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
@ -114,3 +105,4 @@ const Payments = () => {
|
||||
}
|
||||
|
||||
export default Payments
|
||||
|
||||
|
||||
@ -1,46 +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 AuthorisePayment = ({ onOk, objectData }) => {
|
||||
const [authoriseLoading, setAuthoriseLoading] = useState(false)
|
||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
||||
|
||||
const handleAuthorise = async () => {
|
||||
setAuthoriseLoading(true)
|
||||
try {
|
||||
const result = await sendObjectFunction(
|
||||
objectData._id,
|
||||
'Payment',
|
||||
'authorise'
|
||||
)
|
||||
if (result) {
|
||||
message.success('Payment authorised successfully')
|
||||
onOk(result)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error authorising payment:', error)
|
||||
} finally {
|
||||
setAuthoriseLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MessageDialogView
|
||||
title={'Are you sure you want to authorise this payment?'}
|
||||
description={`Authorising payment ${objectData?.name || objectData?._reference || objectData?._id} will update its status to authorised.`}
|
||||
onOk={handleAuthorise}
|
||||
okText='Authorise'
|
||||
okLoading={authoriseLoading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
AuthorisePayment.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default AuthorisePayment
|
||||
@ -1,46 +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 CancelPayment = ({ onOk, objectData }) => {
|
||||
const [cancelLoading, setCancelLoading] = useState(false)
|
||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
||||
|
||||
const handleCancel = async () => {
|
||||
setCancelLoading(true)
|
||||
try {
|
||||
const result = await sendObjectFunction(
|
||||
objectData._id,
|
||||
'Payment',
|
||||
'cancel'
|
||||
)
|
||||
if (result) {
|
||||
message.success('Payment cancelled successfully')
|
||||
onOk(result)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error cancelling payment:', error)
|
||||
} finally {
|
||||
setCancelLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MessageDialogView
|
||||
title={'Are you sure you want to cancel this payment?'}
|
||||
description={`Cancelling payment ${objectData?.name || objectData?._reference || objectData?._id} will update its status to cancelled.`}
|
||||
onOk={handleCancel}
|
||||
okText='Cancel'
|
||||
okLoading={cancelLoading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
CancelPayment.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default CancelPayment
|
||||
@ -1,46 +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 DeclinePayment = ({ onOk, objectData }) => {
|
||||
const [declineLoading, setDeclineLoading] = useState(false)
|
||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
||||
|
||||
const handleDecline = async () => {
|
||||
setDeclineLoading(true)
|
||||
try {
|
||||
const result = await sendObjectFunction(
|
||||
objectData._id,
|
||||
'Payment',
|
||||
'decline'
|
||||
)
|
||||
if (result) {
|
||||
message.success('Payment declined successfully')
|
||||
onOk(result)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error declining payment:', error)
|
||||
} finally {
|
||||
setDeclineLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MessageDialogView
|
||||
title={'Are you sure you want to decline this payment?'}
|
||||
description={`Declining payment ${objectData?.name || objectData?._reference || objectData?._id} will update its status to declined.`}
|
||||
onOk={handleDecline}
|
||||
okText='Decline'
|
||||
okLoading={declineLoading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
DeclinePayment.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default DeclinePayment
|
||||
@ -53,8 +53,6 @@ const NewPayment = ({ onOk, reset, defaultValues }) => {
|
||||
updatedAt: false,
|
||||
_reference: false,
|
||||
postedAt: false,
|
||||
authorisedAt: false,
|
||||
declinedAt: false,
|
||||
cancelledAt: false
|
||||
}}
|
||||
isEditing={false}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,10 +14,8 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import { useActions } from '../../context/ActionsContext'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
@ -25,6 +23,7 @@ import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import { getModelByName } from '../../../../database/ObjectModels.js'
|
||||
import PostPayment from './PostPayment.jsx'
|
||||
|
||||
const log = loglevel.getLogger('PaymentInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -33,20 +32,11 @@ const PaymentInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
|
||||
|
||||
const { setOnModalOk } = useActions()
|
||||
useEffect(() => {
|
||||
setOnModalOk(() => () => {
|
||||
objectFormRef.current?.handleFetchObject?.()
|
||||
})
|
||||
return () => setOnModalOk(null)
|
||||
}, [setOnModalOk])
|
||||
const paymentId = new URLSearchParams(location.search).get('paymentId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('PaymentInfo', {
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
})
|
||||
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
@ -57,6 +47,7 @@ const PaymentInfo = () => {
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
const [postPaymentOpen, setPostPaymentOpen] = useState(false)
|
||||
|
||||
const actions = {
|
||||
edit: () => {
|
||||
@ -75,6 +66,10 @@ const PaymentInfo = () => {
|
||||
objectFormRef?.current?.handleDelete?.()
|
||||
return true
|
||||
},
|
||||
post: () => {
|
||||
setPostPaymentOpen(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const editDisabled =
|
||||
@ -83,6 +78,7 @@ const PaymentInfo = () => {
|
||||
?.disabled(objectFormState.objectData) ?? false
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
@ -92,7 +88,7 @@ const PaymentInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='payment'
|
||||
@ -121,10 +117,7 @@ const PaymentInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -141,18 +134,12 @@ const PaymentInfo = () => {
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther ||
|
||||
objectFormState.lock?.locked ||
|
||||
objectFormState.loading ||
|
||||
editDisabled
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={paymentId}
|
||||
objectType='payment'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -168,7 +155,6 @@ const PaymentInfo = () => {
|
||||
type='payment'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -230,6 +216,25 @@ const PaymentInfo = () => {
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
<Modal
|
||||
open={postPaymentOpen}
|
||||
onCancel={() => {
|
||||
setPostPaymentOpen(false)
|
||||
}}
|
||||
width={500}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<PostPayment
|
||||
onOk={() => {
|
||||
setPostPaymentOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ const FilamentStocks = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -86,15 +86,6 @@ const FilamentStocks = () => {
|
||||
type='filamentStock'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
@ -8,21 +8,18 @@ import ObjectTable from '../../common/ObjectTable'
|
||||
import ObjectActions from '../../common/ObjectActions'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator'
|
||||
import LockIndicator from '../../common/LockIndicator'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon'
|
||||
import FilamentStockIcon from '../../../Icons/FilamentStockIcon'
|
||||
import NoteIcon from '../../../Icons/NoteIcon'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon'
|
||||
import GraphIcon from '../../../Icons/GraphIcon'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle'
|
||||
import ScrollBox from '../../common/ScrollBox'
|
||||
import HistoryDisplay from '../../common/HistoryDisplay'
|
||||
|
||||
const FilamentStockInfo = () => {
|
||||
const location = useLocation()
|
||||
@ -36,9 +33,8 @@ const FilamentStockInfo = () => {
|
||||
{
|
||||
info: true,
|
||||
events: true,
|
||||
history: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
@ -73,7 +69,7 @@ const FilamentStockInfo = () => {
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='filamentStock'
|
||||
@ -86,7 +82,6 @@ const FilamentStockInfo = () => {
|
||||
items={[
|
||||
{ key: 'info', label: 'Filament Stock Information' },
|
||||
{ key: 'events', label: 'Filament Stock Events' },
|
||||
{ key: 'history', label: 'Filament Stock History' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
@ -104,10 +99,7 @@ const FilamentStockInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -126,12 +118,6 @@ const FilamentStockInfo = () => {
|
||||
disabled={true}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={filamentStockId}
|
||||
objectType='filamentStock'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
@ -153,7 +139,6 @@ const FilamentStockInfo = () => {
|
||||
type='filamentStock'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -182,25 +167,7 @@ const FilamentStockInfo = () => {
|
||||
<ObjectTable
|
||||
type='stockEvent'
|
||||
masterFilter={{ 'parent._id': filamentStockId }}
|
||||
visibleColumns={{ parent: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Filament Stock History'
|
||||
icon={<GraphIcon />}
|
||||
active={collapseState.history}
|
||||
onToggle={(expanded) => updateCollapseState('history', expanded)}
|
||||
collapseKey='history'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<HistoryDisplay
|
||||
history={objectFormState.objectData?.history || []}
|
||||
objectType='filamentStock'
|
||||
chartType='line'
|
||||
loading={objectFormState.loading}
|
||||
visibleColumns={{ 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
@ -215,7 +215,6 @@ const LoadFilamentStock = ({
|
||||
showBed={false}
|
||||
showMoreInfo={false}
|
||||
id={loadFilamentStockFormValues.printer._id}
|
||||
offline={!connected}
|
||||
/>
|
||||
) : null}
|
||||
</Flex>
|
||||
|
||||
@ -175,7 +175,6 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
||||
showBed={false}
|
||||
showMoreInfo={false}
|
||||
id={unloadFilamentStockFormValues.printer._id}
|
||||
offline={!connected}
|
||||
/>
|
||||
) : null}
|
||||
</Flex>
|
||||
|
||||
@ -1,14 +1,118 @@
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import DashboardSidebar from '../common/DashboardSidebar'
|
||||
import { getSidebarItems, getSidebarSelectedKey } from '../../../database/Sidebars'
|
||||
import FilamentStockIcon from '../../Icons/FilamentStockIcon'
|
||||
import PartStockIcon from '../../Icons/PartStockIcon'
|
||||
import ProductStockIcon from '../../Icons/ProductStockIcon'
|
||||
import StockEventIcon from '../../Icons/StockEventIcon'
|
||||
import StockAuditIcon from '../../Icons/StockAuditIcon'
|
||||
import PurchaseOrderIcon from '../../Icons/PurchaseOrderIcon'
|
||||
import ShipmentIcon from '../../Icons/ShipmentIcon'
|
||||
import OrderItemIcon from '../../Icons/OrderItemIcon'
|
||||
import InventoryIcon from '../../Icons/InventoryIcon'
|
||||
import StockLocationIcon from '../../Icons/StockLocationIcon'
|
||||
import StockTransferIcon from '../../Icons/StockTransferIcon'
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'overview',
|
||||
label: 'Overview',
|
||||
icon: <InventoryIcon />,
|
||||
path: '/dashboard/inventory/overview'
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'filamentstocks',
|
||||
label: 'Filament Stocks',
|
||||
icon: <FilamentStockIcon />,
|
||||
path: '/dashboard/inventory/filamentstocks'
|
||||
},
|
||||
{
|
||||
key: 'partstocks',
|
||||
label: 'Part Stocks',
|
||||
icon: <PartStockIcon />,
|
||||
path: '/dashboard/inventory/partstocks'
|
||||
},
|
||||
{
|
||||
key: 'productstocks',
|
||||
label: 'Product Stocks',
|
||||
icon: <ProductStockIcon />,
|
||||
path: '/dashboard/inventory/productstocks'
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'purchaseorders',
|
||||
label: 'Purchase Orders',
|
||||
icon: <PurchaseOrderIcon />,
|
||||
path: '/dashboard/inventory/purchaseorders'
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'orderitems',
|
||||
label: 'Order Items',
|
||||
icon: <OrderItemIcon />,
|
||||
path: '/dashboard/inventory/orderitems'
|
||||
},
|
||||
{
|
||||
key: 'shipments',
|
||||
label: 'Shipments',
|
||||
icon: <ShipmentIcon />,
|
||||
path: '/dashboard/inventory/shipments'
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'stocklocations',
|
||||
label: 'Stock Locations',
|
||||
icon: <StockLocationIcon />,
|
||||
path: '/dashboard/inventory/stocklocations'
|
||||
},
|
||||
{
|
||||
key: 'stockevents',
|
||||
label: 'Stock Events',
|
||||
icon: <StockEventIcon />,
|
||||
path: '/dashboard/inventory/stockevents'
|
||||
},
|
||||
{
|
||||
key: 'stockaudits',
|
||||
label: 'Stock Audits',
|
||||
icon: <StockAuditIcon />,
|
||||
path: '/dashboard/inventory/stockaudits'
|
||||
},
|
||||
{
|
||||
key: 'stocktransfers',
|
||||
label: 'Stock Transfers',
|
||||
icon: <StockTransferIcon />,
|
||||
path: '/dashboard/inventory/stocktransfers'
|
||||
}
|
||||
]
|
||||
|
||||
const routeKeyMap = {
|
||||
'/dashboard/inventory/overview': 'overview',
|
||||
'/dashboard/inventory/filamentstocks': 'filamentstocks',
|
||||
'/dashboard/inventory/partstocks': 'partstocks',
|
||||
'/dashboard/inventory/productstocks': 'productstocks',
|
||||
'/dashboard/inventory/stocklocations': 'stocklocations',
|
||||
'/dashboard/inventory/stocktransfers': 'stocktransfers',
|
||||
'/dashboard/inventory/stockevents': 'stockevents',
|
||||
'/dashboard/inventory/stockaudits': 'stockaudits',
|
||||
'/dashboard/inventory/purchaseorders': 'purchaseorders',
|
||||
'/dashboard/inventory/orderitems': 'orderitems',
|
||||
'/dashboard/inventory/shipments': 'shipments'
|
||||
}
|
||||
|
||||
const InventorySidebar = (props) => {
|
||||
const location = useLocation()
|
||||
const includeDev = import.meta.env.MODE === 'development'
|
||||
const items = getSidebarItems('inventory', { includeDev })
|
||||
const selectedKey = getSidebarSelectedKey('inventory', location.pathname, {
|
||||
includeDev
|
||||
})
|
||||
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} />
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ const OrderItems = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -80,15 +80,6 @@ const OrderItems = () => {
|
||||
type='orderItem'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,8 +14,7 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -36,7 +35,7 @@ const OrderItemInfo = () => {
|
||||
{
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -79,7 +78,7 @@ const OrderItemInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='orderItem'
|
||||
@ -103,10 +102,7 @@ const OrderItemInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -122,17 +118,9 @@ const OrderItemInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={orderItemId}
|
||||
objectType='orderItem'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -148,7 +136,6 @@ const OrderItemInfo = () => {
|
||||
type='orderItem'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
|
||||
@ -55,7 +55,7 @@ const PartStocks = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -86,15 +86,6 @@ const PartStocks = () => {
|
||||
type='partStock'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -12,11 +12,9 @@ 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 GraphIcon from '../../../Icons/GraphIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||
import EditButtons from '../../common/EditButtons.jsx'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -24,8 +22,6 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import HistoryDisplay from '../../common/HistoryDisplay.jsx'
|
||||
import PartStockIcon from '../../../Icons/PartStockIcon.jsx'
|
||||
|
||||
const log = loglevel.getLogger('PartStockInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -39,10 +35,9 @@ const PartStockInfo = () => {
|
||||
'PartStockInfo',
|
||||
{
|
||||
info: true,
|
||||
events: true,
|
||||
history: true,
|
||||
stocks: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -50,7 +45,7 @@ const PartStockInfo = () => {
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
activities: [],
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
@ -81,7 +76,7 @@ const PartStockInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='partStock'
|
||||
@ -93,8 +88,6 @@ const PartStockInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Part Stock Information' },
|
||||
{ key: 'events', label: 'Part Stock Events' },
|
||||
{ key: 'history', label: 'Part Stock History' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
@ -112,10 +105,7 @@ const PartStockInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -131,17 +121,9 @@ const PartStockInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={partStockId}
|
||||
objectType='partStock'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -164,7 +146,6 @@ const PartStockInfo = () => {
|
||||
type='partStock'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -189,43 +170,6 @@ const PartStockInfo = () => {
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
|
||||
<InfoCollapse
|
||||
title='Part Stock Events'
|
||||
icon={<PartStockIcon />}
|
||||
active={collapseState.events}
|
||||
onToggle={(expanded) => updateCollapseState('events', expanded)}
|
||||
collapseKey='events'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='stockEvent'
|
||||
masterFilter={{ 'parent._id': partStockId }}
|
||||
visibleColumns={{ parent: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Part Stock History'
|
||||
icon={<GraphIcon />}
|
||||
active={collapseState.history}
|
||||
onToggle={(expanded) => updateCollapseState('history', expanded)}
|
||||
collapseKey='history'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<HistoryDisplay
|
||||
history={objectFormState.objectData?.history || []}
|
||||
objectType='partStock'
|
||||
chartType='line'
|
||||
loading={objectFormState.loading}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
|
||||
@ -56,7 +56,7 @@ const ProductStocks = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -87,15 +87,6 @@ const ProductStocks = () => {
|
||||
type='productStock'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -39,7 +39,6 @@ const NewProductStock = ({ onOk, reset, defaultValues }) => {
|
||||
bordered={false}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
_reference: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
partStocks: false
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -10,29 +10,22 @@ 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 {
|
||||
getModelProperty,
|
||||
getModelByName
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
|
||||
import PostProductStock from './PostProductStock.jsx'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import PartStockIcon from '../../../Icons/PartStockIcon.jsx'
|
||||
import ProductStockIcon from '../../../Icons/ProductStockIcon.jsx'
|
||||
import GraphIcon from '../../../Icons/GraphIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||
import EditButtons from '../../common/EditButtons.jsx'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import { useActions } from '../../context/ActionsContext'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import HistoryDisplay from '../../common/HistoryDisplay.jsx'
|
||||
|
||||
const log = loglevel.getLogger('ProductStockInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -41,27 +34,17 @@ const ProductStockInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
|
||||
|
||||
const { setOnModalOk } = useActions()
|
||||
useEffect(() => {
|
||||
setOnModalOk(() => () => {
|
||||
objectFormRef.current?.handleFetchObject?.()
|
||||
})
|
||||
return () => setOnModalOk(null)
|
||||
}, [setOnModalOk])
|
||||
const productStockId = new URLSearchParams(location.search).get(
|
||||
'productStockId'
|
||||
)
|
||||
const [postProductStockOpen, setPostProductStockOpen] = useState(false)
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'ProductStockInfo',
|
||||
{
|
||||
info: true,
|
||||
partStocks: true,
|
||||
events: true,
|
||||
history: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -69,7 +52,7 @@ const ProductStockInfo = () => {
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
activities: [],
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
@ -91,6 +74,10 @@ const ProductStockInfo = () => {
|
||||
objectFormRef?.current.handleDelete?.()
|
||||
return true
|
||||
},
|
||||
post: () => {
|
||||
setPostProductStockOpen(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const editDisabled =
|
||||
@ -99,6 +86,7 @@ const ProductStockInfo = () => {
|
||||
?.disabled(objectFormState.objectData) ?? false
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
@ -108,7 +96,7 @@ const ProductStockInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='productStock'
|
||||
@ -121,8 +109,6 @@ const ProductStockInfo = () => {
|
||||
items={[
|
||||
{ key: 'info', label: 'Product Stock Information' },
|
||||
{ key: 'partStocks', label: 'Part Stocks' },
|
||||
{ key: 'events', label: 'Product Stock Events' },
|
||||
{ key: 'history', label: 'Product Stock History' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
@ -140,10 +126,7 @@ const ProductStockInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -160,18 +143,12 @@ const ProductStockInfo = () => {
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther ||
|
||||
objectFormState.lock?.locked ||
|
||||
objectFormState.loading ||
|
||||
editDisabled
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={productStockId}
|
||||
objectType='productStock'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -187,7 +164,6 @@ const ProductStockInfo = () => {
|
||||
type='productStock'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -235,43 +211,6 @@ const ProductStockInfo = () => {
|
||||
</ObjectForm>
|
||||
</ActionHandler>
|
||||
|
||||
<InfoCollapse
|
||||
title='Product Stock Events'
|
||||
icon={<ProductStockIcon />}
|
||||
active={collapseState.events}
|
||||
onToggle={(expanded) => updateCollapseState('events', expanded)}
|
||||
collapseKey='events'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='stockEvent'
|
||||
masterFilter={{ 'parent._id': productStockId }}
|
||||
visibleColumns={{ parent: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Product Stock History'
|
||||
icon={<GraphIcon />}
|
||||
active={collapseState.history}
|
||||
onToggle={(expanded) => updateCollapseState('history', expanded)}
|
||||
collapseKey='history'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<HistoryDisplay
|
||||
history={objectFormState.objectData?.history || []}
|
||||
objectType='productStock'
|
||||
chartType='line'
|
||||
loading={objectFormState.loading}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
@ -306,6 +245,25 @@ const ProductStockInfo = () => {
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
<Modal
|
||||
open={postProductStockOpen}
|
||||
onCancel={() => {
|
||||
setPostProductStockOpen(false)
|
||||
}}
|
||||
width={500}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<PostProductStock
|
||||
onOk={() => {
|
||||
setPostProductStockOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ const PurchaseOrders = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -80,15 +80,6 @@ const PurchaseOrders = () => {
|
||||
type='purchaseOrder'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,10 +14,8 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import { useActions } from '../../context/ActionsContext'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
@ -25,10 +23,16 @@ import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
|
||||
import NewOrderItem from '../OrderItems/NewOrderItem.jsx'
|
||||
import NewShipment from '../Shipments/NewShipment.jsx'
|
||||
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)
|
||||
@ -39,15 +43,13 @@ const PurchaseOrderInfo = () => {
|
||||
const orderItemsTableRef = useRef(null)
|
||||
const shipmentsTableRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
|
||||
|
||||
const { setOnModalOk } = useActions()
|
||||
useEffect(() => {
|
||||
setOnModalOk(() => () => {
|
||||
objectFormRef.current?.handleFetchObject?.()
|
||||
})
|
||||
return () => setOnModalOk(null)
|
||||
}, [setOnModalOk])
|
||||
const [newOrderItemOpen, setNewOrderItemOpen] = useState(false)
|
||||
const [newShipmentOpen, setNewShipmentOpen] = useState(false)
|
||||
const [postPurchaseOrderOpen, setPostPurchaseOrderOpen] = useState(false)
|
||||
const [acknowledgePurchaseOrderOpen, setAcknowledgePurchaseOrderOpen] =
|
||||
useState(false)
|
||||
const [cancelPurchaseOrderOpen, setCancelPurchaseOrderOpen] = useState(false)
|
||||
const [newInvoiceOpen, setNewInvoiceOpen] = useState(false)
|
||||
const purchaseOrderId = new URLSearchParams(location.search).get(
|
||||
'purchaseOrderId'
|
||||
)
|
||||
@ -56,9 +58,7 @@ const PurchaseOrderInfo = () => {
|
||||
{
|
||||
info: true,
|
||||
notes: true,
|
||||
orderItems: true,
|
||||
shipments: true,
|
||||
auditLogs: false,
|
||||
auditLogs: true,
|
||||
invoices: true,
|
||||
stockEvents: true
|
||||
}
|
||||
@ -93,6 +93,30 @@ const PurchaseOrderInfo = () => {
|
||||
objectFormRef?.current?.handleDelete?.()
|
||||
return true
|
||||
},
|
||||
newOrderItem: () => {
|
||||
setNewOrderItemOpen(true)
|
||||
return true
|
||||
},
|
||||
newShipment: () => {
|
||||
setNewShipmentOpen(true)
|
||||
return true
|
||||
},
|
||||
newInvoice: () => {
|
||||
setNewInvoiceOpen(true)
|
||||
return true
|
||||
},
|
||||
post: () => {
|
||||
setPostPurchaseOrderOpen(true)
|
||||
return true
|
||||
},
|
||||
acknowledge: () => {
|
||||
setAcknowledgePurchaseOrderOpen(true)
|
||||
return true
|
||||
},
|
||||
cancel: () => {
|
||||
setCancelPurchaseOrderOpen(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const editDisabled = getModelByName('purchaseOrder')
|
||||
@ -100,6 +124,7 @@ const PurchaseOrderInfo = () => {
|
||||
.disabled(objectFormState.objectData)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
@ -109,7 +134,7 @@ const PurchaseOrderInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='purchaseOrder'
|
||||
@ -142,10 +167,7 @@ const PurchaseOrderInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -162,18 +184,12 @@ const PurchaseOrderInfo = () => {
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther ||
|
||||
objectFormState.lock?.locked ||
|
||||
objectFormState.loading ||
|
||||
editDisabled
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={purchaseOrderId}
|
||||
objectType='purchaseOrder'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -189,7 +205,6 @@ const PurchaseOrderInfo = () => {
|
||||
type='purchaseOrder'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -334,6 +349,122 @@ const PurchaseOrderInfo = () => {
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
<Modal
|
||||
open={newOrderItemOpen}
|
||||
onCancel={() => {
|
||||
setNewOrderItemOpen(false)
|
||||
}}
|
||||
width={800}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
>
|
||||
<NewOrderItem
|
||||
onOk={() => {
|
||||
setNewOrderItemOpen(false)
|
||||
}}
|
||||
reset={newOrderItemOpen}
|
||||
defaultValues={{
|
||||
order: { _id: purchaseOrderId },
|
||||
orderType: 'purchaseOrder',
|
||||
syncAmount: 'itemCost'
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
open={newShipmentOpen}
|
||||
onCancel={() => {
|
||||
setNewShipmentOpen(false)
|
||||
}}
|
||||
width={800}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
>
|
||||
<NewShipment
|
||||
onOk={() => {
|
||||
setNewShipmentOpen(false)
|
||||
}}
|
||||
reset={newShipmentOpen}
|
||||
defaultValues={{
|
||||
orderType: 'purchaseOrder',
|
||||
order: { _id: purchaseOrderId }
|
||||
}}
|
||||
/>
|
||||
</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={() => {
|
||||
setPostPurchaseOrderOpen(false)
|
||||
}}
|
||||
width={500}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<PostPurchaseOrder
|
||||
onOk={() => {
|
||||
setPostPurchaseOrderOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
open={acknowledgePurchaseOrderOpen}
|
||||
onCancel={() => {
|
||||
setAcknowledgePurchaseOrderOpen(false)
|
||||
}}
|
||||
width={515}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<AcknowledgePurchaseOrder
|
||||
onOk={() => {
|
||||
setAcknowledgePurchaseOrderOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
open={cancelPurchaseOrderOpen}
|
||||
onCancel={() => {
|
||||
setCancelPurchaseOrderOpen(false)
|
||||
}}
|
||||
width={515}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<CancelPurchaseOrder
|
||||
onOk={() => {
|
||||
setCancelPurchaseOrderOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ const Shipments = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -80,15 +80,6 @@ const Shipments = () => {
|
||||
type='shipment'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Modal, Space } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,8 +14,7 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -51,7 +50,7 @@ const PurchaseOrderInfo = () => {
|
||||
{
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -113,7 +112,7 @@ const PurchaseOrderInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='purchaseOrder'
|
||||
@ -139,10 +138,7 @@ const PurchaseOrderInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -158,17 +154,9 @@ const PurchaseOrderInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={purchaseOrderId}
|
||||
objectType='purchaseOrder'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -184,7 +172,6 @@ const PurchaseOrderInfo = () => {
|
||||
type='purchaseOrder'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,10 +14,8 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import { useActions } from '../../context/ActionsContext'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
@ -25,6 +23,9 @@ import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import OrderItemIcon from '../../../Icons/OrderItemIcon.jsx'
|
||||
import ShipShipment from './ShipShipment.jsx'
|
||||
import ReceiveShipment from './ReceiveShipment.jsx'
|
||||
import CancelShipment from './CancelShipment.jsx'
|
||||
|
||||
const log = loglevel.getLogger('ShipmentInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -33,22 +34,13 @@ const ShipmentInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
|
||||
|
||||
const { setOnModalOk } = useActions()
|
||||
useEffect(() => {
|
||||
setOnModalOk(() => () => {
|
||||
objectFormRef.current?.handleFetchObject?.()
|
||||
})
|
||||
return () => setOnModalOk(null)
|
||||
}, [setOnModalOk])
|
||||
const shipmentId = new URLSearchParams(location.search).get('shipmentId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'ShipmentInfo',
|
||||
{
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -60,6 +52,10 @@ const ShipmentInfo = () => {
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
|
||||
const [shipShipmentOpen, setShipShipmentOpen] = useState(false)
|
||||
const [receiveShipmentOpen, setReceiveShipmentOpen] = useState(false)
|
||||
const [cancelShipmentOpen, setCancelShipmentOpen] = useState(false)
|
||||
const actions = {
|
||||
edit: () => {
|
||||
objectFormRef?.current?.startEditing?.()
|
||||
@ -77,9 +73,22 @@ const ShipmentInfo = () => {
|
||||
objectFormRef?.current?.handleDelete?.()
|
||||
return true
|
||||
},
|
||||
ship: () => {
|
||||
setShipShipmentOpen(true)
|
||||
return true
|
||||
},
|
||||
receive: () => {
|
||||
setReceiveShipmentOpen(true)
|
||||
return true
|
||||
},
|
||||
cancel: () => {
|
||||
setCancelShipmentOpen(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
@ -89,7 +98,7 @@ const ShipmentInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='shipment'
|
||||
@ -118,10 +127,7 @@ const ShipmentInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -137,17 +143,9 @@ const ShipmentInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={shipmentId}
|
||||
objectType='shipment'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -163,7 +161,6 @@ const ShipmentInfo = () => {
|
||||
type='shipment'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -247,6 +244,61 @@ const ShipmentInfo = () => {
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
<Modal
|
||||
open={shipShipmentOpen}
|
||||
onCancel={() => {
|
||||
setShipShipmentOpen(false)
|
||||
}}
|
||||
width={515}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<ShipShipment
|
||||
onOk={() => {
|
||||
setShipShipmentOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
open={receiveShipmentOpen}
|
||||
onCancel={() => {
|
||||
setReceiveShipmentOpen(false)
|
||||
}}
|
||||
width={515}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<ReceiveShipment
|
||||
onOk={() => {
|
||||
setReceiveShipmentOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
open={cancelShipmentOpen}
|
||||
onCancel={() => {
|
||||
setCancelShipmentOpen(false)
|
||||
}}
|
||||
width={515}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<CancelShipment
|
||||
onOk={() => {
|
||||
setCancelShipmentOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ const StockAudits = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -86,15 +86,6 @@ const StockAudits = () => {
|
||||
type='stockAudit'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,8 +14,7 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -38,7 +37,7 @@ const StockAuditInfo = () => {
|
||||
info: true,
|
||||
stocks: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -46,7 +45,7 @@ const StockAuditInfo = () => {
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
activities: [],
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
@ -77,7 +76,7 @@ const StockAuditInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='stockAudit'
|
||||
@ -106,10 +105,7 @@ const StockAuditInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -125,17 +121,9 @@ const StockAuditInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={stockAuditId}
|
||||
objectType='stockAudit'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -158,7 +146,6 @@ const StockAuditInfo = () => {
|
||||
type='stockAudit'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
|
||||
@ -38,7 +38,7 @@ const StockEvents = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -69,15 +69,6 @@ const StockEvents = () => {
|
||||
type='stockEvent'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@ -53,7 +53,7 @@ const StockLocations = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -84,15 +84,6 @@ const StockLocations = () => {
|
||||
type='stockLocation'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,8 +14,7 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -39,7 +38,7 @@ const StockLocationInfo = () => {
|
||||
{
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -47,7 +46,7 @@ const StockLocationInfo = () => {
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
activities: [],
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
@ -78,7 +77,7 @@ const StockLocationInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='stockLocation'
|
||||
@ -107,10 +106,7 @@ const StockLocationInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -126,17 +122,9 @@ const StockLocationInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={stockLocationId}
|
||||
objectType='stockLocation'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -159,7 +147,6 @@ const StockLocationInfo = () => {
|
||||
type='stockLocation'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
|
||||
@ -53,7 +53,7 @@ const StockTransfers = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -84,15 +84,6 @@ const StockTransfers = () => {
|
||||
type='stockTransfer'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import useCollapseState from '../../hooks/useCollapseState.jsx'
|
||||
import NotesPanel from '../../common/NotesPanel.jsx'
|
||||
@ -8,20 +8,16 @@ 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 {
|
||||
getModelProperty,
|
||||
getModelByName
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
|
||||
import PostStockTransfer from './PostStockTransfer.jsx'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import StockTransferIcon from '../../../Icons/StockTransferIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||
import EditButtons from '../../common/EditButtons.jsx'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import { useActions } from '../../context/ActionsContext'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
@ -33,25 +29,17 @@ const StockTransferInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
|
||||
|
||||
const { setOnModalOk } = useActions()
|
||||
useEffect(() => {
|
||||
setOnModalOk(() => () => {
|
||||
objectFormRef.current?.handleFetchObject?.()
|
||||
})
|
||||
return () => setOnModalOk(null)
|
||||
}, [setOnModalOk])
|
||||
const stockTransferId = new URLSearchParams(location.search).get(
|
||||
'stockTransferId'
|
||||
)
|
||||
const [postOpen, setPostOpen] = useState(false)
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'StockTransferInfo',
|
||||
{
|
||||
info: true,
|
||||
lines: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -59,7 +47,7 @@ const StockTransferInfo = () => {
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
activities: [],
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
@ -81,6 +69,10 @@ const StockTransferInfo = () => {
|
||||
objectFormRef?.current.handleDelete?.()
|
||||
return true
|
||||
},
|
||||
post: () => {
|
||||
setPostOpen(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const editDisabled =
|
||||
@ -89,6 +81,7 @@ const StockTransferInfo = () => {
|
||||
?.disabled(objectFormState.objectData) ?? false
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
@ -98,7 +91,7 @@ const StockTransferInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='stockTransfer'
|
||||
@ -128,10 +121,7 @@ const StockTransferInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -148,18 +138,12 @@ const StockTransferInfo = () => {
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther ||
|
||||
objectFormState.lock?.locked ||
|
||||
objectFormState.loading ||
|
||||
editDisabled
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={stockTransferId}
|
||||
objectType='stockTransfer'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -175,7 +159,6 @@ const StockTransferInfo = () => {
|
||||
type='stockTransfer'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -257,6 +240,25 @@ const StockTransferInfo = () => {
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
<Modal
|
||||
open={postOpen}
|
||||
onCancel={() => {
|
||||
setPostOpen(false)
|
||||
}}
|
||||
width={520}
|
||||
footer={null}
|
||||
destroyOnHidden={true}
|
||||
centered={true}
|
||||
>
|
||||
<PostStockTransfer
|
||||
onOk={() => {
|
||||
setPostOpen(false)
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,191 +0,0 @@
|
||||
import { useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
Flex,
|
||||
Typography,
|
||||
Button,
|
||||
Dropdown,
|
||||
Skeleton,
|
||||
Tag,
|
||||
Divider
|
||||
} from 'antd'
|
||||
import useCollapseState from '../hooks/useCollapseState'
|
||||
import InfoCollapse from '../common/InfoCollapse'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import DeveloperIcon from '../../Icons/DeveloperIcon'
|
||||
import { version as appVersion } from '../../../../package.json'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
import { ElectronContext } from '../context/ElectronContext'
|
||||
import { AppUpdateContext } from '../context/AppUpdateContext'
|
||||
import { useMediaQuery } from 'react-responsive'
|
||||
import FarmControlAppIcon from '../../Logos/FarmControlAppIcon'
|
||||
import SoftwareUpdateIcon from '../../Icons/SoftwareUpdateIcon'
|
||||
const { Title, Text, Link } = Typography
|
||||
|
||||
const About = () => {
|
||||
const [collapseState, updateCollapseState] = useCollapseState('About', {
|
||||
updater: true
|
||||
})
|
||||
const { token } = useContext(AuthContext)
|
||||
const { fetchApiServerVersion, fetchWsServerVersion } =
|
||||
useContext(ApiServerContext)
|
||||
const { isElectron, getElectronVersion } = useContext(ElectronContext)
|
||||
const { checkForUpdates } = useContext(AppUpdateContext)
|
||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
|
||||
const buildNumber = import.meta.env.VITE_BUILD_NUMBER
|
||||
? 'b' + import.meta.env.VITE_BUILD_NUMBER
|
||||
: 'dev'
|
||||
const developmentMode = import.meta.env.MODE === 'development'
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: 'Reload Window',
|
||||
icon: <ReloadIcon />,
|
||||
onClick: () => {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
if (isElectron) {
|
||||
actions.unshift(
|
||||
{
|
||||
label: 'Check for Updates',
|
||||
icon: <SoftwareUpdateIcon />,
|
||||
onClick: checkForUpdates
|
||||
},
|
||||
{ type: 'divider' }
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
fetchApiServerVersion().then((version) => {
|
||||
setApiServerVersion(version)
|
||||
})
|
||||
fetchWsServerVersion().then((version) => {
|
||||
setWsServerVersion(version)
|
||||
})
|
||||
}
|
||||
}, [fetchApiServerVersion, fetchWsServerVersion, token])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isElectron) return
|
||||
|
||||
getElectronVersion()
|
||||
.then((version) => {
|
||||
setElectronVersion(version || 'unknown')
|
||||
})
|
||||
.catch(() => {
|
||||
setElectronVersion('unknown')
|
||||
})
|
||||
}, [getElectronVersion, isElectron])
|
||||
|
||||
const [apiServerVersion, setApiServerVersion] = useState(null)
|
||||
const [wsServerVersion, setWsServerVersion] = useState(null)
|
||||
const [electronVersion, setElectronVersion] = useState(null)
|
||||
|
||||
const apiServerVersionText = apiServerVersion ? (
|
||||
<Text>
|
||||
{`v${apiServerVersion.version}-${apiServerVersion.buildNumber == 'dev' ? 'dev' : 'b' + apiServerVersion.buildNumber}`}
|
||||
</Text>
|
||||
) : (
|
||||
<Skeleton.Input active size='small' className='text-skeleton' />
|
||||
)
|
||||
const wsServerVersionText = wsServerVersion ? (
|
||||
<Text>{`v${wsServerVersion.version}-${wsServerVersion.buildNumber == 'dev' ? 'dev' : 'b' + wsServerVersion.buildNumber}`}</Text>
|
||||
) : (
|
||||
<Skeleton.Input active size='small' className='text-skeleton' />
|
||||
)
|
||||
const electronVersionText = electronVersion ? (
|
||||
<Text>{`v${electronVersion}`}</Text>
|
||||
) : (
|
||||
<Skeleton.Input active size='small' className='text-skeleton' />
|
||||
)
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', minHeight: 0, overflowY: 'auto' }}>
|
||||
<Flex vertical gap='large'>
|
||||
<Flex vertical gap='middle' align='start'>
|
||||
<Dropdown menu={{ items: actions }}>
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
<Flex vertical gap='large'>
|
||||
<InfoCollapse
|
||||
title='About Farm Control'
|
||||
icon={<InfoCircleIcon />}
|
||||
canCollapse={false}
|
||||
active={collapseState.purchaseOrderStats}
|
||||
onToggle={(isActive) =>
|
||||
updateCollapseState('purchaseOrderStats', isActive)
|
||||
}
|
||||
className='no-t-padding-collapse'
|
||||
collapseKey='purchaseOrderStats'
|
||||
>
|
||||
<Flex gap='large'>
|
||||
<FarmControlAppIcon style={{ width: '200px', height: '200px' }} />
|
||||
<Flex vertical gap='small' justify='center'>
|
||||
<Flex gap='middle' align='center'>
|
||||
<Title level={2} style={{ margin: 0 }}>
|
||||
Farm Control
|
||||
</Title>
|
||||
{developmentMode && !isMobile && (
|
||||
<Tag
|
||||
color='yellow'
|
||||
style={{ marginRight: 0 }}
|
||||
icon={<DeveloperIcon />}
|
||||
>
|
||||
Development
|
||||
</Tag>
|
||||
)}
|
||||
</Flex>
|
||||
<Text type='secondary'>
|
||||
3D Printer ERP and Control Software.
|
||||
</Text>
|
||||
<Flex style={{ columnGap: '15px', rowGap: '8px' }} wrap='wrap'>
|
||||
<Text type='secondary'>
|
||||
User Interface:{' '}
|
||||
<Text>
|
||||
v{appVersion}-{buildNumber}
|
||||
</Text>
|
||||
</Text>
|
||||
{isElectron && (
|
||||
<Text type='secondary'>
|
||||
Electron: {electronVersionText}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Text type='secondary'>REST API: {apiServerVersionText}</Text>
|
||||
<Text type='secondary'>
|
||||
Web Socket: {wsServerVersionText}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap='middle' align='center'>
|
||||
{developmentMode && isMobile && (
|
||||
<Tag
|
||||
color='yellow'
|
||||
style={{ marginRight: 0 }}
|
||||
icon={<DeveloperIcon />}
|
||||
>
|
||||
Development
|
||||
</Tag>
|
||||
)}
|
||||
<Link href='https://ci.tombutcher.work/job/farmcontrol'>
|
||||
Jenkins
|
||||
</Link>
|
||||
<Divider type='vertical' style={{ margin: 0 }} />
|
||||
<Link href='https://github.com/farmcontrol'>GitHub</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default About
|
||||
@ -49,7 +49,7 @@ const AppPasswords = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -80,15 +80,6 @@ const AppPasswords = () => {
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect, useContext } from 'react'
|
||||
import { useRef, useState, useContext } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Flex, Space } from 'antd'
|
||||
import { Space, Flex, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
@ -10,16 +10,15 @@ import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import { useActions } from '../../context/ActionsContext'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import RegenerateAppPasswordSecret from './RegenerateAppPasswordSecret.jsx'
|
||||
import { getModelByName } from '../../../../database/ObjectModels.js'
|
||||
import { AuthContext } from '../../context/AuthContext.jsx'
|
||||
|
||||
@ -27,24 +26,16 @@ const AppPasswordInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
|
||||
|
||||
const { setOnModalOk } = useActions()
|
||||
useEffect(() => {
|
||||
setOnModalOk(() => () => {
|
||||
objectFormRef.current?.handleFetchObject?.()
|
||||
})
|
||||
return () => setOnModalOk(null)
|
||||
}, [setOnModalOk])
|
||||
const { userProfile } = useContext(AuthContext)
|
||||
const appPasswordId = new URLSearchParams(location.search).get(
|
||||
'appPasswordId'
|
||||
)
|
||||
const [regenerateSecretOpen, setRegenerateSecretOpen] = useState(false)
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'AppPasswordInfo',
|
||||
{
|
||||
info: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
@ -57,6 +48,10 @@ const AppPasswordInfo = () => {
|
||||
})
|
||||
|
||||
const actions = {
|
||||
regenerateSecret: () => {
|
||||
setRegenerateSecretOpen(true)
|
||||
return false
|
||||
},
|
||||
edit: () => {
|
||||
objectFormRef?.current?.startEditing?.()
|
||||
return false
|
||||
@ -76,13 +71,14 @@ const AppPasswordInfo = () => {
|
||||
.disabled({ ...objectFormState.objectData, _user: userProfile })
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='appPassword'
|
||||
@ -110,10 +106,7 @@ const AppPasswordInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -130,18 +123,12 @@ const AppPasswordInfo = () => {
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther ||
|
||||
objectFormState.lock?.locked ||
|
||||
objectFormState.loading ||
|
||||
editDisabled
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={appPasswordId}
|
||||
objectType='appPassword'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
@ -163,7 +150,6 @@ const AppPasswordInfo = () => {
|
||||
type='appPassword'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -202,6 +188,20 @@ const AppPasswordInfo = () => {
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
|
||||
<Modal
|
||||
open={regenerateSecretOpen}
|
||||
destroyOnClose
|
||||
width={650}
|
||||
onCancel={() => {
|
||||
actionHandlerRef.current?.clearAction?.()
|
||||
setRegenerateSecretOpen(false)
|
||||
}}
|
||||
footer={null}
|
||||
>
|
||||
<RegenerateAppPasswordSecret id={appPasswordId} />
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -24,12 +24,25 @@ const NewAppPassword = ({ onOk, reset, defaultValues = {} }) => {
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
labelWidth={75}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='appPassword'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
@ -40,13 +53,11 @@ const NewAppPassword = ({ onOk, reset, defaultValues = {} }) => {
|
||||
bordered={false}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
_reference: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
secret: false
|
||||
}}
|
||||
isEditing={false}
|
||||
labelWidth={70}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -52,10 +52,10 @@ const RegenerateAppPasswordSecret = ({ id }) => {
|
||||
<Flex justify='center' style={{ minWidth: '395px' }}>
|
||||
<Flex justify='center'>
|
||||
<Flex gap='small' align='center' justify='center'>
|
||||
<CopyButton size='default' text={appPassword} />
|
||||
<Text code style={{ fontSize: '18px' }}>
|
||||
{appPassword || '••••••••••••••••••••••••••••••••'}
|
||||
</Text>
|
||||
<CopyButton size='default' text={appPassword} />
|
||||
<Button
|
||||
type='text'
|
||||
loading={loading}
|
||||
|
||||
@ -1,249 +0,0 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { useState } from 'react'
|
||||
import { Button, Flex, Modal, Progress, Typography, theme, Divider } from 'antd'
|
||||
|
||||
import CloudIcon from '../../../Icons/CloudIcon'
|
||||
import HostIcon from '../../../Icons/HostIcon'
|
||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||
|
||||
import CheckCircleIcon from '../../../Icons/CheckCircleIcon'
|
||||
import XMarkCircleIcon from '../../../Icons/XMarkCircleIcon'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const formatBytes = (bytes) => {
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return null
|
||||
|
||||
const units = ['B', 'KB', 'MB', 'GB']
|
||||
let value = bytes
|
||||
let unitIndex = 0
|
||||
|
||||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||
value /= 1024
|
||||
unitIndex += 1
|
||||
}
|
||||
|
||||
return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${
|
||||
units[unitIndex]
|
||||
}`
|
||||
}
|
||||
|
||||
const STAGE_CONFIG = {
|
||||
download: {
|
||||
icon: CloudIcon,
|
||||
labels: {
|
||||
pending: 'Download',
|
||||
active: 'Downloading...',
|
||||
complete: 'Downloaded',
|
||||
error: 'Download failed'
|
||||
}
|
||||
},
|
||||
install: {
|
||||
icon: HostIcon,
|
||||
labels: {
|
||||
pending: 'Install',
|
||||
active: 'Installing...',
|
||||
complete: 'Installed',
|
||||
error: 'Install failed'
|
||||
}
|
||||
},
|
||||
restart: {
|
||||
icon: ReloadIcon,
|
||||
labels: {
|
||||
pending: 'Restart',
|
||||
active: 'Restarting...',
|
||||
complete: 'Restarted',
|
||||
error: 'Restart failed'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getStageColor = (status, token) => {
|
||||
if (status === 'complete') return token.colorSuccess
|
||||
if (status === 'active') return token.colorPrimary
|
||||
if (status === 'error') return token.colorError
|
||||
return token.colorTextQuaternary
|
||||
}
|
||||
|
||||
const getDownloadStageStatus = (phase, isError) => {
|
||||
if (isError && ['preparing', 'downloading'].includes(phase)) return 'error'
|
||||
if (['downloaded', 'installing'].includes(phase)) return 'complete'
|
||||
if (['preparing', 'downloading'].includes(phase)) return 'active'
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
const isInstallComplete = (phase, message) => {
|
||||
if (phase !== 'installing') return false
|
||||
|
||||
const normalized = String(message || '').toLowerCase()
|
||||
|
||||
return (
|
||||
normalized.includes('complete') ||
|
||||
normalized.includes('successful') ||
|
||||
normalized.includes('restarting')
|
||||
)
|
||||
}
|
||||
|
||||
const getInstallStageStatus = (phase, isError, message) => {
|
||||
if (isError && ['downloaded', 'installing'].includes(phase)) return 'error'
|
||||
if (isInstallComplete(phase, message)) return 'complete'
|
||||
if (phase === 'installing') return 'active'
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
const getRestartStageStatus = (phase, isError, message) => {
|
||||
if (isError && isInstallComplete(phase, message)) return 'error'
|
||||
if (isInstallComplete(phase, message)) return 'active'
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
const getProgressStatus = (stageStatus) => {
|
||||
if (stageStatus === 'error') return 'exception'
|
||||
if (stageStatus === 'complete') return 'success'
|
||||
return 'active'
|
||||
}
|
||||
|
||||
const UpdateStage = ({ stage, status, percent, detail }) => {
|
||||
const { token } = theme.useToken()
|
||||
const config = STAGE_CONFIG[stage]
|
||||
const StageIcon = config.icon
|
||||
const resolvedPercent =
|
||||
typeof percent === 'number' ? Math.min(percent, 100) : undefined
|
||||
const resolvedStatus =
|
||||
status !== 'error' && resolvedPercent === 100 ? 'complete' : status
|
||||
const color = getStageColor(resolvedStatus, token)
|
||||
const showProgress = resolvedStatus === 'active' && stage !== 'restart'
|
||||
|
||||
const StatusIcon =
|
||||
resolvedStatus === 'complete'
|
||||
? CheckCircleIcon
|
||||
: resolvedStatus === 'error'
|
||||
? XMarkCircleIcon
|
||||
: StageIcon
|
||||
|
||||
return (
|
||||
<Flex align='start' gap='middle' style={{ width: '100%' }}>
|
||||
<StatusIcon style={{ fontSize: 22, color, flexShrink: 0 }} />
|
||||
<Flex align='start' gap='24px' style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text style={{ flexShrink: 0 }}>{config.labels[resolvedStatus]}</Text>
|
||||
{showProgress && (
|
||||
<Flex vertical gap={2} style={{ flex: 1 }}>
|
||||
<Progress
|
||||
percent={resolvedPercent}
|
||||
status={getProgressStatus(resolvedStatus)}
|
||||
showInfo={typeof resolvedPercent === 'number'}
|
||||
style={{ flex: 1, margin: 0 }}
|
||||
/>
|
||||
{detail && <Text type='secondary'>{detail}</Text>}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
UpdateStage.propTypes = {
|
||||
stage: PropTypes.oneOf(['download', 'install', 'restart']).isRequired,
|
||||
status: PropTypes.oneOf(['pending', 'active', 'complete', 'error'])
|
||||
.isRequired,
|
||||
percent: PropTypes.number,
|
||||
detail: PropTypes.string
|
||||
}
|
||||
|
||||
const AppUpdateProgress = ({ progress, update, onClose }) => {
|
||||
const phase = progress?.phase || 'preparing'
|
||||
const percent =
|
||||
typeof progress?.percent === 'number'
|
||||
? Math.min(progress.percent, 100)
|
||||
: null
|
||||
const downloaded = formatBytes(progress?.downloadedBytes)
|
||||
const total = formatBytes(progress?.totalBytes)
|
||||
const message = progress?.message || 'Preparing update'
|
||||
const isError = phase === 'error'
|
||||
|
||||
const [errorModalOpen, setErrorModalOpen] = useState(true)
|
||||
|
||||
const downloadStatus = getDownloadStageStatus(phase, isError)
|
||||
const installStatus = getInstallStageStatus(phase, isError, message)
|
||||
const restartStatus = getRestartStageStatus(phase, isError, message)
|
||||
|
||||
const downloadPercent =
|
||||
downloadStatus === 'active' ? (phase === 'preparing' ? 0 : percent) : null
|
||||
|
||||
const installPercent = installStatus === 'active' ? percent : null
|
||||
|
||||
const downloadDetail =
|
||||
downloadStatus === 'active' && downloaded && total
|
||||
? `${downloaded} of ${total}`
|
||||
: null
|
||||
|
||||
const installDetail = installStatus === 'active' ? message : null
|
||||
|
||||
return (
|
||||
<Flex vertical gap='middle'>
|
||||
<Text>
|
||||
Updating Farm Control to version{' '}
|
||||
{update?.version ? `${update.version}` : 'unknown'} build{' '}
|
||||
{update?.buildNumber ? `${update.buildNumber}` : 'unknown'} from branch{' '}
|
||||
{update?.branch ? `${update.branch}` : 'unknown'}...
|
||||
</Text>
|
||||
|
||||
<Divider style={{ margin: '4px 0' }} />
|
||||
<Flex vertical gap='middle'>
|
||||
<UpdateStage
|
||||
stage='download'
|
||||
status={downloadStatus}
|
||||
percent={downloadPercent}
|
||||
detail={downloadDetail}
|
||||
/>
|
||||
<UpdateStage
|
||||
stage='install'
|
||||
status={installStatus}
|
||||
percent={installPercent}
|
||||
detail={installDetail}
|
||||
/>
|
||||
<UpdateStage stage='restart' status={restartStatus} />
|
||||
</Flex>
|
||||
|
||||
<Modal
|
||||
title='Update Failed'
|
||||
open={isError && errorModalOpen == true}
|
||||
centered
|
||||
closable={false}
|
||||
maskClosable={false}
|
||||
onCancel={() => {
|
||||
setErrorModalOpen(false)
|
||||
onClose()
|
||||
}}
|
||||
footer={[
|
||||
<Button
|
||||
key='close'
|
||||
onClick={() => {
|
||||
setErrorModalOpen(false)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
<Text>{message}</Text>
|
||||
</Modal>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
AppUpdateProgress.propTypes = {
|
||||
progress: PropTypes.shape({
|
||||
phase: PropTypes.string,
|
||||
percent: PropTypes.number,
|
||||
downloadedBytes: PropTypes.number,
|
||||
totalBytes: PropTypes.number,
|
||||
message: PropTypes.string,
|
||||
artifact: PropTypes.object
|
||||
}),
|
||||
update: PropTypes.object,
|
||||
onClose: PropTypes.func
|
||||
}
|
||||
|
||||
export default AppUpdateProgress
|
||||
@ -1,148 +0,0 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { useState } from 'react'
|
||||
import { Button, Flex, Typography, Card, Dropdown, Modal, Table } from 'antd'
|
||||
import TimeDisplay from '../../common/TimeDisplay'
|
||||
import FarmControlAppIcon from '../../../Logos/FarmControlAppIcon'
|
||||
import EyeIcon from '../../../Icons/EyeIcon'
|
||||
|
||||
const { Text, Title } = Typography
|
||||
|
||||
const NewAppUpdate = ({ update, onCancel, onUpdate }) => {
|
||||
const artifacts = Array.isArray(update?.artifacts) ? update.artifacts : []
|
||||
const primaryArtifact = artifacts.find((artifact) => artifact.url)
|
||||
|
||||
const [changesMenuOpen, setChangesMenuOpen] = useState(false)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Date',
|
||||
dataIndex: 'date',
|
||||
key: 'date',
|
||||
width: 290,
|
||||
render: (text, record) => {
|
||||
return <TimeDisplay dateTime={record.date} showSince={true} />
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Author',
|
||||
dataIndex: 'author',
|
||||
key: 'author',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: 'Message',
|
||||
dataIndex: 'message',
|
||||
key: 'message',
|
||||
width: 500
|
||||
}
|
||||
]
|
||||
|
||||
const actionsMenu = {
|
||||
items: [
|
||||
{
|
||||
label: 'View Changes',
|
||||
key: 'viewChanges',
|
||||
icon: <EyeIcon />
|
||||
}
|
||||
],
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'viewChanges') {
|
||||
setChangesMenuOpen(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex vertical gap='middle'>
|
||||
<Text>
|
||||
A new Farm Control update is available. Would you like to update now?
|
||||
</Text>
|
||||
<Card styles={{ body: { padding: '12px 18px 12px 12px' } }}>
|
||||
<Flex gap={12} style={{ width: '100%' }}>
|
||||
<FarmControlAppIcon style={{ width: '70px', height: '70px' }} />
|
||||
<Flex vertical gap={2} justify='center' style={{ width: '100%' }}>
|
||||
<Title level={3} style={{ margin: 0 }}>
|
||||
{'Farm Control UI'}
|
||||
</Title>
|
||||
<Flex
|
||||
align='center'
|
||||
justify='space-between'
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Flex style={{ columnGap: '15px', rowGap: '8px' }}>
|
||||
<Text style={{ margin: 0 }} type='secondary'>
|
||||
Version:{' '}
|
||||
<Text>
|
||||
{update?.version ? `v${update.version}` : 'Unknown'}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={{ margin: 0 }} type='secondary'>
|
||||
Build Number:{' '}
|
||||
<Text>
|
||||
{update?.buildNumber
|
||||
? `b${update.buildNumber}`
|
||||
: 'Unknown'}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={{ margin: 0 }} type='secondary'>
|
||||
Branch: <Text>{update?.branch || 'Unknown'}</Text>
|
||||
</Text>
|
||||
</Flex>
|
||||
<Dropdown menu={actionsMenu}>
|
||||
<Button size='small' type='text'>
|
||||
Actions
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
||||
<Flex justify='space-between' gap='small' align='center'>
|
||||
<Flex gap='small'>
|
||||
<Text type='secondary'>Built at:</Text>
|
||||
<TimeDisplay dateTime={update?.builtAt} />
|
||||
</Flex>
|
||||
<Flex gap='small'>
|
||||
<Button onClick={onCancel}>Not Now</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={() => onUpdate(update)}
|
||||
disabled={!primaryArtifact}
|
||||
>
|
||||
Update Now
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Modal
|
||||
open={changesMenuOpen}
|
||||
onCancel={() => setChangesMenuOpen(false)}
|
||||
footer={null}
|
||||
title='View Changes'
|
||||
width={1200}
|
||||
>
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<Table
|
||||
dataSource={update?.changes}
|
||||
columns={columns}
|
||||
scroll={{ x: 1100 }}
|
||||
bordered={true}
|
||||
pagination={false}
|
||||
className='child-table'
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
NewAppUpdate.propTypes = {
|
||||
update: PropTypes.object,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default NewAppUpdate
|
||||
@ -36,7 +36,7 @@ const AuditLogs = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -62,15 +62,6 @@ const AuditLogs = () => {
|
||||
visibleColumns={columnVisibility}
|
||||
type='auditLog'
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@ -50,7 +50,7 @@ const CourierServices = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -80,15 +80,6 @@ const CourierServices = () => {
|
||||
type='courierService'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
@ -13,8 +13,7 @@ import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -38,7 +37,7 @@ const CourierServiceInfo = () => {
|
||||
{
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
@ -77,7 +76,7 @@ const CourierServiceInfo = () => {
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='courierService'
|
||||
@ -106,10 +105,7 @@ const CourierServiceInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -125,17 +121,9 @@ const CourierServiceInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={courierServiceId}
|
||||
objectType='courierService'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
@ -157,7 +145,6 @@ const CourierServiceInfo = () => {
|
||||
type='courierService'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
|
||||
@ -10,9 +10,6 @@ const NewCourierService = ({ onOk, defaultValues }) => {
|
||||
defaultValues={{
|
||||
active: true,
|
||||
tracked: false,
|
||||
cost: 0,
|
||||
shippingCurrency: 'GBP',
|
||||
international: false,
|
||||
...defaultValues
|
||||
}}
|
||||
>
|
||||
@ -29,7 +26,6 @@ const NewCourierService = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={80}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -43,7 +39,6 @@ const NewCourierService = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={120}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -64,7 +59,6 @@ const NewCourierService = ({ onOk, defaultValues }) => {
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
labelWidth={120}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ const Couriers = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -79,15 +79,6 @@ const Couriers = () => {
|
||||
type='courier'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
@ -13,8 +13,7 @@ import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -35,7 +34,7 @@ const CourierInfo = () => {
|
||||
const [collapseState, updateCollapseState] = useCollapseState('CourierInfo', {
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
})
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
@ -73,7 +72,7 @@ const CourierInfo = () => {
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='courier'
|
||||
@ -102,10 +101,7 @@ const CourierInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -121,17 +117,9 @@ const CourierInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={courierId}
|
||||
objectType='courier'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
@ -153,7 +141,6 @@ const CourierInfo = () => {
|
||||
type='courier'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
|
||||
@ -19,7 +19,6 @@ const NewCourier = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={75}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -34,7 +33,6 @@ const NewCourier = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
labelWidth={85}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -54,7 +52,6 @@ const NewCourier = ({ onOk, defaultValues }) => {
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
labelWidth={85}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ const DocumentJobs = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -80,15 +80,6 @@ const DocumentJobs = () => {
|
||||
type='documentJob'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,8 +14,7 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -39,7 +38,7 @@ const DocumentJobInfo = () => {
|
||||
{
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
@ -74,7 +73,7 @@ const DocumentJobInfo = () => {
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='documentJob'
|
||||
@ -103,10 +102,7 @@ const DocumentJobInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -122,17 +118,9 @@ const DocumentJobInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={documentJobId}
|
||||
objectType='documentJob'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
@ -154,7 +142,6 @@ const DocumentJobInfo = () => {
|
||||
type='documentJob'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
|
||||
@ -35,7 +35,6 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
|
||||
column={1}
|
||||
visibleProperties={{ name: false }}
|
||||
bordered={false}
|
||||
labelWidth={115}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
@ -57,7 +56,7 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
|
||||
disabled={downloading}
|
||||
sideBarGrow={true}
|
||||
sideBar={
|
||||
<div style={{ minHeight: '760px', flexGrow: 1 }}>
|
||||
<div style={{ minHeight: '500px', flexGrow: 1 }}>
|
||||
<TemplatePreview
|
||||
objectData={objectData?.object}
|
||||
documentTemplate={objectData?.documentTemplate}
|
||||
|
||||
@ -49,7 +49,7 @@ const DocumentPrinters = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -79,15 +79,6 @@ const DocumentPrinters = () => {
|
||||
type='documentPrinter'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,8 +14,7 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -40,7 +39,7 @@ const DocumentPrinterInfo = () => {
|
||||
info: true,
|
||||
stocks: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -48,7 +47,7 @@ const DocumentPrinterInfo = () => {
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
activities: [],
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
@ -79,7 +78,7 @@ const DocumentPrinterInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='documentPrinter'
|
||||
@ -111,10 +110,7 @@ const DocumentPrinterInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -130,17 +126,9 @@ const DocumentPrinterInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={documentPrinterId}
|
||||
objectType='documentPrinter'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -163,7 +151,6 @@ const DocumentPrinterInfo = () => {
|
||||
type='documentPrinter'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -174,7 +161,6 @@ const DocumentPrinterInfo = () => {
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
labelWidth={165}
|
||||
type='documentPrinter'
|
||||
objectData={objectData}
|
||||
/>
|
||||
|
||||
@ -21,7 +21,6 @@ const NewDocumentPrinter = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
labelWidth={100}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -36,7 +35,6 @@ const NewDocumentPrinter = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={125}
|
||||
visibleProperties={{ content: false, testObject: false }}
|
||||
objectData={objectData}
|
||||
/>
|
||||
@ -54,12 +52,8 @@ const NewDocumentPrinter = ({ onOk, defaultValues }) => {
|
||||
_id: false,
|
||||
_reference: false,
|
||||
createdAt: false,
|
||||
connectedAt: false,
|
||||
state: false,
|
||||
online: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
labelWidth={130}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
|
||||
@ -48,7 +48,7 @@ const DocumentSizes = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -78,15 +78,6 @@ const DocumentSizes = () => {
|
||||
type='documentSize'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,8 +14,7 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -39,7 +38,7 @@ const DocumentSizeInfo = () => {
|
||||
{
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
@ -74,7 +73,7 @@ const DocumentSizeInfo = () => {
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='documentSize'
|
||||
@ -103,10 +102,7 @@ const DocumentSizeInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -122,17 +118,9 @@ const DocumentSizeInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={documentSizeId}
|
||||
objectType='documentSize'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
@ -154,7 +142,6 @@ const DocumentSizeInfo = () => {
|
||||
type='documentSize'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -164,7 +151,6 @@ const DocumentSizeInfo = () => {
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
labelWidth={160}
|
||||
type='documentSize'
|
||||
objectData={objectData}
|
||||
/>
|
||||
|
||||
@ -5,10 +5,7 @@ import WizardView from '../../common/WizardView'
|
||||
|
||||
const NewDocumentSize = ({ onOk, defaultValues }) => {
|
||||
return (
|
||||
<NewObjectForm
|
||||
type={'documentSize'}
|
||||
defaultValues={{ infiniteHeight: false, ...defaultValues }}
|
||||
>
|
||||
<NewObjectForm type={'documentSize'} defaultValues={{ ...defaultValues }}>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
@ -22,7 +19,6 @@ const NewDocumentSize = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={120}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -40,7 +36,6 @@ const NewDocumentSize = ({ onOk, defaultValues }) => {
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
labelWidth={120}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
|
||||
@ -50,7 +50,7 @@ const DocumentTemplates = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -80,15 +80,6 @@ const DocumentTemplates = () => {
|
||||
type='documentTemplate'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -10,7 +10,7 @@ import ViewButton from '../../common/ViewButton.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||
import EditButtons from '../../common/EditButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
@ -33,7 +33,7 @@ const DocumentTemplateDesign = () => {
|
||||
preview: true,
|
||||
editor: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -41,7 +41,7 @@ const DocumentTemplateDesign = () => {
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
activities: [],
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
@ -97,7 +97,7 @@ const DocumentTemplateDesign = () => {
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator activities={objectFormState.activities} />
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -113,7 +113,7 @@ const DocumentTemplateDesign = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={objectFormState.beingEditedByOther || objectFormState.loading}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
</Space>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,8 +14,7 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -40,7 +39,7 @@ const DocumentTemplateInfo = () => {
|
||||
info: true,
|
||||
stocks: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -48,7 +47,7 @@ const DocumentTemplateInfo = () => {
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
activities: [],
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
@ -79,7 +78,7 @@ const DocumentTemplateInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='documentTemplate'
|
||||
@ -108,10 +107,7 @@ const DocumentTemplateInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -127,17 +123,9 @@ const DocumentTemplateInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={documentTemplateId}
|
||||
objectType='documentTemplate'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -160,7 +148,6 @@ const DocumentTemplateInfo = () => {
|
||||
type='documentTemplate'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -177,7 +164,7 @@ const DocumentTemplateInfo = () => {
|
||||
content: false,
|
||||
testObject: false
|
||||
}}
|
||||
labelWidth='190px'
|
||||
labelWidth='175px'
|
||||
/>
|
||||
)
|
||||
}}
|
||||
|
||||
@ -21,7 +21,6 @@ const NewDocumentTemplate = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
labelWidth={130}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -53,7 +53,7 @@ const FilamentSkus = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -84,15 +84,6 @@ const FilamentSkus = () => {
|
||||
type='filamentSku'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
@ -8,8 +8,7 @@ import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
@ -25,15 +24,13 @@ const FilamentSkuInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const filamentSkuId = new URLSearchParams(location.search).get(
|
||||
'filamentSkuId'
|
||||
)
|
||||
const filamentSkuId = new URLSearchParams(location.search).get('filamentSkuId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'FilamentSkuInfo',
|
||||
{
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
@ -72,7 +69,7 @@ const FilamentSkuInfo = () => {
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='filamentSku'
|
||||
@ -101,10 +98,7 @@ const FilamentSkuInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -120,17 +114,9 @@ const FilamentSkuInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={filamentSkuId}
|
||||
objectType='filamentSku'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
@ -152,7 +138,6 @@ const FilamentSkuInfo = () => {
|
||||
type='filamentSku'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
|
||||
@ -8,14 +8,9 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
<NewObjectForm
|
||||
type='filamentSku'
|
||||
reset={reset}
|
||||
defaultValues={{
|
||||
overrideCost: false,
|
||||
color: '#ff0000',
|
||||
...defaultValues
|
||||
}}
|
||||
defaultValues={defaultValues}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
console.log('objectData', objectData)
|
||||
const steps = [
|
||||
{
|
||||
title: 'Required',
|
||||
@ -24,7 +19,7 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
labelWidth={80}
|
||||
labelWidth={70}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
@ -34,26 +29,27 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
cost: false,
|
||||
costWithTax: false,
|
||||
costTaxRate: false,
|
||||
overrideCost: false,
|
||||
vendor: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Cost',
|
||||
key: 'cost',
|
||||
title: 'Color & Cost',
|
||||
key: 'colorCost',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
labelWidth={120}
|
||||
required={true}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
overrideCost: true,
|
||||
cost: true,
|
||||
costTaxRate: true,
|
||||
costWithTax: true
|
||||
_id: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
barcode: false,
|
||||
filament: false,
|
||||
name: false,
|
||||
description: false
|
||||
}}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
@ -68,7 +64,7 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
labelWidth={110}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
barcode: true,
|
||||
description: true
|
||||
@ -89,12 +85,11 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
visibleProperties={{
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
_id: false,
|
||||
_reference: false
|
||||
_id: false
|
||||
}}
|
||||
labelWidth={100}
|
||||
bordered={false}
|
||||
isEditing={false}
|
||||
labelWidth={120}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -55,7 +55,7 @@ const Filaments = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -86,15 +86,6 @@ const Filaments = () => {
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config'
|
||||
@ -14,10 +14,8 @@ import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler'
|
||||
import { useActions } from '../../context/ActionsContext'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
@ -26,6 +24,7 @@ import FilamentSkuIcon from '../../../Icons/FilamentSkuIcon.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import NewFilamentSku from '../FilamentSkus/NewFilamentSku'
|
||||
|
||||
const log = loglevel.getLogger('FilamentInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -35,15 +34,8 @@ const FilamentInfo = () => {
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const filamentId = new URLSearchParams(location.search).get('filamentId')
|
||||
const [newFilamentSkuOpen, setNewFilamentSkuOpen] = useState(false)
|
||||
const filamentSkusTableRef = useRef()
|
||||
|
||||
const { setOnModalOk } = useActions()
|
||||
useEffect(() => {
|
||||
setOnModalOk(() => () => {
|
||||
filamentSkusTableRef.current?.reload?.()
|
||||
})
|
||||
return () => setOnModalOk(null)
|
||||
}, [setOnModalOk])
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'FilamentInfo',
|
||||
{
|
||||
@ -51,7 +43,7 @@ const FilamentInfo = () => {
|
||||
filamentSkus: true,
|
||||
stocks: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -65,6 +57,10 @@ const FilamentInfo = () => {
|
||||
})
|
||||
|
||||
const actions = {
|
||||
newFilamentSku: () => {
|
||||
setNewFilamentSkuOpen(true)
|
||||
return true
|
||||
},
|
||||
edit: () => {
|
||||
objectFormRef?.current?.startEditing?.()
|
||||
return false
|
||||
@ -84,192 +80,204 @@ const FilamentInfo = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{
|
||||
maxHeight: '100%',
|
||||
minHeight: 0
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='filament'
|
||||
id={filamentId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Filament Information' },
|
||||
{ key: 'filamentSkus', label: 'Filament SKUs' },
|
||||
{ key: 'stocks', label: 'Filament Stocks' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<UserNotifierToggle
|
||||
type='filament'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='filament'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{
|
||||
maxHeight: '100%',
|
||||
minHeight: 0
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='filament'
|
||||
id={filamentId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Filament Information' },
|
||||
{ key: 'filamentSkus', label: 'Filament SKUs' },
|
||||
{ key: 'stocks', label: 'Filament Stocks' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<UserNotifierToggle
|
||||
type='filament'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='filament'
|
||||
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>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
isEditing={objectFormState.isEditing}
|
||||
handleUpdate={() => {
|
||||
actionHandlerRef.current.callAction('finishEdit')
|
||||
}}
|
||||
cancelEditing={() => {
|
||||
actionHandlerRef.current.callAction('cancelEdit')
|
||||
}}
|
||||
startEditing={() => {
|
||||
actionHandlerRef.current.callAction('edit')
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={filamentId}
|
||||
objectType='filament'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<ScrollBox>
|
||||
<Flex vertical gap={'large'}>
|
||||
<ActionHandler
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Filament Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={filamentId}
|
||||
type='filament'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => {
|
||||
return (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
type='filament'
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
|
||||
<InfoCollapse
|
||||
title='Filament SKUs'
|
||||
icon={<FilamentSkuIcon />}
|
||||
active={collapseState.filamentSkus}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('filamentSkus', expanded)
|
||||
}
|
||||
collapseKey='filamentSkus'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
ref={filamentSkusTableRef}
|
||||
type='filamentSku'
|
||||
masterFilter={{ filament: filamentId }}
|
||||
visibleColumns={{ filament: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Filament Stocks'
|
||||
icon={<FilamentIcon />}
|
||||
active={collapseState.stocks}
|
||||
onToggle={(expanded) => updateCollapseState('stocks', expanded)}
|
||||
collapseKey='stocks'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='filamentStock'
|
||||
masterFilter={{ filament: filamentId }}
|
||||
visibleColumns={{
|
||||
filament: false,
|
||||
startingWeight: false
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={filamentId} type='filament' />
|
||||
</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': filamentId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
|
||||
<ScrollBox>
|
||||
<Flex vertical gap={'large'}>
|
||||
<ActionHandler
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Filament Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={filamentId}
|
||||
type='filament'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => {
|
||||
return (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
type='filament'
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
|
||||
<InfoCollapse
|
||||
title='Filament SKUs'
|
||||
icon={<FilamentSkuIcon />}
|
||||
active={collapseState.filamentSkus}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('filamentSkus', expanded)
|
||||
}
|
||||
collapseKey='filamentSkus'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
ref={filamentSkusTableRef}
|
||||
type='filamentSku'
|
||||
masterFilter={{ filament: filamentId }}
|
||||
visibleColumns={{ filament: false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Filament Stocks'
|
||||
icon={<FilamentIcon />}
|
||||
active={collapseState.stocks}
|
||||
onToggle={(expanded) => updateCollapseState('stocks', expanded)}
|
||||
collapseKey='stocks'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='filamentStock'
|
||||
masterFilter={{ filament: filamentId }}
|
||||
visibleColumns={{
|
||||
filament: false,
|
||||
startingWeight: false
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
|
||||
<Modal
|
||||
open={newFilamentSkuOpen}
|
||||
styles={{ content: { paddingBottom: '24px' } }}
|
||||
footer={null}
|
||||
width={700}
|
||||
onCancel={() => setNewFilamentSkuOpen(false)}
|
||||
destroyOnClose
|
||||
>
|
||||
<NewFilamentSku
|
||||
onOk={() => {
|
||||
setNewFilamentSkuOpen(false)
|
||||
filamentSkusTableRef.current?.reload?.()
|
||||
}}
|
||||
reset={newFilamentSkuOpen}
|
||||
defaultValues={{
|
||||
filament: filamentId ? { _id: filamentId } : undefined
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={filamentId} type='filament' />
|
||||
</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': filamentId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -18,33 +18,7 @@ const NewFilament = ({ onOk }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
labelWidth={120}
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
cost: false,
|
||||
costTaxRate: false,
|
||||
costWithTax: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Cost',
|
||||
key: 'cost',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='filament'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
labelWidth={120}
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
cost: true,
|
||||
costTaxRate: true,
|
||||
costWithTax: true
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -58,7 +32,6 @@ const NewFilament = ({ onOk }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={90}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -77,7 +50,6 @@ const NewFilament = ({ onOk }) => {
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
labelWidth={120}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
|
||||
@ -38,7 +38,7 @@ const Files = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -68,15 +68,6 @@ const Files = () => {
|
||||
type='file'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useContext, useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
import useCollapseState from '../../hooks/useCollapseState.jsx'
|
||||
@ -13,8 +13,7 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.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'
|
||||
@ -26,9 +25,6 @@ import FilePreview from '../../common/FilePreview.jsx'
|
||||
import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
|
||||
import { ApiServerContext } from '../../context/ApiServerContext.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import JsonObjectIcon from '../../../Icons/JsonObjectIcon.jsx'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||
|
||||
const log = loglevel.getLogger('FileInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -41,7 +37,7 @@ const FileInfo = () => {
|
||||
const [collapseState, updateCollapseState] = useCollapseState('FileInfo', {
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
})
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
@ -86,7 +82,7 @@ const FileInfo = () => {
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='file'
|
||||
@ -99,7 +95,6 @@ const FileInfo = () => {
|
||||
items={[
|
||||
{ key: 'info', label: 'File Information' },
|
||||
{ key: 'preview', label: 'Preview' },
|
||||
{ key: 'metaData', label: 'Meta Data' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
@ -117,10 +112,7 @@ const FileInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -136,17 +128,9 @@ const FileInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={fileId}
|
||||
objectType='file'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
@ -168,7 +152,6 @@ const FileInfo = () => {
|
||||
type='file'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -178,8 +161,6 @@ const FileInfo = () => {
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='file'
|
||||
labelWidth={170}
|
||||
visibleProperties={{ metaData: false }}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)}
|
||||
@ -204,24 +185,6 @@ const FileInfo = () => {
|
||||
<MissingPlaceholder message={'No file.'} />
|
||||
)}
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Meta Data'
|
||||
icon={<JsonObjectIcon />}
|
||||
active={collapseState.metaData}
|
||||
onToggle={(expanded) => updateCollapseState('metaData', expanded)}
|
||||
collapseKey='metaData'
|
||||
>
|
||||
{objectFormState?.objectData?._id ? (
|
||||
<Card>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('file', 'metaData')}
|
||||
objectData={objectFormState?.objectData}
|
||||
/>
|
||||
</Card>
|
||||
) : (
|
||||
<MissingPlaceholder message={'No file meta data.'} />
|
||||
)}
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
|
||||
@ -54,7 +54,7 @@ const Hosts = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
@ -85,15 +85,6 @@ const Hosts = () => {
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
saveFilterInSession={true}
|
||||
saveFilterInUrl={true}
|
||||
useFilterInSession={true}
|
||||
useFilterInUrl={true}
|
||||
saveSortInSession={true}
|
||||
saveSortInUrl={true}
|
||||
useSortInSession={true}
|
||||
useSortInUrl={true}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Card, Flex, Space } from 'antd'
|
||||
import { Space, Flex, Card, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
@ -14,13 +14,12 @@ 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 ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import { useActions } from '../../context/ActionsContext'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import HostOTP from './HostOtp.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||
import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
|
||||
@ -34,33 +33,30 @@ const HostInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
|
||||
|
||||
const { setOnModalOk } = useActions()
|
||||
useEffect(() => {
|
||||
setOnModalOk(() => () => {
|
||||
objectFormRef.current?.handleFetchObject?.()
|
||||
})
|
||||
return () => setOnModalOk(null)
|
||||
}, [setOnModalOk])
|
||||
const hostId = new URLSearchParams(location.search).get('hostId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('HostInfo', {
|
||||
info: true,
|
||||
printers: true,
|
||||
documentPrinters: true,
|
||||
notes: true,
|
||||
auditLogs: false
|
||||
auditLogs: true
|
||||
})
|
||||
|
||||
const [hostOTPOpen, setHostOTPOpen] = useState(false)
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
activities: [],
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
|
||||
const actions = {
|
||||
hostOTP: () => {
|
||||
setHostOTPOpen(true)
|
||||
return false
|
||||
},
|
||||
edit: () => {
|
||||
objectFormRef?.current.startEditing()
|
||||
return false
|
||||
@ -80,6 +76,7 @@ const HostInfo = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
@ -89,7 +86,7 @@ const HostInfo = () => {
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='host'
|
||||
@ -120,10 +117,7 @@ const HostInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<ActivityIndicator
|
||||
activities={objectFormState.activities}
|
||||
showStartingDivider={true}
|
||||
/>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
@ -139,17 +133,9 @@ const HostInfo = () => {
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={
|
||||
objectFormState.beingEditedByOther || objectFormState.loading
|
||||
}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||
_id={hostId}
|
||||
objectType='host'
|
||||
showEndingDivider={true}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
@ -172,7 +158,6 @@ const HostInfo = () => {
|
||||
type='host'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
setCurrentObject={true}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
@ -185,7 +170,6 @@ const HostInfo = () => {
|
||||
isEditing={isEditing}
|
||||
type='host'
|
||||
objectData={objectData}
|
||||
labelWidth='175px'
|
||||
/>
|
||||
)
|
||||
}}
|
||||
@ -270,6 +254,20 @@ const HostInfo = () => {
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
|
||||
<Modal
|
||||
open={hostOTPOpen}
|
||||
destroyOnHidden={true}
|
||||
width={650}
|
||||
onCancel={() => {
|
||||
actionHandlerRef.current.clearAction()
|
||||
setHostOTPOpen(false)
|
||||
}}
|
||||
footer={false}
|
||||
>
|
||||
<HostOTP id={hostId} />
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -85,6 +85,11 @@ const HostOTP = ({ id }) => {
|
||||
>
|
||||
<Flex justify='center'>
|
||||
<Flex gap={'small'} align='center' justify='center'>
|
||||
<CopyButton
|
||||
size='default'
|
||||
text={hostObject?.otp}
|
||||
disabled={loading}
|
||||
/>
|
||||
<div>
|
||||
<Input.OTP
|
||||
disabled={loading}
|
||||
@ -95,11 +100,6 @@ const HostOTP = ({ id }) => {
|
||||
onPaste={(e) => e.preventDefault()} // prevent pasting
|
||||
/>
|
||||
</div>
|
||||
<CopyButton
|
||||
size='default'
|
||||
text={hostObject?.otp}
|
||||
disabled={loading}
|
||||
/>
|
||||
<div style={{ margin: '0 6px 0 8px', paddingBottom: '5px' }}>
|
||||
{loading ? (
|
||||
<Text>
|
||||
|
||||