From 47ce2dfe8e96f8f383dad8214019004b1ee9df79 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Fri, 9 May 2025 22:23:51 +0100 Subject: [PATCH] Added more functionality. --- .eslintrc.json | 31 + .prettierrc.json | 7 + .vscode/settings.json | 5 + public/gcode-worker.js | 84 +++ public/silent-check-sso.html | 13 + src/assets/icons/filamenticon.afdesign | Bin 0 -> 81398 bytes src/assets/icons/filamenticon.svg | 8 + src/assets/icons/levelbedicon.afdesign | Bin 0 -> 33490 bytes src/assets/icons/levelbedicon.svg | 5 + src/assets/icons/newwindowicon.afdesign | Bin 0 -> 25541 bytes src/assets/icons/newwindowicon.svg | 9 + src/assets/icons/particon.afdesign | Bin 0 -> 47957 bytes src/assets/icons/particon.svg | 7 + src/assets/icons/producticon.afdesign | Bin 0 -> 49692 bytes src/assets/icons/producticon.svg | 5 + src/assets/icons/unloadicon.afdesign | Bin 0 -> 42187 bytes src/assets/icons/unloadicon.svg | 8 + src/components/Auth/_RegisterPasskey.jsx.old | 56 ++ src/components/Dashboard/Inventory/Spools.jsx | 204 +++++++ .../Dashboard/Inventory/Spools/EditSpool.jsx | 450 ++++++++++++++ .../Dashboard/Inventory/Spools/NewSpool.jsx | 443 ++++++++++++++ .../Dashboard/Management/Filaments.jsx | 262 ++++++++ .../Management/Filaments/FilamentInfo.jsx | 445 ++++++++++++++ .../Management/Filaments/NewFilament.jsx | 432 +++++++++++++ src/components/Dashboard/Management/Parts.jsx | 235 ++++++++ .../Dashboard/Management/Parts/NewPart.jsx | 471 +++++++++++++++ .../Dashboard/Management/Parts/PartInfo.jsx | 273 +++++++++ .../Dashboard/Management/Products.jsx | 237 ++++++++ .../Management/Products/NewProduct.jsx | 213 +++++++ .../Management/Products/ProductInfo.jsx | 273 +++++++++ .../Dashboard/Management/Vendors.jsx | 226 +++++++ .../Management/Vendors/NewVendor.jsx | 205 +++++++ .../Management/Vendors/VendorInfo.jsx | 247 ++++++++ .../Dashboard/Production/GCodeFiles.jsx | 325 ++++++++++ .../Production/GCodeFiles/EditGCodeFile.jsx | 228 +++++++ .../Production/GCodeFiles/GCodeFileInfo.jsx | 205 +++++++ .../Production/GCodeFiles/NewGCodeFile.jsx | 501 ++++++++++++++++ .../Dashboard/Production/Overview.jsx | 273 +++++++++ .../Dashboard/Production/PrintJobs.jsx | 381 ++++++++++++ .../Production/PrintJobs/NewPrintJob.jsx | 253 ++++++++ .../Production/PrintJobs/PrintJobInfo.jsx | 174 ++++++ .../Dashboard/Production/Printers.jsx | 329 ++++++++++ .../Production/Printers/ChangeFillament.jsx | 333 +++++++++++ .../Production/Printers/ControlPrinter.jsx | 357 +++++++++++ .../Production/Printers/NewPrinter.jsx | 565 ++++++++++++++++++ .../Production/Printers/PrinterInfo.jsx | 359 +++++++++++ .../Dashboard/common/FilamentSelect.jsx | 170 ++++++ .../Dashboard/common/GCodeFileSelect.jsx | 206 +++++++ src/components/Dashboard/common/IdText.jsx | 127 ++++ .../Dashboard/common/InventorySidebar.jsx | 59 ++ src/components/Dashboard/common/JobState.jsx | 116 ++++ .../Dashboard/common/ManagementSidebar.jsx | 98 +++ .../Dashboard/common/NotificationCenter.jsx | 0 .../Dashboard/common/PartSelect.jsx | 170 ++++++ .../Dashboard/common/PrinterJobsTree.jsx | 194 ++++++ .../Dashboard/common/PrinterMovementPanel.jsx | 256 ++++++++ .../Dashboard/common/PrinterSelect.jsx | 115 ++++ .../Dashboard/common/PrinterState.jsx | 189 ++++++ .../common/PrinterTemperaturePanel.jsx | 304 ++++++++++ .../Dashboard/common/ProductionSidebar.jsx | 78 +++ .../Dashboard/common/SubJobCounter.jsx | 119 ++++ .../Dashboard/common/SubJobState.jsx | 204 +++++++ .../Dashboard/common/SubJobsTree.jsx | 204 +++++++ .../Dashboard/context/SpotlightContext.js | 376 ++++++++++++ src/components/Dashboard/utils/GCode.js | 30 + src/components/Dashboard/utils/Utils.js | 31 + src/components/Icons/FilamentIcon.jsx | 7 + src/components/Icons/LevelBedIcon.jsx | 7 + src/components/Icons/NewWindowIcon.jsx | 7 + src/components/Icons/PartIcon.jsx | 7 + src/components/Icons/ProductIcon.jsx | 7 + src/components/Icons/UnloadIcon.jsx | 7 + 72 files changed, 12225 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .prettierrc.json create mode 100644 .vscode/settings.json create mode 100644 public/gcode-worker.js create mode 100644 public/silent-check-sso.html create mode 100644 src/assets/icons/filamenticon.afdesign create mode 100644 src/assets/icons/filamenticon.svg create mode 100644 src/assets/icons/levelbedicon.afdesign create mode 100644 src/assets/icons/levelbedicon.svg create mode 100644 src/assets/icons/newwindowicon.afdesign create mode 100644 src/assets/icons/newwindowicon.svg create mode 100644 src/assets/icons/particon.afdesign create mode 100644 src/assets/icons/particon.svg create mode 100644 src/assets/icons/producticon.afdesign create mode 100644 src/assets/icons/producticon.svg create mode 100644 src/assets/icons/unloadicon.afdesign create mode 100644 src/assets/icons/unloadicon.svg create mode 100644 src/components/Auth/_RegisterPasskey.jsx.old create mode 100644 src/components/Dashboard/Inventory/Spools.jsx create mode 100644 src/components/Dashboard/Inventory/Spools/EditSpool.jsx create mode 100644 src/components/Dashboard/Inventory/Spools/NewSpool.jsx create mode 100644 src/components/Dashboard/Management/Filaments.jsx create mode 100644 src/components/Dashboard/Management/Filaments/FilamentInfo.jsx create mode 100644 src/components/Dashboard/Management/Filaments/NewFilament.jsx create mode 100644 src/components/Dashboard/Management/Parts.jsx create mode 100644 src/components/Dashboard/Management/Parts/NewPart.jsx create mode 100644 src/components/Dashboard/Management/Parts/PartInfo.jsx create mode 100644 src/components/Dashboard/Management/Products.jsx create mode 100644 src/components/Dashboard/Management/Products/NewProduct.jsx create mode 100644 src/components/Dashboard/Management/Products/ProductInfo.jsx create mode 100644 src/components/Dashboard/Management/Vendors.jsx create mode 100644 src/components/Dashboard/Management/Vendors/NewVendor.jsx create mode 100644 src/components/Dashboard/Management/Vendors/VendorInfo.jsx create mode 100644 src/components/Dashboard/Production/GCodeFiles.jsx create mode 100644 src/components/Dashboard/Production/GCodeFiles/EditGCodeFile.jsx create mode 100644 src/components/Dashboard/Production/GCodeFiles/GCodeFileInfo.jsx create mode 100644 src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx create mode 100644 src/components/Dashboard/Production/Overview.jsx create mode 100644 src/components/Dashboard/Production/PrintJobs.jsx create mode 100644 src/components/Dashboard/Production/PrintJobs/NewPrintJob.jsx create mode 100644 src/components/Dashboard/Production/PrintJobs/PrintJobInfo.jsx create mode 100644 src/components/Dashboard/Production/Printers.jsx create mode 100644 src/components/Dashboard/Production/Printers/ChangeFillament.jsx create mode 100644 src/components/Dashboard/Production/Printers/ControlPrinter.jsx create mode 100644 src/components/Dashboard/Production/Printers/NewPrinter.jsx create mode 100644 src/components/Dashboard/Production/Printers/PrinterInfo.jsx create mode 100644 src/components/Dashboard/common/FilamentSelect.jsx create mode 100644 src/components/Dashboard/common/GCodeFileSelect.jsx create mode 100644 src/components/Dashboard/common/IdText.jsx create mode 100644 src/components/Dashboard/common/InventorySidebar.jsx create mode 100644 src/components/Dashboard/common/JobState.jsx create mode 100644 src/components/Dashboard/common/ManagementSidebar.jsx create mode 100644 src/components/Dashboard/common/NotificationCenter.jsx create mode 100644 src/components/Dashboard/common/PartSelect.jsx create mode 100644 src/components/Dashboard/common/PrinterJobsTree.jsx create mode 100644 src/components/Dashboard/common/PrinterMovementPanel.jsx create mode 100644 src/components/Dashboard/common/PrinterSelect.jsx create mode 100644 src/components/Dashboard/common/PrinterState.jsx create mode 100644 src/components/Dashboard/common/PrinterTemperaturePanel.jsx create mode 100644 src/components/Dashboard/common/ProductionSidebar.jsx create mode 100644 src/components/Dashboard/common/SubJobCounter.jsx create mode 100644 src/components/Dashboard/common/SubJobState.jsx create mode 100644 src/components/Dashboard/common/SubJobsTree.jsx create mode 100644 src/components/Dashboard/context/SpotlightContext.js create mode 100644 src/components/Dashboard/utils/GCode.js create mode 100644 src/components/Dashboard/utils/Utils.js create mode 100644 src/components/Icons/FilamentIcon.jsx create mode 100644 src/components/Icons/LevelBedIcon.jsx create mode 100644 src/components/Icons/NewWindowIcon.jsx create mode 100644 src/components/Icons/PartIcon.jsx create mode 100644 src/components/Icons/ProductIcon.jsx create mode 100644 src/components/Icons/UnloadIcon.jsx diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..08ebcd1 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,31 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:prettier/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended" + ], + "env": { + "browser": true, // Allows access to browser globals like `localStorage` + "node": true, // If you're also using Node.js + "es2021": true // Use ECMAScript 2021 features + }, + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "settings": { + "react": { + "version": "detect" // Automatically detect the React version + } + }, + "rules": { + "camelcase": ["error", { "properties": "always" }], + "multiline-ternary": ["error", "never"], + "no-debugger": "off", + "no-console": "warn" + } +} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..625a5f5 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "trailingComma": "none", + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "jsxSingleQuote": true +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f55da2d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "eslint.options": { + "overrideConfigFile": "./.eslintrc.json" + } +} diff --git a/public/gcode-worker.js b/public/gcode-worker.js new file mode 100644 index 0000000..c8a97de --- /dev/null +++ b/public/gcode-worker.js @@ -0,0 +1,84 @@ +// gcode-worker.js + +self.onmessage = function (event) { + const { configString } = event.data + const configObject = {} + let isThumbnailSection = false + let base64ImageData = '' + const lines = configString.split('\n') + const totalLines = lines.length + + for (let i = 0; i < totalLines; i++) { + const line = lines[i] + let trimmedLine = line.trim() + + // Skip empty lines or lines that are not part of the config + if (!trimmedLine || !trimmedLine.startsWith(';')) { + continue + } + + // Remove the leading semicolon and trim the line + trimmedLine = trimmedLine.substring(1).trim() + + // Handle thumbnail section + if (trimmedLine.startsWith('thumbnail begin')) { + isThumbnailSection = true + base64ImageData = '' // Reset image data + continue + } else if (trimmedLine.startsWith('thumbnail end')) { + isThumbnailSection = false + configObject.thumbnail = base64ImageData // Store base64 string as-is + continue + } + + if (isThumbnailSection) { + base64ImageData += trimmedLine // Accumulate base64 data + continue + } + + // Split the line into key and value parts + let [key, ...valueParts] = trimmedLine.split('=').map((part) => part.trim()) + + if (!key || !valueParts.length) { + continue + } + + if ( + key === 'end_gcode' || + key === 'start_gcode' || + key === 'start_filament_gcode' || + key === 'end_filament_gcode' + ) { + continue + } + + const value = valueParts.join('=').trim() + + // Handle multi-line values (assuming they start and end with curly braces) + if (value.startsWith('{')) { + let multiLineValue = value + while (!multiLineValue.endsWith('}')) { + // Read the next line + const nextLine = lines[++i].trim() + multiLineValue += '\n' + nextLine + } + // Remove the starting and ending braces + configObject[key.replace(/\s+/g, '_').replace('(', '').replace(')', '')] = + multiLineValue.substring(1, multiLineValue.length - 1).trim() + } else { + key = key.replace('[', '').replace(']', '') + key = key.replace('(', '').replace(')', '') + // Regular key-value pair + configObject[key.replace(/\s+/g, '_')] = value + .replace('"', '') + .replace('"', '') + } + + // Report progress + const progress = ((i + 1) / totalLines) * 100 + self.postMessage({ type: 'progress', progress }) + } + + // Post the result back to the main thread + self.postMessage({ type: 'result', configObject }) +} diff --git a/public/silent-check-sso.html b/public/silent-check-sso.html new file mode 100644 index 0000000..dd91685 --- /dev/null +++ b/public/silent-check-sso.html @@ -0,0 +1,13 @@ + + + + Silent SSO Check + + + + + + diff --git a/src/assets/icons/filamenticon.afdesign b/src/assets/icons/filamenticon.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..3e5d04a0b41374ba457d83ba8c21fe1266c7db1e GIT binary patch literal 81398 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 aG6f;;)Q;cNBRV?)fU1&~Vy(Po + + + + + + + diff --git a/src/assets/icons/levelbedicon.afdesign b/src/assets/icons/levelbedicon.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..ec82c90102cb2676b0bb22a294c8aa92ce22e0e4 GIT binary patch literal 33490 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 A7XSbN literal 0 HcmV?d00001 diff --git a/src/assets/icons/levelbedicon.svg b/src/assets/icons/levelbedicon.svg new file mode 100644 index 0000000..af737c7 --- /dev/null +++ b/src/assets/icons/levelbedicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/newwindowicon.afdesign b/src/assets/icons/newwindowicon.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..556eb8574baa95a0690e26792e08c01ca6b8af4c GIT binary patch literal 25541 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 Ma^*tVdAB?N2U{^YcK`qY literal 0 HcmV?d00001 diff --git a/src/assets/icons/newwindowicon.svg b/src/assets/icons/newwindowicon.svg new file mode 100644 index 0000000..38c396a --- /dev/null +++ b/src/assets/icons/newwindowicon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/icons/particon.afdesign b/src/assets/icons/particon.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..f15a3cd1377dde40a221210586aeee272d570c97 GIT binary patch literal 47957 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

kK9!u{*Eu0zmQv{mD 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 Lo@$M + + + + + + diff --git a/src/assets/icons/producticon.afdesign b/src/assets/icons/producticon.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..dfea98baea6d2c625cb5ca039b3705eb8b7b9151 GIT binary patch literal 49692 zcmZ^Jb9`Mv({OCtP8!>`of{jC?XJbZ41i>n5@zj*!jAjkro(${va5v-wD&A4O;T{Rw?kUY&sC$gWN(^xHP8B2hM+? zogV%0Jdr+`YRMqEIX@YPIUjB>Je`Z3M@v?m&=u3NNTxW##Q7AdVI)7&&){Z@V&mvL z6^tlM4m7iP_YPS^C?Pqo$t_rheZl~YNsQ>_PCo4j+?Tn0Se8pfcJRnb3pOwWG6onY zs0?b8$hJN*KEz6z!fdcDF|ZQ^1822EG*Z2zV3gW@$npD|@zh;23uFH)E$cuH>T#ToLa}My5KlYV?pL#9o7Z2}E+Ty+h<) zB+=1c-UcWl3<*en$C$>;Jq0LcU97zmWM^5V?zzDq#G>6hdkQGnTJ^zUWMt@={j!D7 zvNNB-R7fb?bAgFL{9B?>%33tx-D3Ble1%lHlnCA-gK-qTffAD_iNZ#BLCLsVU^~^K zun2N%cim(i5VM%1XbHhQ{QSl!^7X+%@AL)@w8VIbaDwVffjq1>?9uE}}?vGnvrY0~nf@EbRod-lixth1H$y`w$*fCa?l{Bn6{L64KUz0}`)fU??eALx`X# zx2QP+VxTCS99Shb)tQf2Zc-|40i`sUuJjVQCWftdh#>vc3F{#(_%C%tv`|7~>Pxa1 zIOE}XabDkZ}7J zU^NJwY@-m~&O@Ky1ZA^zLSpDG5iy4%^Jpy^yLVV9hWj6f(1q*!s>u~aBaj2pZgr$w zwM@MCFHOO~N6fzmp$dt*V9O+fH9HSq5RbxptNzTlfEF;ZK7v6)NoIS(O)iM!fQ|ow z5K$x*O=KU>y0eF*?7c_xgG5-AN;!D1Kmtwdat{sjm;LX2G1%?_LTCQwd?{?$HQc57 z@u2Kr2-`p&7%&KEBf$~T&uj*q$D#Ia3WpM6pK;cxc2feW8a~M(%tz8f^V&b8h5NOC zzLQTt>Olnb8Rh?_O^plxe#@u__8PEYodJ&#G}3?yM&$yFH}9ZV9|yn-M4i0& z^c9t_d@8-&*b=D}1DEkind#^oSL9m)ejZ)7R zKhn6Da?aWm=LoO>whHBwSBh1t1&e>$vTX0Ax5hViT_Y^tP8(E8D>12y)}2cyH}EA& zT8BNGGi^KySu=ELnc$8;D6cI(HVe@!fC5im;9ntbgTHv2j1ro}-^&rOn61;OBZJ10}ZBxFNG&G16fv`E1dljwQAgKaNtA+zM5sW4D)bP!Drh~u{+WVGaj30Gouj(*WcR{nb4KT@R{9vGlYq>@8UR5RFr zmoVW?<81;y9fe9WNEd&!!O|2rnweI_%R^S_HK;B%Y*+dV@@IAQ9W%Fle1CJ|b>E2h zrJ|;uFWgg21Kx&ZjlWmBOInH?(`&4#Zle80;?Kda~T|`E7skW%P^;{j*8T@m!>kxl}l)|JZ^ z&#*9yET|Wt=Up9J|Ad}9U!bI|05k-1luN1EILoaQC^n)9Dx@4+n`M=dm8Q`%spJxU zImIhxB4V*9)r(~d3dYxKE>vR@vzUmbbZ4eh&$E@kEGDK?O1rZb4vi~%>_>)zkI)7+ zUb{;1NtNLdY}lT-0yxKz7S^rV{*7`TNAcf)fs6o*7n54*HN7 z!ddS&t(dfzSQ-t~esU$!>`c%}e2%2suN)Rrv7vZaMLpDA8hEgq#9}jORqHgUUt4o6 zX4aN|*Q-E2{;1WfxDpAEz0!Jo2=xtq#+Y7x4mO1(OKo{@)1Cgir)nE#@Z-IzBO_s| z_p4*sXKvmyCWQks3|dv#&s6L7FGYGR|U`C+%wvr;ElP#fC=)u(n> zIrkXr=2R?~gEE6OoRPqdy-s;C{8hX3#FKr<`r?&|0ajmaa-u_asg{*WWHxeYT!cZI z|8E*L<$M5cswUQ^-wsO^?bHXP36R*WD(dJ1B8;a1h=W7h4ez((=0!vIQ&ec!Fww0}r>xG* z`7vM;`Fl}_EP-{ToX*4e(l4sl3)l%gSb{fWGQ9*;ym=8vClTkRzorw=%6`eivi+ct zah9Z#ijU#tma~`6GVjv-Fk=UrCX;3(tra88VcQqez)2{i(yj$obf8JAX-j@SKH*lI zB9m>R*u#PSl1LrauCR> ziaErhMUk_g!CYuTkZ7TzE`OmIFaoKqi84kYl#ky)$VYetx^ppDAYL6$`71;;86R`C$qN`UIv%q4 zTUWj^zhlF~KHQX?ID)Qo;@)96G#1sJG%>1b!ya-9zofjBG@&CL`7fkp+^@gE5NYOA z4c8w{5Did`oLIvoaCFJWF`(Up1~{OkPGP!~vH8lu;xX8zwPc6w`@vy$kVP|xO&~~p zO<*P1<;gZ>8^AZ>k4BS-yiLICc<@S1aKn)al|%_uZ`DUpSQy?ekXu9hVT!5#AqjZNVQZ7|3bDZ{9lHmdV|YC!3pk(*(AkPFDBw6QP4GrDzM)Tp z*M>WT!^k)L-hHccwU`nGIo?mJgiz*iQG>CF_TUF(V9|@Igw>ab;rh3#kZ>Hd5Kd(M z2=Lg7P(kBmlx8!zV7eahK_kh<`NS5%7=f|zatV*$l}`d{Tf5r?qX>u=GHCo;dA1j?rZORReKr@CugmAs_fQ@L(3=Ytik z&H^l(Ilgx9;wo!?Fs$#oE?<34YPJm>e9Tyj9rw+A3Yv+j0E*LGm0Yyd-J9!mOeY8h zR09RXB#G5jv2S+4L$^$&)Q^weNNxCvG6L2kYPo<1KDnnox3mLqN@CJga>EJ>s@sX2 z0PTmHmx1+g`7W)E$XADhF)Td=0cpJ%YfBgbn|8osnru$zOm`8Ap*GQc%Zb#6{9E^K z4rbK3h?U4P^m#ZyKDB0Km|9c2JgNG&6dx@YBaoV zQOevaSz5A2r@)C|XO*U>7v@%Z8m1Dpxu3Koy%7K6*M}g6>(z3QWS+`YR zn}gt*^D3P)%Vv@*ELh$lh@C;H4>p87WE@GOV5(P1Eo0wvKbls1VTG8Y>C$wP0QuLO zX8D|tZA9z$lAvH>JzK)q;U#b*2)Q6~2K9J(J#ai3EHG=7TOAY*p3(^NFeoPadI;7y zvH{#Q2u}dIL?I*}1{x%;l4pFBxqK81RC`by8y;&Q79kCF7gD{ce1#YcoT9uZzR6JN z5|+k}wP^zc8XaY6_xzw4G?}{If-Ge$C#Wb1Df$mAAv?BhUU@1En415Cq)Kok9h3iH z66Z1ho}i>vHzXnkff$^OA@%mXR(ua*Bi1Qira^;sWDTqG3U7owjp9%@%uHy>6c zsk>FYnihPr=^j7->}XU?tHieV7<6Y>FyIX?+?ww;Zk3PxOc~cY6G1^Famh#kX_3B< z6voLn;rE31OyvKAzo6diyjyAI`fKUr-?BUJ%cQbj<6fJ#XQlAVqyUphi zNm}}l!i={)P*SUOv1a$??oYcU?oC?u_zI9fi)i-YEiLvm5I>D!ZFKKwNPv8HbQ_$ct&pf_SPc7whW554?d=A5T2u82#pXS;!VVRnr zSOQ0{>-?f{Z0j+dt=y+w>DZK^D9?VRh$RG*-jxgNcC+Vsd9wnxi7uo)OtqASrktmN|-pBeVS z7z0`;Lru*PS*EDAv~t1Pwn9FHlHT=v0egAbmF`N~?H?A_k<7hNZa3{)s34cQL{wQL zDaN-gUsPJUA@T5&%7HjjEX=vmrfWXtogwCnisQ*HyL@{pjdpzuMJ+2(f`t=YG(CzT zW-2Cl(2=1Cqm}(zMmc@w1YWdev;Gy{)hH_t{~ft@Q7udsoyuy&aGz~*3V}{yl$?3; zn(AV6sPtH~1s0@A)Zuwncf#qj%8sQtJCKTxBk}Jp%?|aUPtW^BVnV@`Sd=j{*Vilm;N1r&ei0-SNlw z$(ky)Cz0TsSibe=a)DWVwn03PQ#uG%W#L}9AYAcsX2Z!m8C))d!2Y*>obS`uLBvxH zW+PD`AGuj4y@#c~oy_kGIhIvXhCskiM*o;HC8jH#C`;ISRTp{(>ooHZRwCBMvwq69 z+O+_w2SCD8U!yel_SD4ZtT|2%N71#Qm8EYWeWBWWn;9@^G`UP~H7Wgi7)bv_N?|jc z)4CuefE(3Gg`n=&hjCL7LwP#?i0MZmAMXyEO+FmZec8 z!>$MBi840;bKxzp>!8#liD}p)NvJRsNW^GNfL~(QfO1grQt^`UfM4XYk-EDn8P^rCu$B^ zb02&g#*|RYh{c6k2t56~_j~=&cr+?A^etRHkew|yJ6VrjZ7Tpwz&q3vBjYn8U0PYb z(5LooQ$6ya59~=K;;y-(MD0f{*T2Rcov4!l)HfdVX$_E>&kL>wNaR?5Le^O4UU=~p zCk4OuWX#VbvuSRe2l!-wRw3Z*C+o4TJB829^hC@1^SW*afuB>A!6MN06l1V40F^JP z=501r(QCg=*fbihWncGN^+MxXc?tLLu+gWBN2_ItpOzJ3OFE*;((6=|^pbGRQn+5; zvW-LyYD{Y3lMVX5>O~R1%2FK;I!X%z`&7ZFM*#zUlLdcx`8X$%8ezCON(*Dwc^HP& zI&D)CtfAF+JYkA6_Q&|*$)k^_m(bq<9YVt7uOH0b2fw2HggJl>bJ$tyrKPY^J)i^+ zl?MCBed2*^;ZBpMf*NpVL682ysZ+tO1lU~KmEzB`3<@*)v=?(a64B;6GrNt^ExSG@ zwWcE5Zm>u0h$heFkA8?nJQK`5=%>-|sFKYAZ#>(R=vpe6-kJLpBD2h3{(5y2-vlg2 zK9MCA7M1;jv9L-8bjmD85B6EIjZ2<{V_VJ##5?INAAhA^c(d1cd$H{g@0Ma!E&*$f zA-Vs7N0@*s1y1AL=<(i(<==;oP!TEbP%*E(2#hMtECDup&d3WSRzwmk z3^6RhEJ>UhFGoV8>?F!iZzldoUS{MAr##;ki=zje^o+A*cLa(Uh*#m9=jbG3wJ2CW zg+N5Ft-N zHg+w*;|oh1TOvAiOK8rge-y6N+Kh9DCu1#+5Hj}wHsEmmM`^Z3wD|&ZO?Q^CntyPh zqYUuKkjnGNNknyR(T z{#^{fckMPoA8Elr`!te~Xcy2)?4Z*2aOExZ;(13h%K+oP&uSnp&^Cbq2Q30h<{Fx( zct(1wiPED>QDGE{Qqj7lpo#nzI)$`Z65d_Tu-yGCPsV7^-XK=O;d1?(l;KXn#YR}8 z(cVZ{m!FZ%qdAMKg!7U~G${}b4=F@z*g-)l+Hz9q@Ej(>Btl1a$pM^F$Zrw*S`OST zA~>&F;*areA{rFl6_pgQDGA0LQ>VrwGjY3y|DL$ojRH0~$U~N?8KpA({8nCd9f|Gl z7@e~&aNe^^{)k>!Deu#AtLUG$3$=N zL}c|OEBwy~fJY8VrcyOW?J~1d_plWNbJL5@feWDO7mDOu5tcHxeOm;(70vU-&}u)_ zr@>oSU79r8MV)7mlAPXr+rwd%bpyp#wr)SS)V^KkfF@4^)=iUoJDIR;l0b=%-4Xlp zPqw9qNXK}iUC6x%s!s`wsJO1=oZcQZl;Qe_9`>+Uvl6C1!y2ZAYWThFo9d4z#CJ+l z(F*YXe;cx>w`<#7g_i3L9S_dp)bP}Wmk3wUy-s*tFi1MWzdp3Oz>;x=$o4iA zr4VSwnvNHg(P974?d)-7%(vBeFtv(3C=hRIv&WbibxY>i%`Ny+q^gzdnZQbOnmgl? zS>$`m-u8INDwmr+xKmKMR0q28SbE;SlPE%Vu$74dK2<-d<=ErC8}9u4r7f@$gV4iq z1qQ~!vZeC&`R&L-29uuJ%vQsXo$?CT8LG5CP8+eYCQ-Ov+D}k+v}a)~LBVo<>9$W7SJ*UzU2!f^J( zVqDTKA<)Ajn{n$$U(`Fq?#Qx!Ao@I9N~(Ux-w_A?7J~I;&HCcDmSm^=)3POrikgz7 z-gHr5DYeho%wfK26evJ*##E3Lg~JE?Y4PLtXiO*_w#G`MJ$}zPb6Tf$;@C0)6XcRB zOo4zI?I;#iaV#dTmxxr%nNM%RWq(yK8L_@CO5$mn*%vO0aaC@0EiOb+RkjQENx`@X zvG(XCbPJ+aI4qvH2)F|(8S2lY)1nS*nH?qc#`qN-X&oR!0Xj`e)wMv;^Ht26$ zfMrS@MJQ3h!_&Whe6RtJT2<@OnM;PXv6NJLS@E60>DlPc?338!lT$wlE{~F3v~Ykw z@AeFcQhxR?bWH>m5_|_Qn#$EM=4L&pxiYPGl!cc@<#mzl=2l_dO{}|^hl-2l)Ps82 zM{|8-cEh~OenQjIE`h3oE#|jNRN0h6C%x;X==QwNzI3gKrtvYTnP=}uh7C3F`-L7Y zVy#)S)W7vf^UW;jIg-XgfzUL~ufy>*&^q)$EAiQiBrIe;;2|G@uFcmIE?q z96VB0Sq2$_00HzLvYf1>I;bi7-%&Fh45-B|Kol8N!8^<9x`BbA4F9{pQy5SP!NADC z8^=}@U+uv1`3Fy=)-e9exSdccw=%pwi_F7wf z!$Jzd(BQltgw}h@q>CD)w+Lh25_4t0`SRrsKPD6>O)`wZ*Dk-%pXnP20fSv|eu@~! zEt-8^xn_?&ZX)|D#|rK5FF57TTPNe8kN7#PTv0l}9aI!srLC4_%7#K6bVMGyu!$^& z6WzaRa-nm7)Tcbaz7W2g!n^^YdL z^TVki{62a3AtFr}E?Yn?8~%&@b8_D9%0$P>b-`O2aP;|N4K{Bq^igb~YVrbh8ve%v zOXVScuEM5crKdhq%)hq)^Gi%9yz}|kcvo&^A9#n&r)!|~P^m&=bdMhR)~;5C`+TRK zpGA|FMW55)5B}V@7+afv^^LlyD8(DIiDAh!qa=!PNGH2@F$d?=PZ)wvtY8o z(SWI~8cV@un+PZPDlRl9sRvU-h_Oz&d`+JSMw-PbEjcLq_w3kibI`g_2S1aMAoe34 z;nK1`Q|#28?arliY9ToP;#{&tfT0GjEAw60LCtpv=vU0IPd^?qbY1>Dl1=4mtYI*; z0zEI;0P&CXWFQ-u(`zeAuHE5VfS-?F*cc0^ir+Lq$LY^ATY&2OyZ`&t%{Vh%oJ^(} z5})*0tJMbDP#2zi%6jAcv0ILfGEpQ=Ec84`DO7el9+NCw8D1s4Ud|(i8|2#;pc#)Hcoe~ z(YgL5g`4WGsyIOI6?v5>vn%npdZB=g@tPE~6PL;I4(cJ-ROhGAigwP@4FszkFuj}V~rybbt51^->k}(%_^#>2&ks^=F z%gS%rz<=0c*fi003*+Aa@+34V#S+UK6T5>fW1;F|G{`PAy`St1X5PHbBFfYlDV7-PyCjqOtZ^dR!1vOg}&Bja@fYc2VB&t$o!P7den zQx78U3qTvQ6mppg6=c;y*zuVDkL|Cz z^$43>n0>aG&7Y61-7DR#6w9VEj~GbSyU~Bu@kSU)_RjT?%a#{nlSh1=s6Q(B)aJ|z zkUH%+?msw9w^;upydOO7U2?R|rzVdqiJvt}2$y{QAnY4yHRCRGOW-gSn zgU*luz!Hb(V3$b5_fM;7O((*((0_&{p1a`pHC#hWcyn7)q`PjGOBbWhRzA!_k%F3k z!If9~w&~F-Ln{z+7sZm}#`p`gTuT)BQ?^35S`l{#gjMAm1{^utb zM9S}7x9LNyI8DJtgTE=k{$q+*k}E}Mp8GxzX{9&)>c4NI?I!ZesJ^XO7F(N|!~SCf zo-zYTOZSwOEyiM~N|9C{2kWKF&Vqbd(nUpsY)e;JQNq7KE_p6EG&g`C26m&BznCVH zR6I$lh>cue!6%(j2h}BKBcgJe4swHTJ%R9)t*DVvkcSN**-&L?T)L%Rew$mG4hMPf zplg8nPl2J8VHG^C+64M>hu9}w=d{w;Tkb+^B(Atj(NnIjfq9a5280)TfQL?`nH*QU zs%a|840Icc$gV%SK+b>TMP|RvY{6`01kK0tCugGIPGu4mE|u?(`TX`J3?8`gvH8rL z=&&9o?5ZES=3a}t8#D+&(!`MQgRJYB(~re})0U$p1BI$7nmxEQX>K$g$zc?8cr9>^qFWnU(<5ihxH z9IPd^TvfnRlqd1{x8dbZ@_|M!m0x9kaBO3kwGp*_ETi?i%S+~23$9BK2Z%TkgKH6+ z8o)uOsl}$1$*;82F6VeChkNqc=?7YMEq8YdCQ;Qf$K!J12@S%zn{IwRK?-jHEd1dN zM%WUFymB#@sdth~*rWM$RgEsoK-IR2rm?*~);hiWbk=q4G&8lp3LFgyuk*BWfH0Qe z{1PQ4M;Y($m4jQWfnZ@hcCV;^LL7^!#MmsGUb3Jdw{U{Tlt%b0`AyoCz0$d^MM%UE zwh~wUequf*`c5oBzTa~4h_`&(r+rWH+`Qd$qm2HaaToy>h&8RaQSErybgnuWYTeSw z)H8jjEyMu5-j3mR?}Uj&)?ZZ#J~K+s9bh{&?zaxNk4JTkq#J)QdjkaX}FUK@TX8Q6U0yig$ zjBUV~G*d%^Ysjom$|oPj{9&XZka?z}HLJ2~`LqGB;jH=lwBsh|5}tV)^3ZHM;5ryp>Rc~X`MJ5T&@>qSn?D~1L#Q3*+yDn5D}=S1-p6$=7J z8Jx9}uMNqFQH|K{Tz<=zkc1`SmVL0oGfH_ON9VJv_xP=>-oa(Odj$P&$C8^z1KjPc zw6p-7#~IKj;1f?c4G>v$NfXBi z-zKrj{56PBO>)E8p|WuQQhjzaeW$uvF${2dq|4z#wcFD6on7;gR#fZ0~t**i--d@th8%Yofa zYUckC^pv4iBN8F(P6#j8uo8^p2feGE@o0^JV z=X%%UVsU~&`m?xI#Kv6hop`lv!~R9_%_1kzc$H8eV^qdLn8&G5SSnyiP>oE+-zod2 zEobVhu(zz~Hv9#1?&0p*$n108O^;XRIfv_-xWavVS)`;^B#3xoc{SXupPo6|#wr!O zkp}h(b0|G?!yg#xWoXZ>_$s1+Q6l!1#cwi~QdeUc$X^>}+iD@vu^{X6_mQfrEzR0x;@zle2^ zF-D+kVL}GpV2qPoPibIAZ5ImbH|b=gwtD-N)}C+ms&HuS8?6~5n;B^r_cgL0SCW6% zx2WpkR)UVTWT++4YX8lm3}3k-VG7?(lwH8BlWxx zftPD1-}X^xL;2_AV)fpu(E^Rvj{LYO2Te48~ z?nP$j&BI?g4Znt`E4|Trnyz=*SAnc7qD`pArbTM+Q6;hTZ0El2w-oCIIqEt zYvp$3z_J&^$IJ8E5NN5|f?}7+)z(A&a>vW@mDfswVw6!9Ysy&$>Ube-A9$p*g(5SH-v?cR|RsM=X41HBfKE;E7>sez{FQu%qTn(+Re=2N? zt{|W@N)z{-igD#>ql)%}j@HFY^!Ek|e3c~%7>5uxvN08&e+Mwf8cHjWjf-bCR^(R1 zt#&VUNA)%`<1s!PWImJbcQZwg4v=5q;swa6ucZ)VWx<_#3aDK=IiyOJ4p@TyNgB1d zHS(CKKGpTww=bfK@!aMAJeE2J(?E_MmTl z_hW2;OQY!Hy#kiziY=siywFAC7ZO1dj0(V!{SU;0z3>s=A-c~rinm?)C{4TEW@CNb z&PvE*R;5ddxV@J9V&mS$4Ww?~Kp;~j1y@<)e)5lTmXG%@nO&{L9=^MO{j+T^^O_bm z#hlAlN`=$p4r)$3#BeivomR|PzhA9@jJX}Y_$_ik_Nm*y;0mX7eP?N?xPJpNQL=YN zk1;w1e=g5SJBE6=2JX#jYPTXaii{0#rBOHG+=;9j`m+W9nG60xlk%(XT0&gTiM^T* zIXC=}PGkJfXR_$O1_qfeOva$N9Ai{K$%POjj7hGKhGF+veQFg%mah2g{)hxm^7!B* zh7+sN*rUbRgH~OSwCd@&R`51kDjHB|G@$wJtVx*Sf|?#fr!qg*{{f0=s=L!a-2WS< znI6`4#)p!}@^fPO^|)cNgKJIYjfl0nv~pp+)cNV&=}xMYx#lkoUp-WS>+x2WI10kY zy2=&O7k#t^-bLHp^2!?BD@Ef*(k|ZUlvNTM#L7YcIYF3&U^n~K4(^y=QQ&Ley%pPt zqE{NDgqYY7#TPcZCl0o}_K!UlzF*~*2&V=7yrxm;)Bm(g7CSu{129-lFx?IPhz41m z$DWjSN2o7llTonSTcPoY$>pG=X`(Ko7@jWafN7gJ&ewS!!>`<5zX)KAXgjd{@hcwK z!Kr+np7F09`z5WM$C<#?wWPKZ)=*8K+cN4mgAe$U$?av6D&wX`D zrVF4W*U^dmt3sX%Es>ilQvG5c-r*FZcu_CEZ^l@-A_3^)CGB#Fr3K(X3_RV2D*!e6P629*w(ykFju_1Vh zE0&nf;@>C~n&gDfvqg{A^z*bGVhZ2A0IW21jD*@o;tdze!IM|H^l+x^4`6O$r^N#T z_FlDG>?W8)CTgUr#9f3W2k|-zs?CoJrRX8QH&t7lkJs>{R*uSt-`t%VOv)r;b@JpMk$NFaj5(x}D)XUTQLi2H(js^)!>QW8(PWl4F&j zd)U`vJkg?7W6^D~97O>?!V^4~Q>40KB1dvu5(|MKF5I}R(_bcOWLtWtyp@PP0~vQo zEi-6%fZ21&iGq#s%4`Dqm#O-mv!ZF*Z&E@`kazn|w4PDR1_OVPdopahN-8k>x9hFk zXYY-PM5BbaE+%RFXG3yH7ET~%+jX9;Yr|dsX_v4pQkUNTbx^#*FLzCPPkOjRj+`k%jnv+fTT{vl5bDtQ;u6pJ!jS|WImnFb zRaOPFX#XI8(Wc&Nh@trLNZ?m|W^^!1UwCVJ?T0kRa*6WwDQ78F>JX!71KMs~aMjW5 zSDq$fykqc0qYgp`>skaY^bUPN8@*w|!a=q`L{5!T7NaMn-=q78?|iG2=_Wd@-7OC) z8^I;Pwk&+*6!z-6Der@PMBCloJ@f_G8PO`lf$X?n;Swp??Ipu>9isT*O$Qz;^s5cr zx5*0lXuAS2)PDgzRU%O;Ysnf3G?QskzM6YJbgW5JcASyHuw0nXh9#t&x~fzN`qnl* zDHl!S2iFCi=+*}tjrDuGea+*xm9m3U-gHg<4K!ycuVyl0_DRs+^#xE8I<9D<)p)w) zYm1nos|IGr3{To^HXf5B@w9l9vB6g%8pMq8vP~+Xiw+uai&}C~n8JgC+tmtVhYMlSW*(kPcGFDN zKQgVJ`S>KFuA`N3#$qo@$G)|nXQqaY4}v}{`du;}e5-V2;(_g~JdSM1%4VGtB011n zI>b3NN?}sMp|bTf@a9jg)~(%xgGZziTRG-E{rzt1{crC8e}W>oXW4}Nh|r2)!cg{J z``mN7KFDw-MyYj7o)uzNUvkvQ7uE4J&8)2WaVV}5;p&vNusY?#fU%zK?3|6McV&}_Y<#FjMpV~I^+QvZ4WZWem z1b`fjRH@|@dnOOe?o3A7dl|Dmx~L%ARjE0y<1u>*H_h*hqptac^66bFT>8n4+(Cuu z8N>UMaZ3fRw(S=pjWlNPbAxv@PU3DOZJb}CPBSA0m+!C9uN5o4C(dLsUgJo&U;#Uc zQij_(a`Bc9buctx5<^mVuo@Y*cQb=mvI79yHug&~<#seHuq&3{=FifOs}?jK-a;`5 zDmji>l6P2j9;m=x_fDqB$-4~Ljc#L`Z$O$~+L;l-TDTk~#jT9jQ0%cSl_f82+-D6L z7?WeZalY5o3ExywzM3XFhcA+jyceBAbUJO&lw|X)1R0*?;YXajN49#*T!uIA3P%P% z68Mu~j3(C7Qr_tI29z$#CtZx&w6tdTifAW|xCCo1(#4OCN^y48;MmP`Sp_j-XO$n3 zG~Q=&VdMrZ89azIW$AG2$9crbSZp}p&J#1=3rrqx?>8{bV_{DXfj3nvx9Bo_8SyUn zQKVem>uoH3A-EhKn?1-qXAW(x?$uudO(gizxjWmWELXU0zVxdGceKsuBxbUw9ukj? zB7?D7(*lvn@Gop~N*G??&C*H)D3|x~>Y9Hyca4#)khF>_QFdh&CfyHVc9)Z7kZ@JxIFX%+p=Q_qW!QZ$ZW1L4$?e!E+2YV<3OC zDr2CJjw5=}gRps8jLL_wPZR^W-tzVnKX1-`wu@b|Wd}_>fIZ%q0~YYB#>GgT@JCpE z50aoftldK0?6b?JOI$L7sU8y(B9PdKWp^C3^6?HZo2+w1myzxqDgRWmv#{a8+J;oN z^FXkFqV&|#;P!5K9N-7V)BN4=2QOlMJr^w2YS}hTj=tsfCH)706?#6}PxVdYx_5q5 zE|#M$E7oi-ybP_Zw?RZDyVEzT1q;xiCI=-Gw4^YkQwX|Xc#6a~<18$%d11!L#nbxU=%W-Nn29U`l8z)0^iN;G4o-by^0paj+u(2DaoN1Ey(Y5W zOrAbCY(Nsfq=+_jqy_H!)>4bclCCJ#->+2G0|#F6J0_zZrtHrFYvG*RxHJehMk$8W z(e0iC*D5p$^`E0SVTS#RBSwRb@vS#+?hO&9lOelRie>0l&+YnzzRBDfXp!`qfn}3)M!G^eN$XuUEsiw zZMNlbe4G|0xWriF>ai4c&KgU<92Acqy!2Q9Ncg(neN$qnDu?~C)VV3-xiwpE)M z@tX1p&*=wc$K(FR(_}Zx_RT4N8bp6;84*Jy8?Y*eN_Oi|(;Re(=+co>9qd|@ zu?N{4R#KrO&C&=fqb~?}V(D9@9oR=14f0GU4=TZU4sjF$E%rfOtF~AEb+y()@r&2n zL6$liHNrmn$_>EYYn@42f|_r~zmy-$C!DajQ{~bHgnf(CW%S7({}`n5E{M}567p7; z9hAjgB49+c5rExt89@_l>6o`??9=ctD#a+*;2nGQX(uE zP*Sl-m6FZCwP8^z<}aPEZ^^_7P07r&t_|#uI=$R< z@GG&WdICInj6Jp$YbN|q&`J*wG2qrEBCTWzsIeHp^y`JifFvzrx72d`;^RB#p7=dq z?s+W8`N`gKOX(=HAiuOmr!#{$cR$l@mFw>-AnB_bmxNvj;W>r>O6o|$AwOF*S4RPT zEK#46O&9S|*+9J`R*JRC!QXZ0SjY0wZJ01fq2IPK}psbAZu+TFRc$GdYF6UI*CG^4dhiF z5=IN9=bG`sk8?gF8|I1ej*xYmiWjbzPQG29ZfWbF#YTRR(aZ;b38{kEvRh-UT75?T z?)o?VtiC#R`mU`s%y!$~YxV8hUm}{QADIAYLN7uCj#gWS#uuf9KISpI<=RrK%<6+F z_X&x4f_`tM(mJoH1C)IF*9vyl71tq}TKO-E>e?~v|N9nTw-UEw(_Ec?1Nfg$JB(H{4$g;h!*i%@7~botN4@ypfyt2E)g^IYbDQ>PCqvgX$9 zo0UgIfa>9^?4S+<+dFM#U@?zl>4u)xM(^o!X2gN{RK@-fMJ0!z_KJ z$|kZleHB%pV$j#C;pcP98cNe6D^_{#zn{7w^|Dm5yr=&fl1#t}-&UqU=)~}E7HQj* z*K>-xi^HKcAobined=5McAMf@+|A#fV^dFqxME1eu8S8#K4TUM+9o3Dt+^>8CAu4I zG>>L|&!6QMBooeBGCbeJHqTn)d|1h5TGL}af>X=tTFDwqoLg~c{?a0+irMSa$AOa& zNJFu0a)2~TN@+)N`UA=l#{XUPf#whB6iHf5pZuL?9ZvgcRxf)`hpWh+NDEbi{%uwT`+ARwSRJiJN4}MuBw0w!Zgt&1McAPwj+v9JD zlIUrWDSM?iAAgwLh!24eekj{$r4=^*+)VRkmRICifpm1vc$EB?O3_uGajYE0yfTs~ zx~!6rdVF>dd_y^yb2^fIe7<}oWa_xQq|x%Q+9!!7*tK*0dh8tK7&q~1owhtn2@PCW z9)1BoWob49j2-ZI6%kdLC?&f5{!yJLrjHXV0PRS>a#aRx7ESI^E^K|S;!NO%IjU>W zci`ivq|xWX<*K197bm|$Q#wEfTU&#*v&VFWMmki<@OQpj0@e0D~T=6|s3ue5JL>IIW=}*8m zyfysQKRuNbs(WvA*pla)1W&N8qbInLcQMylzb=pZ31mt+VZ6UDaA-#lYVyl;2Uig9 zOrE|Z>Um=7zIWi%3@KbAV-734H;hdnJ#VQx4$))u@p=XlRTz2(Ik-|~Hg9Gy#27eDx+yCx5E)GIn>&P3| z6W>bORLh5tS%}v$K`9Y6@6^KR`-fRLY2sYKRq1Ux|HM{;CJ5cp*a6Sjy|VOy(>p3- zUj6Ck9gJi7>3)sN?wbCv8%cE%^m2X!xAU; zri4B+;`V{+lQl&I#~A(-8@A-Ee6?<7Z3e@~;)>L@pTHuQ$FOOQfs#P=8X7)2m_JT% zqSy>}`1rr8H;90Uca4%;;MN$~%cv2{u*e#;O=@oDH~8%)sip3SpEY?tno>s%!yFkZ zbkm<0_6$=ue~pyi%c)W7bJ$Y;5O+%>`hZ-Cu7l$vp!aW-v68FcLY1`)ceOh&H*mg{ ztB`mU@*h(!H+W+p@3-Bizdy0wKg!&?QCfuJ#KtbTgB-C5&3`kAGN>~LS3R|tolNyA z^^wct(33rVP5f<$H=yy0^aM}J*h5);zEt7uW`4+< zFMsY@jDxc7(fTQa9)~CN%^QuU;r^O_Xe)3&CfKZ z$Wk}2N6wT?laW|r!RHM3Avc{g7_jOjU{o;zRo)KHyqXF+JYW3e(QgNX4~PT88x-Tb%FI6ee4V>nRvVg9_F%C@u{vT3*2OX81e1_OmJh*uo;(Af zOXQEW)I9S2G--H;MPl-P<{_6qn0)h|;?|CloUyL5&?5}Es6WSazSZk3Uu*9M(xSCNC;jp7kO=O=6hnWrBPeS z&?;iJQa|?ZIXud(i#BR@;b=E>2GLyTCHwgV=r{0LDvaj<8+)%Tr}#S7)Gs=?bxgjj zMq|K6>&z#4>C|wGDMvN}uX`|jItSMM@9agdd zkE!m766*Lh{aU#3^H#26w=cdV+|&{TSn-P&NURg#BqC?n{=2F~O>B2+hof23BiUy- zd#e*B)RytQUv9(~X%*ig3tcqFMsGJ~5ZcPJUtYEMqy38IeCKaT?wdrPO#6EnA?*E} z|Ds`4aht`vph@k<0aDzpQG?o&Jquv04T;>6eXiQHPGv(np1Py@71p+!ZQQHd=`{}ZuUm}zS&By^a6t10GUnl~!8-EO3~U2l~1$}88axsow6 z(_Ytn(s!nTcOBt*xC`a}seg%8hj*@1D12{OMIQQYz20YAn#@H3dVwMKiP8_rqcBMM-hn4ZRBz zZ)-6$*B-vwKgkV2>xZxH(Kk7uuJGtB%9{m3^wR@NhFXr6vKh+}_txJH1-#om^Wrvp zqeWcKYtlF#XNg%EJ3)ipn^aLG^aEW7(3xF*%E-%E8*Q^{ z$fjC-9&1OU4Qp-}RS$s#5W9r#@N^7yx#(ciL=2C0sg`k+>OJnSBOKi^4Lz8PH4_7C zed)eQA*Nl%%6*$ex0jAF_LXjNMmt($|05SQqi&e?=NM~Z$hnZV4UV0b@T4;njXVk} z`fI^}-T~if8N5U5Phmj7H9=uv@VMZADK&6ZU>WyGVN*!g-;pH9)n+Q>VGU~2(2+`O z<pja7j{5HddMnk4!Qw%Advp^=SN+*cx3yu-Gv6&k8 z>gVNs{;*s{9gj*>(e7Ubs=iYM5h}E|PitY1X^3-4ru1$6Lt5tW6X+V*7BojN7|~k^ z>ikRTeS*J>;y!0DAI*j){TIJK5uVYf37QQG-v}v6tPBs_Ysls_Ja~8jlI^Wt#pY+S z8KXspoJdeU`K|d(mMUL;Yd>-0s0xGPZk6OU51HplalFza^Slyvi0^0D(Y(q34rQM? z;st-3WlifFGX@dIDo*XOoWAnV*^$PmKS!)yA-iX)>G}&-tZZ9!YlSf*%ZfEB6b>g0 zPw<4H@2}uoRE%2`srna9`3p~fa1JUGND4yhaOY7E!s2GBu0-QD14AM@c9;we+XSMQSLec=8 zG8^Hl`JrMgXQXd=PuR+NtU8uM70h8}znoh!qNO9-P5z;%XXxL`^%TVle$MYm$0MnW8wkRxt`MY)z4*@ zvL*@R)z;T-Cu)&1>OI6^m2aEfQ|vAvtj1{sr20=wv2Ds3bXm4pP(C8}$>@R_kHXHK z6NsynM&Hz|L_=@=p{x#uI@%%K$CQwGg+|`#2HkRiY;B}=bgH50JYO2Ux-A%D=3v0O zpkM4kU#RLs0_SNhzA6SViSVEhXN6ydm({kLrpBh@<^DJ{|21DoYuS>c1ft@O+UFgd zgWu|6(371siRfOHopLQJ9);R)+N0*HX=+d|<{35Mpax}RFhVsJd9{Hhe|KglCxA99D99Ya;sLyj7I|3|*fbync(kWCeAC*qE;ureG`Uim{M zEkMkE%@&YK!&R0~41KWI>tg?)Qi%c_so`%-A(Qm}YKTsTE7_ZqHX3Jg8Lbbqy&Jo_ zSxM(8x!!`{;VY`V6NUR*UOk(*O#R!U84$=y6AOQI}F~Wd0Qbs4y@Ph!NH3SNU_C3-h}m%%_M!~iW(16?C^vyKhUcxyl8;_W!5*zl93(d0dZYWU}yDG$VRXT5Q*Z~U( z)s@N(#`vS-4a0TfGv@4))`yfMc`5yar41hD|7;x16TSeedH?sIuik&hcDcfIQ`pUC zHWO{~98WVQ_M$zkyGuwjc%>a>T;XmAm(L*#7vV_mYQb*qiT8#VcN)ZjrTY|9%bbu^ zRA8FN;3Bl=t~WdgIt$gi11?VZsju^Q+Wiewf3m*r%fEKk)mrd#)sK4ftfiDHB&;XZ z9mqdEpEH=Q5F^K1snfg}T#XL&ss*5$8$Z<0@|HJeI$cO4b}pfVZ5fo&C{16`eCX#Z zb49cSFD2D_vY_wLm3JYFk*Voo2H0jTZ#@shMBEBP=wAIh2qrdiP=O(*Ip-v97c%cj z{rry#3tfzegWG*Xxxy_7=VG+TPDs{m4(BVUm*&Se%+}qj9&63c16*i8M~E)I(4*g$ z$lrlntpA%eHiL86a?6t_G`W6j8qPWQX<{s3D;uujpRN&GE+L$h?g`HeumkhU27x!| z{zpI?!FZ>ahS!E>wYgpSn&5v%HirF2n9%A%l-Hr}B`Bx`=maJ}*�J^GVJmt+2k* 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 + + + + diff --git a/src/assets/icons/unloadicon.afdesign b/src/assets/icons/unloadicon.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..f4ed7fcfda57ed9502b694842a1d177945412069 GIT binary patch literal 42187 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 NIdtq`nIkdz{{V4-Vm1H( literal 0 HcmV?d00001 diff --git a/src/assets/icons/unloadicon.svg b/src/assets/icons/unloadicon.svg new file mode 100644 index 0000000..6b4c0d8 --- /dev/null +++ b/src/assets/icons/unloadicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/components/Auth/_RegisterPasskey.jsx.old b/src/components/Auth/_RegisterPasskey.jsx.old new file mode 100644 index 0000000..0e51c14 --- /dev/null +++ b/src/components/Auth/_RegisterPasskey.jsx.old @@ -0,0 +1,56 @@ +import React, { useState, useContext } from 'react' +import { useNavigate } from 'react-router-dom' +import { Button, Typography, Flex } from 'antd' +import { LockOutlined } from '@ant-design/icons' +import { AuthContext } from './AuthContext' + +import PassKeysIcon from '../Icons/PassKeysIcon' // Adjust the path if necessary + +import './Auth.css' +import AuthLayout from './AuthLayout' + +const { Text } = Typography + +const RegisterPasskey = () => { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState('') + const navigate = useNavigate() + const { registerPasskey } = useContext(AuthContext) + const [init, setInit] = useState(false) + + const handleRegisterPasskey = async (e) => { + const result = await registerPasskey(email, password) + if (result.successful === true) { + setTimeout(() => { + navigate('/dashboard/overview') + }, 500) + } else { + } + } + + return ( + + + +

Register a Passkey

+ + Please setup a passkey in order to continue. The passkey may use + another device for encryption. + +
+ +
+ ) +} + +export default RegisterPasskey diff --git a/src/components/Dashboard/Inventory/Spools.jsx b/src/components/Dashboard/Inventory/Spools.jsx new file mode 100644 index 0000000..a982896 --- /dev/null +++ b/src/components/Dashboard/Inventory/Spools.jsx @@ -0,0 +1,204 @@ +import React, { useEffect, useState, useContext } from 'react' +import axios from 'axios' +import moment from 'moment' + +import { + Table, + Button, + Flex, + Space, + Modal, + Drawer, + message, + Dropdown +} from 'antd' +import { EditOutlined, LoadingOutlined, PlusOutlined } from '@ant-design/icons' + +import { AuthContext } from '../../Auth/AuthContext' + +import NewSpool from './Spools/NewSpool.jsx' +import EditSpool from './Spools/EditSpool.jsx' + +const Spools = () => { + const [messageApi, contextHolder] = message.useMessage() + + const [spoolsData, setSpoolsData] = useState([]) + + const [pagination] = useState({ + current: 1, + pageSize: 10, + total: 0 + }) + + const [newSpoolOpen, setNewSpoolOpen] = useState(false) + const [loading, setLoading] = useState(true) + + const [editSpoolOpen, setEditSpoolOpen] = useState(false) + const [editSpool, setEditSpool] = useState(null) + + const { token } = useContext(AuthContext) + + const fetchSpoolsData = async () => { + try { + const response = await axios.get('http://localhost:8080/spools', { + params: { + page: 1, + limit: 25 + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + }) + setSpoolsData(response.data) + setLoading(false) + } catch (err) { + messageApi.info(err) + } + } + + useEffect(() => { + // Fetch initial data + //fetchSpoolsData() + }, [token]) + + // Column definitions + const columns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name' + }, + { + title: 'Filament', + dataIndex: 'filament', + key: 'filament', + render: (filament) => { + return filament?.name || 'N/A' + } + }, + { + title: 'Current Weight', + dataIndex: 'currentWeight', + key: 'currentWeight', + render: (weight) => { + return weight ? weight + 'g' : 'N/A' + } + }, + { + title: 'Barcode', + dataIndex: 'barcode', + key: 'barcode' + }, + { + title: 'Updated At', + dataIndex: 'updatedat', + key: 'updatedAt', + render: (updatedAt) => { + if (updatedAt !== null) { + const formattedDate = moment(updatedAt.$date).format( + 'YYYY-MM-DD HH:mm:ss' + ) + return {formattedDate} + } else { + return 'n/a' + } + } + }, + { + title: 'Actions', + key: 'operation', + fixed: 'right', + width: 100, + render: (text, record) => { + return ( + + + + + }} + /> + + { + setNewSpoolOpen(false) + }} + > + { + setNewSpoolOpen(false) + fetchSpoolsData() + }} + reset={newSpoolOpen} + /> + + { + setEditSpoolOpen(false) + }} + > + {editSpool} + + + ) +} + +export default Spools diff --git a/src/components/Dashboard/Inventory/Spools/EditSpool.jsx b/src/components/Dashboard/Inventory/Spools/EditSpool.jsx new file mode 100644 index 0000000..cf3d5fd --- /dev/null +++ b/src/components/Dashboard/Inventory/Spools/EditSpool.jsx @@ -0,0 +1,450 @@ +import PropTypes from 'prop-types' +import React, { useState, useEffect } from 'react' +import axios from 'axios' +import { + Form, + Input, + InputNumber, + Button, + message, + Typography, + Select, + Flex, + Steps, + Divider, + ColorPicker, + Upload, + Descriptions, + Badge +} from 'antd' +import { UploadOutlined, LinkOutlined } from '@ant-design/icons' + +const { Text } = Typography + +const EditSpool = ({ id, onOk }) => { + const [messageApi, contextHolder] = message.useMessage() + const [filaments, setFilaments] = useState([]) + + const [editSpoolLoading, setEditSpoolLoading] = useState(false) + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + + const [editSpoolForm] = Form.useForm() + const [editSpoolFormValues, setEditSpoolFormValues] = useState(null) + + const [imageList, setImageList] = useState([]) + + const editSpoolFormUpdateValues = Form.useWatch([], editSpoolForm) + + useEffect(() => { + const fetchFilaments = async () => { + try { + const response = await axios.get('http://localhost:8080/filaments', { + withCredentials: true + }) + setFilaments(response.data) + } catch (error) { + messageApi.error('Error fetching filaments: ' + error.message) + } + } + fetchFilaments() + }, [messageApi]) + + React.useEffect(() => { + editSpoolForm + .validateFields({ + validateOnly: true + }) + .then(() => setNextEnabled(true)) + .catch(() => setNextEnabled(false)) + }, [editSpoolForm, editSpoolFormUpdateValues]) + + useEffect(() => { + const fetchSpoolData = async () => { + try { + const response = await axios.get(`http://localhost:8080/spools/${id}`, { + withCredentials: true + }) + const spoolData = response.data + setEditSpoolFormValues(spoolData) + editSpoolForm.setFieldsValue(spoolData) + if (spoolData.image) { + setImageList([ + { + uid: '-1', + name: 'Spool Image', + status: 'done', + url: spoolData.image + } + ]) + } + } catch (error) { + messageApi.error('Error fetching spool data: ' + error.message) + } + } + + fetchSpoolData() + }, [id, editSpoolForm, messageApi]) + + const summaryItems = [ + { + key: 'name', + label: 'Name', + children: editSpoolFormValues?.name + }, + { + key: 'brand', + label: 'Brand', + children: editSpoolFormValues?.brand + }, + { + key: 'type', + label: 'Material', + children: editSpoolFormValues?.type + }, + { + key: 'price', + label: 'Price', + children: '£' + editSpoolFormValues?.price + ' per kg' + }, + { + key: 'color', + label: 'Colour', + children: ( + + ) + }, + { + key: 'diameter', + label: 'Diameter', + children: editSpoolFormValues?.diameter + 'mm' + }, + { + key: 'density', + label: 'Density', + children: editSpoolFormValues?.diameter + 'g/cm³' + }, + { + key: 'image', + label: 'Image', + children: editSpoolFormValues?.image ? ( + + ) : null + }, + { + key: 'url', + label: 'URL', + children: editSpoolFormValues?.url + }, + { + key: 'barcode', + label: 'Barcode', + children: editSpoolFormValues?.barcode + }, + { + key: 'filament', + label: 'Filament', + children: editSpoolFormValues?.filament?.name || 'N/A' + }, + { + key: 'currentWeight', + label: 'Current Weight', + children: editSpoolFormValues?.currentWeight + 'g' + } + ] + + const handleEditSpool = async () => { + setEditSpoolLoading(true) + try { + await axios.put( + `http://localhost:8080/spools/${id}`, + editSpoolFormValues, + { + withCredentials: true + } + ) + messageApi.success('Spool updated successfully.') + onOk() + } catch (error) { + messageApi.error('Error updating spool: ' + error.message) + } finally { + setEditSpoolLoading(false) + } + } + + const getBase64 = (file) => { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.readAsDataURL(file) + reader.onload = () => resolve(reader.result) + reader.onerror = (error) => reject(error) + }) + } + + const handleImageUpload = async ({ file, fileList }) => { + if (fileList.length === 0) { + setImageList(fileList) + editSpoolForm.setFieldsValue({ image: '' }) + return + } + const base64 = await getBase64(file) + setEditSpoolFormValues((prevValues) => ({ + ...prevValues, + image: base64 + })) + fileList[0].name = 'Spool Image' + setImageList(fileList) + editSpoolForm.setFieldsValue({ image: base64 }) + } + + const steps = [ + { + title: 'Required', + key: 'required', + content: ( + <> + + Required information: + + + + + + + + + + + + + { + if (!value) return '£' + return `£${value}` + }} + step={0.01} + style={{ width: '100%' }} + addonAfter='per kg' + /> + + + + + + + + + + + + + + + ) + }, + { + title: 'Optional', + key: 'optional', + content: ( + <> + + + + + + + + + + } + placeholder='https://example.com' + /> + + + ) + }, + { + title: 'Summary', + key: 'summary', + content: ( + <> + + Please review the information: + + + + ) + } + ] + + return ( + <> + {contextHolder} +
{ + setEditSpoolFormValues(allValues) + }} + > + { + setCurrentStep(current) + }} + /> + + {steps[currentStep].content} + + + {currentStep > 0 && ( + + )} + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + + + ) +} + +EditSpool.propTypes = { + id: PropTypes.string.isRequired, + onOk: PropTypes.func.isRequired +} + +export default EditSpool diff --git a/src/components/Dashboard/Inventory/Spools/NewSpool.jsx b/src/components/Dashboard/Inventory/Spools/NewSpool.jsx new file mode 100644 index 0000000..1456d91 --- /dev/null +++ b/src/components/Dashboard/Inventory/Spools/NewSpool.jsx @@ -0,0 +1,443 @@ +import PropTypes from 'prop-types' +import React, { useState, useEffect } from 'react' +import axios from 'axios' +import { + Form, + Input, + InputNumber, + Button, + message, + Typography, + Select, + Flex, + Steps, + Divider, + ColorPicker, + Upload, + Descriptions, + Badge +} from 'antd' +import { UploadOutlined, LinkOutlined } from '@ant-design/icons' + +const { Text } = Typography + +const initialNewSpoolForm = { + name: '', + brand: '', + type: '', + price: 0, + color: '#FFFFFF', + diameter: '1.75', + image: null, + url: '', + barcode: '', + filament: null, + currentWeight: 0 +} + +const NewSpool = ({ onOk, reset }) => { + const [messageApi, contextHolder] = message.useMessage() + const [filaments, setFilaments] = useState([]) + + const [newSpoolLoading, setNewSpoolLoading] = useState(false) + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + + const [newSpoolForm] = Form.useForm() + const [newSpoolFormValues, setNewSpoolFormValues] = + useState(initialNewSpoolForm) + + const [imageList, setImageList] = useState([]) + + const newSpoolFormUpdateValues = Form.useWatch([], newSpoolForm) + + useEffect(() => { + const fetchFilaments = async () => { + try { + const response = await axios.get('http://localhost:8080/filaments', { + withCredentials: true + }) + setFilaments(response.data) + } catch (error) { + messageApi.error('Error fetching filaments: ' + error.message) + } + } + fetchFilaments() + }, [messageApi]) + + React.useEffect(() => { + newSpoolForm + .validateFields({ + validateOnly: true + }) + .then(() => setNextEnabled(true)) + .catch(() => setNextEnabled(false)) + }, [newSpoolForm, newSpoolFormUpdateValues]) + + const summaryItems = [ + { + key: 'name', + label: 'Name', + children: newSpoolFormValues.name + }, + { + key: 'brand', + label: 'Brand', + children: newSpoolFormValues.brand + }, + { + key: 'type', + label: 'Material', + children: newSpoolFormValues.type + }, + { + key: 'price', + label: 'Price', + children: '£' + newSpoolFormValues.price + ' per kg' + }, + { + key: 'color', + label: 'Colour', + children: ( + + ) + }, + { + key: 'diameter', + label: 'Diameter', + children: newSpoolFormValues.diameter + 'mm' + }, + { + key: 'density', + label: 'Density', + children: newSpoolFormValues.diameter + 'g/cm³' + }, + { + key: 'image', + label: 'Image', + children: ( + + ) + }, + { + key: 'url', + label: 'URL', + children: newSpoolFormValues.url + }, + { + key: 'barcode', + label: 'Barcode', + children: newSpoolFormValues.barcode + }, + { + key: 'filament', + label: 'Filament', + children: newSpoolFormValues.filament?.name || 'N/A' + }, + { + key: 'currentWeight', + label: 'Current Weight', + children: newSpoolFormValues.currentWeight + 'g' + } + ] + + React.useEffect(() => { + if (reset) { + newSpoolForm.resetFields() + } + }, [reset, newSpoolForm]) + + const handleNewSpool = async () => { + setNewSpoolLoading(true) + try { + await axios.post(`http://localhost:8080/spools`, newSpoolFormValues, { + withCredentials: true + }) + messageApi.success('New spool created successfully.') + onOk() + } catch (error) { + messageApi.error('Error creating new spool: ' + error.message) + } finally { + setNewSpoolLoading(false) + } + } + + const getBase64 = (file) => { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.readAsDataURL(file) + reader.onload = () => resolve(reader.result) + reader.onerror = (error) => reject(error) + }) + } + + const handleImageUpload = async ({ file, fileList }) => { + if (fileList.length === 0) { + setImageList(fileList) + newSpoolForm.setFieldsValue({ image: '' }) + return + } + const base64 = await getBase64(file) + setNewSpoolFormValues((prevValues) => ({ + ...prevValues, + image: base64 + })) + fileList[0].name = 'Spool Image' + setImageList(fileList) + newSpoolForm.setFieldsValue({ image: base64 }) + } + + const steps = [ + { + title: 'Required', + key: 'required', + content: ( + <> + + Required information: + + + + + + + + + + + + + { + if (!value) return '£' + return `£${value}` + }} + step={0.01} + style={{ width: '100%' }} + addonAfter='per kg' + /> + + + + + + + + + + + + + + + ) + }, + { + title: 'Optional', + key: 'optional', + content: ( + <> + + Optional information: + + + + + + + + + + + } + placeholder='https://example.com' + /> + + + ) + }, + { + title: 'Summary', + key: 'summary', + content: ( + <> + + Please review the information: + + + + ) + } + ] + + return ( + <> + {contextHolder} +
{ + setNewSpoolFormValues(allValues) + }} + > + { + setCurrentStep(current) + }} + /> + + {steps[currentStep].content} + + + {currentStep > 0 && ( + + )} + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + + + ) +} + +NewSpool.propTypes = { + onOk: PropTypes.func.isRequired, + reset: PropTypes.bool.isRequired +} + +export default NewSpool diff --git a/src/components/Dashboard/Management/Filaments.jsx b/src/components/Dashboard/Management/Filaments.jsx new file mode 100644 index 0000000..6d53194 --- /dev/null +++ b/src/components/Dashboard/Management/Filaments.jsx @@ -0,0 +1,262 @@ +// src/filaments.js + +import React, { useEffect, useState, useContext, useCallback } from 'react' +import { useNavigate } from 'react-router-dom' +import axios from 'axios' +import moment from 'moment' + +import { + Table, + Badge, + Button, + Flex, + Space, + Modal, + message, + Dropdown +} from 'antd' +import { createStyles } from 'antd-style' +import { + LoadingOutlined, + PlusOutlined, + ReloadOutlined, + InfoCircleOutlined +} from '@ant-design/icons' + +import { AuthContext } from '../../Auth/AuthContext' + +import NewFilament from './Filaments/NewFilament' +import IdText from '../common/IdText' +import FilamentIcon from '../../Icons/FilamentIcon' + +const useStyle = createStyles(({ css, token }) => { + const { antCls } = token + return { + customTable: css` + ${antCls}-table { + ${antCls}-table-container { + ${antCls}-table-body, + ${antCls}-table-content { + scrollbar-width: thin; + scrollbar-color: #eaeaea transparent; + scrollbar-gutter: stable; + } + } + } + ` + } +}) + +const Filaments = () => { + const [messageApi, contextHolder] = message.useMessage() + const navigate = useNavigate() + const { styles } = useStyle() + + const [filamentsData, setFilamentsData] = useState([]) + + const [newFilamentOpen, setNewFilamentOpen] = useState(false) + //const [newFilament, setNewFilament] = useState(null) + + const [loading, setLoading] = useState(true) + + const { authenticated } = useContext(AuthContext) + + const fetchFilamentsData = useCallback(async () => { + try { + const response = await axios.get('http://localhost:8080/filaments', { + params: { + page: 1, + limit: 25 + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + }) + setFilamentsData(response.data) + setLoading(false) + //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count + } catch (err) { + messageApi.info(err) + } + }, [messageApi]) + + useEffect(() => { + // Fetch initial data + if (authenticated) { + fetchFilamentsData() + } + }, [authenticated, fetchFilamentsData]) + + const getFilamentActionItems = (id) => { + return { + items: [ + { + label: 'Info', + key: 'info', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'info') { + navigate(`/management/filaments/info?filamentId=${id}`) + } + } + } + } + + // Column definitions + const columns = [ + { + title: '', + dataIndex: '', + key: 'icon', + width: 40, + fixed: 'left', + render: () => + }, + { + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 200, + fixed: 'left' + }, + { + title: 'ID', + dataIndex: '_id', + key: 'id', + width: 165, + render: (text) => + }, + { + title: 'Vendor', + dataIndex: 'brand', + key: 'brand', + width: 200 + }, + { + title: 'Material', + dataIndex: 'type', + width: 90, + key: 'material' + }, + { + title: 'Price', + dataIndex: 'price', + width: 120, + key: 'price', + render: (price) => { + return '£' + price + ' per kg' + } + }, + { + title: 'Colour', + dataIndex: 'color', + key: 'color', + width: 120, + render: (color) => { + return + } + }, + { + title: 'Created At', + dataIndex: 'createdAt', + key: 'createdAt', + width: 180, + render: (createdAt) => { + if (createdAt) { + const formattedDate = moment(createdAt).format('YYYY-MM-DD HH:mm:ss') + return {formattedDate} + } else { + return 'n/a' + } + } + }, + { + title: 'Actions', + key: 'actions', + fixed: 'right', + width: 150, + render: (text, record) => { + return ( + + + + + ) + } + } + ] + + const actionItems = { + items: [ + { + label: 'New Filament', + key: 'newFilament', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + fetchFilamentsData() + } else if (key === 'newFilament') { + setNewFilamentOpen(true) + } + } + } + + return ( + <> + + {contextHolder} + + + + + +
}} + scroll={{ y: 'calc(100vh - 270px)' }} + /> + + { + setNewFilamentOpen(false) + }} + destroyOnClose + > + { + setNewFilamentOpen(false) + fetchFilamentsData() + }} + reset={newFilamentOpen} + /> + + + ) +} + +export default Filaments diff --git a/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx b/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx new file mode 100644 index 0000000..cf30b65 --- /dev/null +++ b/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx @@ -0,0 +1,445 @@ +import React, { useState, useEffect } from 'react' +import { useLocation } from 'react-router-dom' +import axios from 'axios' +import { + Descriptions, + Spin, + Space, + Button, + message, + Badge, + Typography, + Flex, + Form, + Input, + InputNumber, + ColorPicker, + Select +} from 'antd' +import { + LoadingOutlined, + ReloadOutlined, + EditOutlined, + CheckOutlined, + CloseOutlined, + ExportOutlined +} from '@ant-design/icons' +import IdText from '../../common/IdText' +import moment from 'moment' + +const { Title, Link } = Typography + +const FilamentInfo = () => { + const [filamentData, setFilamentData] = useState(null) + const [fetchLoading, setFetchLoading] = useState(true) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const location = useLocation() + const [messageApi, contextHolder] = message.useMessage() + const filamentId = new URLSearchParams(location.search).get('filamentId') + const [isEditing, setIsEditing] = useState(false) + const [form] = Form.useForm() + + useEffect(() => { + if (filamentId) { + fetchFilamentDetails() + } + }, [filamentId]) + + useEffect(() => { + if (filamentData) { + form.setFieldsValue({ + name: filamentData.name || '', + brand: filamentData.brand || '', + type: filamentData.type || '', + price: filamentData.price || null, + color: filamentData.color || '#000000', + diameter: filamentData.diameter || null, + density: filamentData.density || null, + url: filamentData.url || '', + barcode: filamentData.barcode || '', + emptySpoolWeight: filamentData.emptySpoolWeight || '' + }) + } + }, [filamentData, form]) + + const fetchFilamentDetails = async () => { + try { + setFetchLoading(true) + const response = await axios.get( + `http://localhost:8080/filaments/${filamentId}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true + } + ) + setFilamentData(response.data) + setError(null) + } catch (err) { + setError('Failed to fetch filament details') + messageApi.error('Failed to fetch filament details') + } finally { + setFetchLoading(false) + } + } + + const startEditing = () => { + setIsEditing(true) + } + + const cancelEditing = () => { + // Reset form values to original data + if (filamentData) { + form.setFieldsValue({ + name: filamentData.name || '', + brand: filamentData.brand || '', + type: filamentData.type || '', + price: filamentData.price || null, + color: filamentData.color || '#000000', + diameter: filamentData.diameter || null, + density: filamentData.density || null, + url: filamentData.url || '', + barcode: filamentData.barcode || '', + emptySpoolWeight: filamentData.emptySpoolWeight || '' + }) + } + setIsEditing(false) + } + + const updateFilamentInfo = async () => { + try { + const values = await form.validateFields() + setLoading(true) + + await axios.put( + `http://localhost:8080/filaments/${filamentId}`, + { + name: values.name, + brand: values.brand, + type: values.type, + price: values.price, + color: values.color, + diameter: values.diameter, + density: values.density, + url: values.url, + barcode: values.barcode, + emptySpoolWeight: values.emptySpoolWeight + }, + { + headers: { + 'Content-Type': 'application/json' + }, + withCredentials: true + } + ) + + // Update the local state with the new values + setFilamentData({ ...filamentData, ...values }) + setIsEditing(false) + messageApi.success('Filament information updated successfully') + } catch (err) { + if (err.errorFields) { + // This is a form validation error + return + } + console.error('Failed to update filament information:', err) + messageApi.error('Failed to update filament information') + } finally { + setLoading(false) + } + } + + if (fetchLoading) { + return ( +
+ } /> +
+ ) + } + + if (error || !filamentData) { + return ( + +

{error || 'Filament not found'}

+ +
+ ) + } + + return ( +
+ {contextHolder} + + + Filament Information + + + {isEditing ? ( + <> + + + + ) : ( + + )} + + + +
+ + {/* Read-only fields */} + + {filamentData.id ? ( + + ) : ( + 'n/a' + )} + + + {(() => { + if (filamentData.createdAt) { + return moment(filamentData.createdAt.$date).format( + 'YYYY-MM-DD HH:mm:ss' + ) + } + return 'N/A' + })()} + + + {/* Editable fields */} + + {isEditing ? ( + + + + ) : ( + filamentData.name || 'n/a' + )} + + + + {isEditing ? ( + + + + ) : ( + filamentData.brand || 'n/a' + )} + + + + {isEditing ? ( + + + + ) : ( + filamentData.type || 'n/a' + )} + + + + {isEditing ? ( + + + + ) : filamentData.price ? ( + `£${filamentData.price} per kg` + ) : ( + 'n/a' + )} + + + + {isEditing ? ( + + + + ) : filamentData.color ? ( + + ) : ( + 'n/a' + )} + + + + {isEditing ? ( + + + + ) : filamentData.diameter ? ( + `${filamentData.diameter}mm` + ) : ( + 'n/a' + )} + + + + {isEditing ? ( + + + + ) : filamentData.density ? ( + `${filamentData.density}g/cm³` + ) : ( + 'n/a' + )} + + + + {isEditing ? ( + + + + ) : filamentData.emptySpoolWeight ? ( + `${filamentData.emptySpoolWeight}g` + ) : ( + 'n/a' + )} + + + + {isEditing ? ( + + + + ) : filamentData.url ? ( + + {new URL(filamentData.url).hostname + ' '} + + + ) : ( + 'n/a' + )} + + + + {isEditing ? ( + + + + ) : ( + filamentData.barcode || 'n/a' + )} + + + +
+ ) +} + +export default FilamentInfo diff --git a/src/components/Dashboard/Management/Filaments/NewFilament.jsx b/src/components/Dashboard/Management/Filaments/NewFilament.jsx new file mode 100644 index 0000000..e6ee00e --- /dev/null +++ b/src/components/Dashboard/Management/Filaments/NewFilament.jsx @@ -0,0 +1,432 @@ +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import axios from 'axios' +import { + Form, + Input, + InputNumber, + Button, + message, + Typography, + Select, + Flex, + Steps, + Col, + Row, + Divider, + ColorPicker, + Upload, + Descriptions, + Badge +} from 'antd' +import { UploadOutlined, LinkOutlined } from '@ant-design/icons' + +const { Title, Text } = Typography + +const initialNewFilamentForm = { + name: '', + brand: '', + type: '', + price: 0, + color: '#FFFFFF', + diameter: '1.75', + image: null, + url: '', + barcode: '' +} + +const NewFilament = ({ onOk, reset }) => { + const [messageApi, contextHolder] = message.useMessage() + + const [newFilamentLoading, setNewFilamentLoading] = useState(false) + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + + const [newFilamentForm] = Form.useForm() + const [newFilamentFormValues, setNewFilamentFormValues] = useState( + initialNewFilamentForm + ) + + const [imageList, setImageList] = useState([]) + + const newFilamentFormUpdateValues = Form.useWatch([], newFilamentForm) + + React.useEffect(() => { + newFilamentForm + .validateFields({ + validateOnly: true + }) + .then(() => setNextEnabled(true)) + .catch(() => setNextEnabled(false)) + }, [newFilamentForm, newFilamentFormUpdateValues]) + + const summaryItems = [ + { + key: 'name', + label: 'Name', + children: newFilamentFormValues.name + }, + { + key: 'brand', + label: 'Brand', + children: newFilamentFormValues.brand + }, + { + key: 'type', + label: 'Material', + children: newFilamentFormValues.type + }, + { + key: 'price', + label: 'Price', + children: '£' + newFilamentFormValues.price + ' per kg' + }, + { + key: 'color', + label: 'Colour', + children: ( + + ) + }, + { + key: 'diameter', + label: 'Diameter', + children: newFilamentFormValues.diameter + 'mm' + }, + { + key: 'density', + label: 'Density', + children: newFilamentFormValues.diameter + 'g/cm³' + }, + { + key: 'image', + label: 'Image', + children: ( + + ) + }, + { + key: 'url', + label: 'URL', + children: newFilamentFormValues.url + }, + { + key: 'barcode', + label: 'Barcode', + children: newFilamentFormValues.barcode + } + ] + + React.useEffect(() => { + if (reset) { + newFilamentForm.resetFields() + } + }, [reset, newFilamentForm]) + + const handleNewFilament = async () => { + setNewFilamentLoading(true) + try { + await axios.post( + `http://localhost:8080/filaments`, + newFilamentFormValues, + { + withCredentials: true // Important for including cookies + } + ) + messageApi.success('New filament created successfully.') + onOk() + } catch (error) { + messageApi.error('Error creating new filament: ' + error.message) + } finally { + setNewFilamentLoading(false) + } + } + + const getBase64 = (file) => { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.readAsDataURL(file) + reader.onload = () => resolve(reader.result) + reader.onerror = (error) => reject(error) + }) + } + + const handleImageUpload = async ({ file, fileList }) => { + console.log(fileList) + if (fileList.length === 0) { + setImageList(fileList) + newFilamentForm.setFieldsValue({ image: '' }) + return + } + const base64 = await getBase64(file) + setNewFilamentFormValues((prevValues) => ({ + ...prevValues, + image: base64 + })) + fileList[0].name = 'Filament Image' + setImageList(fileList) + newFilamentForm.setFieldsValue({ image: base64 }) + } + + const steps = [ + { + title: 'Details', + key: 'details', + content: ( + <> + + + + + + + + + + + { + if (!value) return '£' + return `£${value}` + }} + step={0.01} + style={{ width: '100%' }} + addonAfter='per kg' + /> + + + + + + { + if (!value) return '' + return `${value}` + }} + step={0.01} + style={{ width: '100%' }} + addonAfter='g/cm³' + /> + + + { + if (!value) return '' + return `${value}` + }} + step={0.01} + style={{ width: '100%' }} + addonAfter='g' + /> + + + ) + }, + { + title: 'Optional', + key: 'optional', + content: ( + <> + + Optional information: + + + { + return '#' + color.toHex() + }} + > + + + (Array.isArray(e) ? e : e && e.fileList)} + > + false} // Prevent automatic upload + onChange={handleImageUpload} + > + + + + + } /> + + + } /> + + + ) + }, + { + title: 'Summary', + key: 'done', + content: ( + + + + ) + } + ] + + return ( + + {contextHolder} + + + + + + + + + + New Filament + +
+ setNewFilamentFormValues((prevValues) => ({ + ...prevValues, + ...changedValues + })) + } + initialValues={initialNewFilamentForm} + > + {steps[currentStep].content} + + + + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + +
+ + + ) +} + +NewFilament.propTypes = { + reset: PropTypes.bool.isRequired, + onOk: PropTypes.func.isRequired +} + +export default NewFilament diff --git a/src/components/Dashboard/Management/Parts.jsx b/src/components/Dashboard/Management/Parts.jsx new file mode 100644 index 0000000..9542b14 --- /dev/null +++ b/src/components/Dashboard/Management/Parts.jsx @@ -0,0 +1,235 @@ +// src/gcodefiles.js + +import React, { useEffect, useState, useContext, useCallback } from 'react' +import { useNavigate } from 'react-router-dom' +import axios from 'axios' +import moment from 'moment' + +import { Table, Button, Flex, Space, Modal, Dropdown, message } from 'antd' +import { createStyles } from 'antd-style' +import { + LoadingOutlined, + PlusOutlined, + DownloadOutlined, + ReloadOutlined, + InfoCircleOutlined +} from '@ant-design/icons' + +import { AuthContext } from '../../Auth/AuthContext' + +import IdText from '../common/IdText' +import NewPart from './Parts/NewPart' +import PartIcon from '../../Icons/PartIcon' + +const useStyle = createStyles(({ css, token }) => { + const { antCls } = token + return { + customTable: css` + ${antCls}-table { + ${antCls}-table-container { + ${antCls}-table-body, + ${antCls}-table-content { + scrollbar-width: thin; + scrollbar-color: #eaeaea transparent; + scrollbar-gutter: stable; + } + } + } + ` + } +}) + +const Parts = () => { + const [messageApi, contextHolder] = message.useMessage() + const navigate = useNavigate() + const { styles } = useStyle() + + const [partsData, setPartsData] = useState([]) + + const [newPartOpen, setNewPartOpen] = useState(false) + + const [loading, setLoading] = useState(true) + + const { authenticated } = useContext(AuthContext) + + const fetchPartsData = useCallback(async () => { + try { + const response = await axios.get('http://localhost:8080/parts', { + params: { + page: 1, + limit: 25 + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + }) + setPartsData(response.data) + setLoading(false) + //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count + } catch (error) { + if (error.response) { + messageApi.error( + 'Error updating printer details:', + error.response.status + ) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + }, [messageApi]) + + useEffect(() => { + if (authenticated) { + fetchPartsData() + } + }, [authenticated, fetchPartsData]) + + const getPartActionItems = (id) => { + return { + items: [ + { + label: 'Info', + key: 'info', + icon: + }, + { + label: 'Download', + key: 'download', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'info') { + navigate(`/management/parts/info?partId=${id}`) + } + } + } + } + + // Column definitions + const columns = [ + { + title: '', + dataIndex: '', + key: '', + width: 40, + fixed: 'left', + render: () => + }, + { + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 200, + fixed: 'left' + }, + { + title: 'ID', + dataIndex: '_id', + key: 'id', + width: 165, + render: (text) => + }, + { + title: 'Created At', + dataIndex: 'createdAt', + key: 'createdAt', + width: 180, + render: (createdAt) => { + if (createdAt) { + const formattedDate = moment(createdAt).format('YYYY-MM-DD HH:mm:ss') + return {formattedDate} + } else { + return 'n/a' + } + } + }, + { + title: 'Actions', + key: 'actions', + fixed: 'right', + width: 150, + render: (text, record) => { + return ( + + + + + ) + } + } + ] + + const actionItems = { + items: [ + { + label: 'New Part', + key: 'newPart', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + fetchPartsData() + } else if (key === 'newPart') { + setNewPartOpen(true) + } + } + } + + return ( + <> + + {contextHolder} + + + + + +
}} + /> + + { + setNewPartOpen(false) + }} + > + { + setNewPartOpen(false) + fetchPartsData() + }} + reset={newPartOpen} + /> + + + ) +} + +export default Parts diff --git a/src/components/Dashboard/Management/Parts/NewPart.jsx b/src/components/Dashboard/Management/Parts/NewPart.jsx new file mode 100644 index 0000000..5235180 --- /dev/null +++ b/src/components/Dashboard/Management/Parts/NewPart.jsx @@ -0,0 +1,471 @@ +import PropTypes from 'prop-types' +import React, { useState, useContext, useEffect, useRef } from 'react' +import axios from 'axios' +import { + Form, + Input, + Button, + message, + Typography, + Flex, + Steps, + Divider, + Upload, + Descriptions, + Modal +} from 'antd' +import { DeleteOutlined, EyeOutlined } from '@ant-design/icons' +import { AuthContext } from '../../../Auth/AuthContext' +import PartIcon from '../../../Icons/PartIcon' +import { StlViewer } from 'react-stl-viewer' + +const { Dragger } = Upload +const { Title } = Typography + +const initialNewPartsForm = { parts: [{ name: 'Test' }] } + +const NewPart = ({ onOk, reset }) => { + const [messageApi, contextHolder] = message.useMessage() + const [newPartLoading, setNewPartLoading] = useState(false) + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + + const [newPartsForm] = Form.useForm() + const [newPartsFormValues, setNewPartsFormValues] = + useState(initialNewPartsForm) + + // Store files and their object URLs + const [fileList, setFileList] = useState([]) + const [fileObjectUrls, setFileObjectUrls] = useState({}) + const [names, setNames] = useState({}) + + // Preview modal state + const [previewVisible, setPreviewVisible] = useState(false) + const [previewFile, setPreviewFile] = useState(null) + const [stlLoading, setStlLoading] = useState(false) + + const newPartsFormUpdateValues = Form.useWatch([], newPartsForm) + + const { token, authenticated } = useContext(AuthContext) + + // Timer reference for delayed STL rendering + const stlTimerRef = useRef(null) + + // Validate form fields + useEffect(() => { + if (currentStep === 0) { + // For combined upload/files step + setNextEnabled(fileList.length > 0) + } else { + newPartsForm + .validateFields({ + validateOnly: true + }) + .then(() => setNextEnabled(true)) + .catch(() => setNextEnabled(false)) + } + }, [newPartsForm, newPartsFormUpdateValues, fileList, currentStep]) + + // Handle reset + useEffect(() => { + if (reset) { + newPartsForm.resetFields() + setFileList([]) + setFileObjectUrls({}) + setNames({}) + setCurrentStep(0) + } + }, [reset, newPartsForm]) + + // Clean up object URLs when component unmounts + useEffect(() => { + return () => { + Object.values(fileObjectUrls).forEach((url) => { + URL.revokeObjectURL(url) + }) + if (stlTimerRef.current) { + clearTimeout(stlTimerRef.current) + } + } + }, [fileObjectUrls]) + + // Create a summary of all parts for the final step + const summaryItems = fileList + .map((file, index) => ({ + key: file.uid, + label: `Part ${index + 1}`, + children: names[file.uid] || file.name.replace(/\.[^/.]+$/, '') + })) + .concat([ + { + key: 'name', + label: 'Product Name', + children: newPartsFormValues.name + } + ]) + + // Handle file upload + const handleFileUpload = async (files) => { + if (!authenticated) { + return + } + setNewPartLoading(true) + + try { + // First create the part entries + const partsData = [] + + for (const file of files) { + const partName = names[file.uid] || file.name.replace(/\.[^/.]+$/, '') + const partData = { + name: partName, + partInfo: {} + } + + const response = await axios.post( + `http://localhost:8080/parts`, + partData, + { + headers: { + Authorization: `Bearer ${token}` + } + } + ) + + // Now upload the actual file content + const formData = new FormData() + formData.append('partFile', file) + await axios.post( + `http://localhost:8080/parts/${response.data._id}/content`, + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: `Bearer ${token}` + } + } + ) + + partsData.push({ + id: response.data._id, + name: partName + }) + } + + // Create product with all the parts references + await axios.post(`http://localhost:8080/products`, newPartsFormValues, { + headers: { + Authorization: `Bearer ${token}` + } + }) + + messageApi.success(`Parts and product created successfully!`) + onOk() + } catch (error) { + messageApi.error('Error creating parts: ' + error.message) + } finally { + setNewPartLoading(false) + } + } + + // Handle file name change + const handleFileNameChange = (uid, name) => { + setNames((prev) => ({ + ...prev, + [uid]: name + })) + } + + // Preview STL file + const handlePreview = (file) => { + setPreviewFile(file) + setPreviewVisible(true) + setStlLoading(true) + + // Delay the rendering of the STL viewer to fix glitch + if (stlTimerRef.current) { + clearTimeout(stlTimerRef.current) + } + + stlTimerRef.current = setTimeout(() => { + setStlLoading(false) + }, 300) + } + + // Add file to list + const handleAddFile = (file) => { + // Create object URL for preview + const objectUrl = URL.createObjectURL(file) + + setNewPartsFormValues((prev) => ({ + parts: [ + ...prev.parts, + { + name: file.name, + size: file.size, + objectUrl: objectUrl + } + ] + })) + + console.log(newPartsFormValues) + newPartsForm.setFormValues({ + parts: [ + ...newPartsFormValues.parts, + { + name: file.name, + size: file.size, + objectUrl: objectUrl + } + ] + }) + + // Set default name (filename without extension) + const defaultName = file.name.replace(/\.[^/.]+$/, '') + setNames((prev) => ({ + ...prev, + [file.uid]: defaultName + })) + + return false // Prevent default upload behavior + } + + // Combined upload and files content for step 1 + const combinedUploadFilesContent = ( + <> + {fileList.length > 0 && ( + + + {(parts, { remove }) => ( + <> + {parts.map((part) => ( + + + + handleFileNameChange('file.uid', e.target.value) + } + /> + + + + , + + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + + + + {/* STL Preview Modal */} + { + setPreviewVisible(false) + setPreviewFile(null) + if (stlTimerRef.current) { + clearTimeout(stlTimerRef.current) + } + }} + style={{ top: 30 }} + width={'90%'} + > + + {previewFile && !stlLoading && ( +
+ +
+ )} + {stlLoading && ( +
+ Loading 3D model... +
+ )} +
+
+ + ) +} + +NewPart.propTypes = { + reset: PropTypes.bool.isRequired, + onOk: PropTypes.func.isRequired +} + +export default NewPart diff --git a/src/components/Dashboard/Management/Parts/PartInfo.jsx b/src/components/Dashboard/Management/Parts/PartInfo.jsx new file mode 100644 index 0000000..de8a4b9 --- /dev/null +++ b/src/components/Dashboard/Management/Parts/PartInfo.jsx @@ -0,0 +1,273 @@ +import React, { useState, useEffect } from 'react' +import { useLocation } from 'react-router-dom' +import axios from 'axios' +import { + Descriptions, + Spin, + Space, + Button, + message, + Typography, + Card, + Flex, + Form, + Input +} from 'antd' +import { + LoadingOutlined, + EditOutlined, + ReloadOutlined, + CheckOutlined, + CloseOutlined +} from '@ant-design/icons' +import IdText from '../../common/IdText.jsx' +import moment from 'moment' + +const { Title } = Typography +import { StlViewer } from 'react-stl-viewer' + +const PartInfo = () => { + const [partData, setPartData] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const location = useLocation() + const [messageApi, contextHolder] = message.useMessage() + const partId = new URLSearchParams(location.search).get('partId') + const [partFileObjectId, setPartFileObjectId] = useState(null) + const [isEditing, setIsEditing] = useState(false) + const [form] = Form.useForm() + const [fetchLoading, setFetchLoading] = useState(true) + + useEffect(() => { + async function fetchData() { + await fetchPartDetails() + await fetchPartContent() + } + if (partId) { + fetchData() + } + }, [partId]) + + useEffect(() => { + if (partData) { + form.setFieldsValue({ + name: partData.name || '' + }) + } + }, [partData, form]) + + const fetchPartDetails = async () => { + try { + setFetchLoading(true) + const response = await axios.get( + `http://localhost:8080/parts/${partId}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true + } + ) + setPartData(response.data) + setError(null) + } catch (err) { + setError('Failed to fetch Part details') + console.log(err) + messageApi.error('Failed to fetch Part details') + } finally { + setFetchLoading(false) + } + } + + const fetchPartContent = async () => { + try { + setFetchLoading(true) + const response = await axios.get( + `http://localhost:8080/parts/${partId}/content`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true, + responseType: 'blob' + } + ) + + setPartFileObjectId(URL.createObjectURL(response.data)) + setError(null) + } catch (err) { + setError('Failed to fetch Part content') + console.log(err) + messageApi.error('Failed to fetch Part content') + } finally { + setFetchLoading(false) + } + } + + const startEditing = () => { + setIsEditing(true) + } + + const cancelEditing = () => { + form.setFieldsValue({ + name: partData?.name || '' + }) + setIsEditing(false) + } + + const updateInfo = async () => { + try { + const values = await form.validateFields() + setLoading(true) + + await axios.put( + `http://localhost:8080/parts/${partId}`, + { + name: values.name + }, + { + headers: { + 'Content-Type': 'application/json' + }, + withCredentials: true + } + ) + + // Update the local state with the new name + setPartData({ ...partData, name: values.name }) + setIsEditing(false) + messageApi.success('Part information updated successfully') + } catch (err) { + if (err.errorFields) { + // This is a form validation error + return + } + console.error('Failed to update part information:', err) + messageApi.error('Failed to update part information') + } finally { + setLoading(false) + } + } + + if (fetchLoading) { + return ( +
+ } /> +
+ ) + } + + if (error || !partData) { + return ( + +

{error || 'Part not found'}

+ +
+ ) + } + + return ( +
+ {contextHolder} + + + Part Information + + + {isEditing ? ( + <> + + + + ) : ( + + )} + + + +
+ + + {partData.id ? ( + + ) : ( + 'n/a' + )} + + + {(() => { + if (partData.createdAt) { + return moment(partData.createdAt.$date).format( + 'YYYY-MM-DD HH:mm:ss' + ) + } + return 'N/A' + })()} + + + {isEditing ? ( + + + + ) : ( + partData.name || 'n/a' + )} + + + + + + + Part Preview + + + + + +
+ ) +} + +export default PartInfo diff --git a/src/components/Dashboard/Management/Products.jsx b/src/components/Dashboard/Management/Products.jsx new file mode 100644 index 0000000..1d4c4ac --- /dev/null +++ b/src/components/Dashboard/Management/Products.jsx @@ -0,0 +1,237 @@ +// src/gcodefiles.js + +import React, { useEffect, useState, useContext, useCallback } from 'react' +import { useNavigate } from 'react-router-dom' +import axios from 'axios' +import moment from 'moment' + +import { Table, Button, Flex, Space, Modal, Dropdown, message } from 'antd' +import { createStyles } from 'antd-style' +import { + LoadingOutlined, + PlusOutlined, + DownloadOutlined, + ReloadOutlined, + InfoCircleOutlined +} from '@ant-design/icons' + +import { AuthContext } from '../../Auth/AuthContext' + +import IdText from '../common/IdText' +import NewProduct from './Products/NewProduct' +import ProductIcon from '../../Icons/ProductIcon' + +const useStyle = createStyles(({ css, token }) => { + const { antCls } = token + return { + customTable: css` + ${antCls}-table { + ${antCls}-table-container { + ${antCls}-table-body, + ${antCls}-table-content { + scrollbar-width: thin; + scrollbar-color: #eaeaea transparent; + scrollbar-gutter: stable; + } + } + } + ` + } +}) + +const Products = () => { + const [messageApi, contextHolder] = message.useMessage() + const navigate = useNavigate() + const { styles } = useStyle() + + const [productsData, setProductsData] = useState([]) + + const [newProductOpen, setNewProductOpen] = useState(false) + + const [loading, setLoading] = useState(true) + + const { authenticated } = useContext(AuthContext) + + const fetchProductsData = useCallback(async () => { + try { + const response = await axios.get('http://localhost:8080/products', { + params: { + page: 1, + limit: 25 + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + }) + setProductsData(response.data) + setLoading(false) + //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count + } catch (error) { + if (error.response) { + messageApi.error( + 'Error updating printer details:', + error.response.status + ) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + }, [messageApi]) + + useEffect(() => { + if (authenticated) { + fetchProductsData() + } + }, [authenticated, fetchProductsData]) + + const getProductActionItems = (id) => { + return { + items: [ + { + label: 'Info', + key: 'info', + icon: + }, + { + label: 'Download', + key: 'download', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'info') { + navigate(`/management/products/info?productId=${id}`) + } + } + } + } + + // Column definitions + const columns = [ + { + title: '', + dataIndex: '', + key: '', + width: 40, + fixed: 'left', + render: () => + }, + { + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 200, + fixed: 'left' + }, + { + title: 'ID', + dataIndex: '_id', + key: 'id', + fixed: 'left', + width: 165, + render: (text) => + }, + { + title: 'Created At', + dataIndex: 'createdAt', + key: 'createdAt', + width: 180, + render: (createdAt) => { + if (createdAt) { + const formattedDate = moment(createdAt).format('YYYY-MM-DD HH:mm:ss') + return {formattedDate} + } else { + return 'n/a' + } + } + }, + { + title: 'Actions', + key: 'actions', + fixed: 'right', + width: 150, + render: (text, record) => { + return ( + + + + + ) + } + } + ] + + const actionItems = { + items: [ + { + label: 'New Product', + key: 'newProduct', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + fetchProductsData() + } else if (key === 'newProduct') { + setNewProductOpen(true) + } + } + } + + return ( + <> + + {contextHolder} + + + + + +
}} + /> + + { + setNewProductOpen(false) + }} + destroyOnClose + > + { + setNewProductOpen(false) + fetchProductsData() + }} + reset={newProductOpen} + /> + + + ) +} + +export default Products diff --git a/src/components/Dashboard/Management/Products/NewProduct.jsx b/src/components/Dashboard/Management/Products/NewProduct.jsx new file mode 100644 index 0000000..c6d02f7 --- /dev/null +++ b/src/components/Dashboard/Management/Products/NewProduct.jsx @@ -0,0 +1,213 @@ +import PropTypes from 'prop-types' +import React, { useState, useContext } from 'react' +import axios from 'axios' +import { + Form, + Input, + Button, + message, + Typography, + Flex, + Steps, + Divider, + Descriptions +} from 'antd' + +import { AuthContext } from '../../../Auth/AuthContext' + +const { Title } = Typography + +const initialNewProductForm = { + productInfo: {}, + printTimeMins: 0, + price: 0 +} + +//const chunkSize = 5000 + +const NewProduct = ({ onOk, reset }) => { + const [messageApi, contextHolder] = message.useMessage() + const [newProductLoading, setNewProductLoading] = useState(false) + + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + + const [newProductForm] = Form.useForm() + const [newProductFormValues, setNewProductFormValues] = useState( + initialNewProductForm + ) + + const newProductFormUpdateValues = Form.useWatch([], newProductForm) + + const { token, authenticated } = useContext(AuthContext) + + React.useEffect(() => { + newProductForm + .validateFields({ + validateOnly: true + }) + .then(() => setNextEnabled(true)) + .catch(() => setNextEnabled(false)) + }, [newProductForm, newProductFormUpdateValues]) + + const summaryItems = [ + { + key: 'name', + label: 'Name', + children: newProductFormValues.name + } + ] + + React.useEffect(() => { + if (reset) { + newProductForm.resetFields() + } + }, [reset, newProductForm]) + + const handleNewProduct = async () => { + if (!authenticated) { + return + } + setNewProductLoading(true) + try { + await axios.post(`http://localhost:8080/products`, newProductFormValues, { + headers: { + Authorization: `Bearer ${token}` + } + }) + + messageApi.success(`Product created successfully.`) + onOk() + } catch (error) { + messageApi.error('Error creating new product file: ' + error.message) + } finally { + setNewProductLoading(false) + } + } + + const steps = [ + { + title: 'Parts', + key: 'parts', + content: ( + <> + (Array.isArray(e) ? e : e && e.fileList)} + > + + ) + }, + { + title: 'Details', + key: 'details', + content: ( + + + + + + ) + }, + { + title: 'Summary', + key: 'done', + content: ( + <> + + + + + ) + } + ] + + return ( + + {contextHolder} +
+ +
+ + + + + + New Product + +
+ setNewProductFormValues((prevValues) => ({ + ...prevValues, + ...changedValues + })) + } + initialValues={initialNewProductForm} + > +
{steps[currentStep].content}
+ + + + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + +
+
+ ) +} + +NewProduct.propTypes = { + reset: PropTypes.bool.isRequired, + onOk: PropTypes.func.isRequired +} + +export default NewProduct diff --git a/src/components/Dashboard/Management/Products/ProductInfo.jsx b/src/components/Dashboard/Management/Products/ProductInfo.jsx new file mode 100644 index 0000000..c77a14f --- /dev/null +++ b/src/components/Dashboard/Management/Products/ProductInfo.jsx @@ -0,0 +1,273 @@ +import React, { useState, useEffect } from 'react' +import { useLocation } from 'react-router-dom' +import axios from 'axios' +import { + Descriptions, + Spin, + Space, + Button, + message, + Typography, + Card, + Flex, + Form, + Input +} from 'antd' +import { + LoadingOutlined, + EditOutlined, + ReloadOutlined, + CheckOutlined, + CloseOutlined +} from '@ant-design/icons' +import IdText from '../../common/IdText.jsx' +import moment from 'moment' + +const { Title } = Typography +import { StlViewer } from 'react-stl-viewer' + +const ProductInfo = () => { + const [productData, setProductData] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const location = useLocation() + const [messageApi, contextHolder] = message.useMessage() + const productId = new URLSearchParams(location.search).get('productId') + const [productFileObjectId, setProductFileObjectId] = useState(null) + const [isEditing, setIsEditing] = useState(false) + const [form] = Form.useForm() + const [fetchLoading, setFetchLoading] = useState(true) + + useEffect(() => { + async function fetchData() { + await fetchProductDetails() + await fetchProductContent() + } + if (productId) { + fetchData() + } + }, [productId]) + + useEffect(() => { + if (productData) { + form.setFieldsValue({ + name: productData.name || '' + }) + } + }, [productData, form]) + + const fetchProductDetails = async () => { + try { + setFetchLoading(true) + const response = await axios.get( + `http://localhost:8080/products/${productId}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true + } + ) + setProductData(response.data) + setError(null) + } catch (err) { + setError('Failed to fetch Product details') + console.log(err) + messageApi.error('Failed to fetch Product details') + } finally { + setFetchLoading(false) + } + } + + const fetchProductContent = async () => { + try { + setFetchLoading(true) + const response = await axios.get( + `http://localhost:8080/products/${productId}/content`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true, + responseType: 'blob' + } + ) + + setProductFileObjectId(URL.createObjectURL(response.data)) + setError(null) + } catch (err) { + setError('Failed to fetch Product content') + console.log(err) + messageApi.error('Failed to fetch Product content') + } finally { + setFetchLoading(false) + } + } + + const startEditing = () => { + setIsEditing(true) + } + + const cancelEditing = () => { + form.setFieldsValue({ + name: productData?.name || '' + }) + setIsEditing(false) + } + + const updateInfo = async () => { + try { + const values = await form.validateFields() + setLoading(true) + + await axios.put( + `http://localhost:8080/products/${productId}`, + { + name: values.name + }, + { + headers: { + 'Content-Type': 'application/json' + }, + withCredentials: true + } + ) + + // Update the local state with the new name + setProductData({ ...productData, name: values.name }) + setIsEditing(false) + messageApi.success('Product information updated successfully') + } catch (err) { + if (err.errorFields) { + // This is a form validation error + return + } + console.error('Failed to update product information:', err) + messageApi.error('Failed to update product information') + } finally { + setLoading(false) + } + } + + if (fetchLoading) { + return ( +
+ } /> +
+ ) + } + + if (error || !productData) { + return ( + +

{error || 'Product not found'}

+ +
+ ) + } + + return ( +
+ {contextHolder} + + + Product Information + + + {isEditing ? ( + <> + + + + ) : ( + + )} + + + +
+ + + {productData.id ? ( + + ) : ( + 'n/a' + )} + + + {(() => { + if (productData.createdAt) { + return moment(productData.createdAt.$date).format( + 'YYYY-MM-DD HH:mm:ss' + ) + } + return 'N/A' + })()} + + + {isEditing ? ( + + + + ) : ( + productData.name || 'n/a' + )} + + + + + + + Product Preview + + + + + +
+ ) +} + +export default ProductInfo diff --git a/src/components/Dashboard/Management/Vendors.jsx b/src/components/Dashboard/Management/Vendors.jsx new file mode 100644 index 0000000..e391208 --- /dev/null +++ b/src/components/Dashboard/Management/Vendors.jsx @@ -0,0 +1,226 @@ +import React, { useEffect, useState, useContext, useCallback } from 'react' +import { useNavigate } from 'react-router-dom' +import axios from 'axios' +import moment from 'moment' +import { Table, Button, Flex, Space, Modal, Dropdown, message } from 'antd' +import { createStyles } from 'antd-style' +import { + LoadingOutlined, + PlusOutlined, + ReloadOutlined, + InfoCircleOutlined, + ShopOutlined +} from '@ant-design/icons' +import { AuthContext } from '../../Auth/AuthContext' +import IdText from '../common/IdText' +import NewVendor from './Vendors/NewVendor' + +const useStyle = createStyles(({ css, token }) => { + const { antCls } = token + return { + customTable: css` + ${antCls}-table { + ${antCls}-table-container { + ${antCls}-table-body, + ${antCls}-table-content { + scrollbar-width: thin; + scrollbar-color: #eaeaea transparent; + scrollbar-gutter: stable; + } + } + } + ` + } +}) + +const Vendors = () => { + const [messageApi, contextHolder] = message.useMessage() + const navigate = useNavigate() + const { styles } = useStyle() + const [vendorsData, setVendorsData] = useState([]) + const [newVendorOpen, setNewVendorOpen] = useState(false) + const [loading, setLoading] = useState(true) + const { authenticated } = useContext(AuthContext) + + const fetchVendorsData = useCallback(async () => { + try { + const response = await axios.get('http://localhost:8080/vendors', { + params: { + page: 1, + limit: 25 + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true + }) + setVendorsData(response.data) + setLoading(false) + } catch (error) { + if (error.response) { + messageApi.error('Error fetching vendor data:', error.response.status) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + }, [messageApi]) + + useEffect(() => { + if (authenticated) { + fetchVendorsData() + } + }, [authenticated, fetchVendorsData]) + + const getVendorActionItems = (id) => { + return { + items: [ + { + label: 'Info', + key: 'info', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'info') { + navigate(`/management/vendors/info?vendorId=${id}`) + } + } + } + } + + const columns = [ + { + title: '', + dataIndex: '', + key: '', + width: 40, + fixed: 'left', + render: () => + }, + { + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 200, + fixed: 'left' + }, + { + title: 'ID', + dataIndex: '_id', + key: 'id', + width: 165, + render: (text) => + }, + { + title: 'Website', + dataIndex: 'website', + key: 'website', + width: 200 + }, + { + title: 'Contact', + dataIndex: 'contact', + key: 'contact', + width: 200 + }, + { + title: 'Created At', + dataIndex: 'createdAt', + key: 'createdAt', + width: 180, + render: (createdAt) => { + if (createdAt) { + const formattedDate = moment(createdAt).format('YYYY-MM-DD HH:mm:ss') + return {formattedDate} + } else { + return 'n/a' + } + } + }, + { + title: 'Actions', + key: 'actions', + fixed: 'right', + width: 150, + render: (text, record) => { + return ( + + + + + ) + } + } + ] + + const actionItems = { + items: [ + { + label: 'New Vendor', + key: 'newVendor', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + fetchVendorsData() + } else if (key === 'newVendor') { + setNewVendorOpen(true) + } + } + } + + return ( + <> + + {contextHolder} + + + + + +
}} + /> + + setNewVendorOpen(false)} + footer={null} + destroyOnClose + width={700} + > + { + setNewVendorOpen(false) + fetchVendorsData() + }} + reset={!newVendorOpen} + /> + + + ) +} + +export default Vendors diff --git a/src/components/Dashboard/Management/Vendors/NewVendor.jsx b/src/components/Dashboard/Management/Vendors/NewVendor.jsx new file mode 100644 index 0000000..cf674c3 --- /dev/null +++ b/src/components/Dashboard/Management/Vendors/NewVendor.jsx @@ -0,0 +1,205 @@ +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import axios from 'axios' +import { + Form, + Input, + Button, + message, + Typography, + Flex, + Steps, + Descriptions, + Divider +} from 'antd' + +const { Title } = Typography + +const initialNewVendorForm = { + name: '', + website: '', + contact: '' +} + +const NewVendor = ({ onOk, reset }) => { + const [messageApi, contextHolder] = message.useMessage() + const [newVendorLoading, setNewVendorLoading] = useState(false) + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + const [newVendorForm] = Form.useForm() + const [newVendorFormValues, setNewVendorFormValues] = + useState(initialNewVendorForm) + + const newVendorFormUpdateValues = Form.useWatch([], newVendorForm) + + React.useEffect(() => { + newVendorForm + .validateFields({ + validateOnly: true + }) + .then(() => setNextEnabled(true)) + .catch(() => setNextEnabled(false)) + }, [newVendorForm, newVendorFormUpdateValues]) + + const summaryItems = [ + { + key: 'name', + label: 'Name', + children: newVendorFormValues.name + }, + { + key: 'website', + label: 'Website', + children: newVendorFormValues.website + }, + { + key: 'contact', + label: 'Contact', + children: newVendorFormValues.contact + } + ] + + React.useEffect(() => { + if (reset) { + newVendorForm.resetFields() + } + }, [reset, newVendorForm]) + + const handleNewVendor = async () => { + setNewVendorLoading(true) + try { + await axios.post('http://localhost:8080/vendors', newVendorFormValues, { + withCredentials: true + }) + messageApi.success('New vendor created successfully.') + onOk() + } catch (error) { + messageApi.error('Error creating new vendor: ' + error.message) + } finally { + setNewVendorLoading(false) + } + } + + const steps = [ + { + title: 'Details', + key: 'details', + content: ( + <> + + + + + + + + + + + ) + }, + { + title: 'Summary', + key: 'summary', + content: + } + ] + + return ( + + {contextHolder} +
+ +
+ + + + + + New Vendor + +
+ setNewVendorFormValues((prevValues) => ({ + ...prevValues, + ...changedValues + })) + } + initialValues={initialNewVendorForm} + > +
{steps[currentStep].content}
+ + + + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + +
+
+ ) +} + +NewVendor.propTypes = { + onOk: PropTypes.func.isRequired, + reset: PropTypes.bool +} + +export default NewVendor diff --git a/src/components/Dashboard/Management/Vendors/VendorInfo.jsx b/src/components/Dashboard/Management/Vendors/VendorInfo.jsx new file mode 100644 index 0000000..e692fdd --- /dev/null +++ b/src/components/Dashboard/Management/Vendors/VendorInfo.jsx @@ -0,0 +1,247 @@ +import React, { useState, useEffect } from 'react' +import { useLocation } from 'react-router-dom' +import axios from 'axios' +import { + Descriptions, + Spin, + Space, + Button, + message, + Typography, + Flex, + Form, + Input +} from 'antd' +import { + LoadingOutlined, + EditOutlined, + ReloadOutlined, + CheckOutlined, + CloseOutlined +} from '@ant-design/icons' +import IdText from '../../common/IdText' +import moment from 'moment' + +const { Title } = Typography + +const VendorInfo = () => { + const [vendorData, setVendorData] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const location = useLocation() + const [messageApi, contextHolder] = message.useMessage() + const vendorId = new URLSearchParams(location.search).get('vendorId') + const [isEditing, setIsEditing] = useState(false) + const [form] = Form.useForm() + const [fetchLoading, setFetchLoading] = useState(true) + + useEffect(() => { + if (vendorId) { + fetchVendorDetails() + } + }, [vendorId]) + + useEffect(() => { + if (vendorData) { + form.setFieldsValue({ + name: vendorData.name || '', + website: vendorData.website || '', + contact: vendorData.contact || '' + }) + } + }, [vendorData, form]) + + const fetchVendorDetails = async () => { + try { + setFetchLoading(true) + const response = await axios.get( + `http://localhost:8080/vendors/${vendorId}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true + } + ) + setVendorData(response.data) + setError(null) + } catch (err) { + setError('Failed to fetch vendor details') + messageApi.error('Failed to fetch vendor details') + } finally { + setFetchLoading(false) + } + } + + const startEditing = () => { + setIsEditing(true) + } + + const cancelEditing = () => { + form.setFieldsValue({ + name: vendorData?.name || '', + website: vendorData?.website || '', + contact: vendorData?.contact || '' + }) + setIsEditing(false) + } + + const updateInfo = async () => { + try { + const values = await form.validateFields() + setLoading(true) + + await axios.put( + `http://localhost:8080/vendors/${vendorId}`, + { + name: values.name, + website: values.website, + contact: values.contact + }, + { + headers: { + 'Content-Type': 'application/json' + }, + withCredentials: true + } + ) + + setVendorData({ ...vendorData, ...values }) + setIsEditing(false) + messageApi.success('Vendor information updated successfully') + } catch (err) { + if (err.errorFields) { + return + } + console.error('Failed to update vendor information:', err) + messageApi.error('Failed to update vendor information') + } finally { + setLoading(false) + } + } + + if (fetchLoading) { + return ( +
+ } /> +
+ ) + } + + if (error || !vendorData) { + return ( + +

{error || 'Vendor not found'}

+ +
+ ) + } + + return ( +
+ {contextHolder} + + + Vendor Information + + + {isEditing ? ( + <> +
+ ) +} + +export default VendorInfo diff --git a/src/components/Dashboard/Production/GCodeFiles.jsx b/src/components/Dashboard/Production/GCodeFiles.jsx new file mode 100644 index 0000000..b5e2b07 --- /dev/null +++ b/src/components/Dashboard/Production/GCodeFiles.jsx @@ -0,0 +1,325 @@ +// src/gcodefiles.js + +import React, { useEffect, useState, useContext, useCallback } from 'react' +import { useNavigate } from 'react-router-dom' +import axios from 'axios' +import moment from 'moment' + +import { + Table, + Badge, + Button, + Flex, + Space, + Modal, + Dropdown, + Typography, + message +} from 'antd' +import { createStyles } from 'antd-style' +import { + LoadingOutlined, + PlusOutlined, + DownloadOutlined, + ReloadOutlined, + InfoCircleOutlined +} from '@ant-design/icons' + +import { AuthContext } from '../../Auth/AuthContext' + +import NewGCodeFile from './GCodeFiles/NewGCodeFile' +import IdText from '../common/IdText' +import GCodeFileIcon from '../../Icons/GCodeFileIcon' + +const { Text } = Typography + +const useStyle = createStyles(({ css, token }) => { + const { antCls } = token + return { + customTable: css` + ${antCls}-table { + ${antCls}-table-container { + ${antCls}-table-body, + ${antCls}-table-content { + scrollbar-width: thin; + scrollbar-color: #eaeaea transparent; + scrollbar-gutter: stable; + } + } + } + ` + } +}) + +const GCodeFiles = () => { + const [messageApi, contextHolder] = message.useMessage() + const navigate = useNavigate() + const { styles } = useStyle() + + const [gcodeFilesData, setGCodeFilesData] = useState([]) + + const [newGCodeFileOpen, setNewGCodeFileOpen] = useState(false) + + const [loading, setLoading] = useState(true) + + const { authenticated } = useContext(AuthContext) + + const fetchGCodeFilesData = useCallback(async () => { + try { + const response = await axios.get('http://localhost:8080/gcodefiles', { + params: { + page: 1, + limit: 25 + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + }) + setGCodeFilesData(response.data) + setLoading(false) + //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count + } catch (error) { + if (error.response) { + messageApi.error( + 'Error updating printer details:', + error.response.status + ) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + }, [messageApi]) + + useEffect(() => { + if (authenticated) { + fetchGCodeFilesData() + } + }, [authenticated, fetchGCodeFilesData]) + + const getGCodeFileActionItems = (id) => { + return { + items: [ + { + label: 'Info', + key: 'info', + icon: + }, + { + label: 'Download', + key: 'download', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'info') { + navigate(`/production/gcodefiles/info?gcodeFileId=${id}`) + } else if (key === 'download') { + handleDownloadGCode( + id, + gcodeFilesData.find((file) => file._id === id)?.name + '.gcode' + ) + } + } + } + } + + // Column definitions + const columns = [ + { + title: '', + dataIndex: '', + key: '', + width: 40, + fixed: 'left', + render: () => + }, + { + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 200, + fixed: 'left', + render: (text) => {text} + }, + { + title: 'ID', + dataIndex: '_id', + key: 'id', + width: 165, + render: (text) => + }, + { + title: 'Filament', + dataIndex: 'filament', + key: 'filament', + width: 200, + render: (filament) => { + return + } + }, + { + title: 'Price / Cost', + dataIndex: 'price', + key: 'price', + width: 120, + render: (price) => { + return '£' + price.toFixed(2) + } + }, + { + title: 'Est. Print Time', + key: 'estimatedPrintingTimeNormalMode', + width: 140, + render: (text, record) => { + return `${record.gcodeFileInfo.estimatedPrintingTimeNormalMode}` + } + }, + { + title: 'Created At', + dataIndex: 'createdAt', + key: 'createdAt', + width: 180, + render: (createdAt) => { + if (createdAt) { + const formattedDate = moment(createdAt).format('YYYY-MM-DD HH:mm:ss') + return {formattedDate} + } else { + return 'n/a' + } + } + }, + { + title: 'Actions', + key: 'actions', + fixed: 'right', + width: 150, + render: (text, record) => { + return ( + + + + + ) + } + } + ] + + const handleDownloadGCode = async (id, fileName) => { + if (!authenticated) { + return + } + try { + const response = await axios.get( + `http://localhost:8080/gcodefiles/${id}/content`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + } + ) + + setLoading(false) + + const fileURL = window.URL.createObjectURL(new Blob([response.data])) + // Create an anchor element and simulate a click to download the file + const fileLink = document.createElement('a') + fileLink.href = fileURL + + fileLink.setAttribute('download', fileName) + document.body.appendChild(fileLink) + + // Simulate click to download the file + fileLink.click() + + // Clean up and remove the anchor element + fileLink.parentNode.removeChild(fileLink) + } catch (error) { + if (error.response) { + messageApi.error( + 'Error updating printer details:', + error.response.status + ) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + } + + const actionItems = { + items: [ + { + label: 'New GCodeFile', + key: 'newGCodeFile', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + fetchGCodeFilesData() + } else if (key === 'newGCodeFile') { + setNewGCodeFileOpen(true) + } + } + } + + return ( + <> + + {contextHolder} + + + + + +
}} + /> + + { + setNewGCodeFileOpen(false) + }} + > + { + setNewGCodeFileOpen(false) + fetchGCodeFilesData() + }} + reset={newGCodeFileOpen} + /> + + + ) +} + +export default GCodeFiles diff --git a/src/components/Dashboard/Production/GCodeFiles/EditGCodeFile.jsx b/src/components/Dashboard/Production/GCodeFiles/EditGCodeFile.jsx new file mode 100644 index 0000000..4803317 --- /dev/null +++ b/src/components/Dashboard/Production/GCodeFiles/EditGCodeFile.jsx @@ -0,0 +1,228 @@ +import React, { useEffect, useState, useContext } from 'react' +import axios from 'axios' +import PropTypes from 'prop-types' +import { + Form, + Input, + InputNumber, + Button, + message, + Spin, + Select, + Flex, + ColorPicker, + Upload, + Popconfirm +} from 'antd' +import { + LoadingOutlined, + UploadOutlined, + LinkOutlined +} from '@ant-design/icons' + +import { AuthContext } from '../../../Auth/AuthContext' + +const EditFilament = ({ id, onOk }) => { + const [messageApi, contextHolder] = message.useMessage() + + const [dataLoading, setDataLoading] = useState(false) + const [editFilamentLoading, setEditFilamentLoading] = useState(false) + + const [imageList, setImageList] = useState([]) + + const [editFilamentForm] = Form.useForm() + const [editFilamentFormValues, setEditFilamentFormValues] = useState({}) + + const { token } = useContext(AuthContext) + + useEffect(() => { + // Fetch printer details when the component mounts + const fetchFilamentDetails = async () => { + if (id) { + try { + setDataLoading(true) + const response = await axios.get( + `http://localhost:8080/filaments/${id}`, + { + headers: { + Authorization: `Bearer ${token}` + } + } + ) + setDataLoading(false) + editFilamentForm.setFieldsValue(response.data) // Set form values with fetched data + setEditFilamentFormValues(response.data) + } catch (error) { + messageApi.error('Error fetching printer details:' + error.message) + } + } + } + fetchFilamentDetails() + }, [id, editFilamentForm, token, messageApi]) + + const handleEditFilament = async () => { + setEditFilamentLoading(true) + try { + await axios.put( + `http://localhost:8080/filaments/${id}`, + editFilamentFormValues, + { + headers: { + Authorization: `Bearer ${token}` + } + } + ) + messageApi.success('Filament details updated successfully.') + onOk() + } catch (error) { + messageApi.error('Error updating filament details: ' + error.message) + } finally { + setEditFilamentLoading(false) + } + } + + const handleDeleteFilament = async () => { + try { + await axios.delete(`http://localhost:8080/filaments/${id}`, '', { + headers: { + Authorization: `Bearer ${token}` + } + }) + messageApi.success('Filament deleted successfully.') + onOk() + } catch (error) { + messageApi.error('Error updating filament details: ' + error.message) + } + } + + const handleImageUpload = ({ file, onSuccess }) => { + const reader = new FileReader() + reader.onload = () => { + onSuccess('ok') + } + reader.readAsDataURL(file) + } + + return ( + <> + {contextHolder} + } + size='large' + > +
+ setEditFilamentFormValues((prevValues) => ({ + ...prevValues, + ...changedValues + })) + } + > + + + + + + + + + + + { + if (!value) return '£' + return `£${value}` + }} + step={0.01} + style={{ width: '100%' }} + addonAfter='per kg' + /> + + + { + return '#' + color.toHex() + }} + > + + + + + + + + { + setImageList(fileList) + }} + > + + + + + } /> + + + } /> + + + + + + + + + + +
+ + ) +} + +EditFilament.propTypes = { + id: PropTypes.string.isRequired, + onOk: PropTypes.func.isRequired +} + +export default EditFilament diff --git a/src/components/Dashboard/Production/GCodeFiles/GCodeFileInfo.jsx b/src/components/Dashboard/Production/GCodeFiles/GCodeFileInfo.jsx new file mode 100644 index 0000000..0c33d83 --- /dev/null +++ b/src/components/Dashboard/Production/GCodeFiles/GCodeFileInfo.jsx @@ -0,0 +1,205 @@ +import React, { useState, useEffect } from 'react' +import { useLocation } from 'react-router-dom' +import axios from 'axios' +import { Descriptions, Spin, Space, Button, message, Badge } from 'antd' +import { LoadingOutlined, ReloadOutlined } from '@ant-design/icons' +import IdText from '../../common/IdText.jsx' +import moment from 'moment' +import { capitalizeFirstLetter } from '../../utils/Utils.js' + +const GCodeFileInfo = () => { + const [gcodeFileData, setGCodeFileData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const location = useLocation() + const [messageApi] = message.useMessage() + const gcodeFileId = new URLSearchParams(location.search).get('gcodeFileId') + + useEffect(() => { + if (gcodeFileId) { + fetchFilamentDetails() + } + }, [gcodeFileId]) + + const fetchFilamentDetails = async () => { + try { + setLoading(true) + const response = await axios.get( + `http://localhost:8080/gcodefiles/${gcodeFileId}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true + } + ) + setGCodeFileData(response.data) + setError(null) + } catch (err) { + setError('Failed to fetch GCodeFile details') + messageApi.error('Failed to fetch GCodeFile details') + } finally { + setLoading(false) + } + } + + if (loading) { + return ( +
+ } /> +
+ ) + } + + if (error || !gcodeFileData) { + return ( + +

{error || 'GCodeFile not found'}

+ +
+ ) + } + + return ( +
+ + + {gcodeFileData.id ? ( + + ) : ( + 'n/a' + )} + + + {(() => { + if (gcodeFileData.createdAt) { + return moment(gcodeFileData.createdAt.$date).format( + 'YYYY-MM-DD HH:mm:ss' + ) + } + return 'N/A' + })()} + + + {gcodeFileData.name || 'n/a'} + + + {gcodeFileData.gcodeFileInfo.estimatedPrintingTimeNormalMode || 'n/a'} + + + {gcodeFileData.filament ? ( + + ) : ( + 'n/a' + )} + + + {gcodeFileData.filament ? ( + + ) : ( + 'n/a' + )} + + + {(() => { + if (gcodeFileData.gcodeFileInfo.sparseInfillDensity) { + return gcodeFileData.gcodeFileInfo.sparseInfillDensity + } else { + return 'n/a' + } + })()} + + + {(() => { + if (gcodeFileData.gcodeFileInfo.sparseInfillPattern) { + return capitalizeFirstLetter( + gcodeFileData.gcodeFileInfo.sparseInfillPattern + ) + } else { + return 'n/a' + } + })()} + + + {(() => { + if (gcodeFileData.gcodeFileInfo.filamentUsedMm) { + return `${gcodeFileData.gcodeFileInfo.filamentUsedMm}mm` + } else { + return 'n/a' + } + })()} + + + {(() => { + if (gcodeFileData.gcodeFileInfo.filamentUsedG) { + return `${gcodeFileData.gcodeFileInfo.filamentUsedG}g` + } else { + return 'n/a' + } + })()} + + + {(() => { + if (gcodeFileData.gcodeFileInfo.nozzleTemperature) { + return `${gcodeFileData.gcodeFileInfo.nozzleTemperature}°` + } else { + return 'n/a' + } + })()} + + + {(() => { + if (gcodeFileData.gcodeFileInfo.hotPlateTemp) { + return `${gcodeFileData.gcodeFileInfo.hotPlateTemp}°` + } else { + return 'n/a' + } + })()} + + + {(() => { + if (gcodeFileData.gcodeFileInfo.filamentSettingsId) { + return `${gcodeFileData.gcodeFileInfo.filamentSettingsId.replaceAll('"', '')}` + } else { + return 'n/a' + } + })()} + + + {(() => { + if (gcodeFileData.gcodeFileInfo.printSettingsId) { + return `${gcodeFileData.gcodeFileInfo.printSettingsId.replaceAll('"', '')}` + } else { + return 'n/a' + } + })()} + + + {gcodeFileData.gcodeFileInfo.thumbnail ? ( + GCodeFile + ) : ( + 'n/a' + )} + + +
+ ) +} + +export default GCodeFileInfo diff --git a/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx b/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx new file mode 100644 index 0000000..06596ef --- /dev/null +++ b/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx @@ -0,0 +1,501 @@ +import PropTypes from 'prop-types' +import React, { useState, useContext } from 'react' +import axios from 'axios' +import { + capitalizeFirstLetter, + timeStringToMinutes +} from '../../utils/Utils.js' +import { + Form, + Input, + Button, + message, + Typography, + Flex, + Steps, + Divider, + Upload, + Descriptions, + Checkbox, + Spin +} from 'antd' +import { LoadingOutlined } from '@ant-design/icons' + +import { AuthContext } from '../../../Auth/AuthContext' + +import GCodeFileIcon from '../../../Icons/GCodeFileIcon' + +import FilamentSelect from '../../common/FilamentSelect' + +const { Dragger } = Upload + +const { Title } = Typography + +const initialNewGCodeFileForm = { + gcodeFileInfo: {}, + name: '', + printTimeMins: 0, + price: 0, + file: null, + material: null +} + +//const chunkSize = 5000 + +const NewGCodeFile = ({ onOk, reset }) => { + const [messageApi, contextHolder] = message.useMessage() + + const [newGCodeFileLoading, setNewGCodeFileLoading] = useState(false) + const [gcodeParsing, setGcodeParsing] = useState(false) + + const [filamentSelectFilter, setFilamentSelectFilter] = useState(null) + const [useFilamentSelectFilter, setUseFilamentSelectFilter] = useState(true) + + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + const [nextLoading, setNextLoading] = useState(false) + + const [newGCodeFileForm] = Form.useForm() + const [newGCodeFileFormValues, setNewGCodeFileFormValues] = useState( + initialNewGCodeFileForm + ) + + const [gcodeFile, setGCodeFile] = useState(null) + + const newGCodeFileFormUpdateValues = Form.useWatch([], newGCodeFileForm) + + const { token, authenticated } = useContext(AuthContext) + // eslint-disable-next-line + const fetchFilamentDetails = async () => { + if (!authenticated) { + return + } + if ( + newGCodeFileFormValues.filament && + newGCodeFileFormValues.gcodeFileInfo + ) { + try { + setNextLoading(true) + const response = await axios.get( + `http://localhost:8080/filaments/${newGCodeFileFormValues.filament}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + } + ) + setNextLoading(false) + + const price = + (response.data.price / 1000) * + newGCodeFileFormValues.gcodeFileInfo.filament_used_g // convert kg to g and multiply + + const printTimeMins = timeStringToMinutes( + newGCodeFileFormValues.gcodeFileInfo + .estimated_printing_time_normal_mode + ) + setNewGCodeFileFormValues({ + ...newGCodeFileFormValues, + price, + printTimeMins + }) + } catch (error) { + if (error.response) { + messageApi.error( + 'Error fetching filament data:', + error.response.status + ) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + } + } + + React.useEffect(() => { + newGCodeFileForm + .validateFields({ + validateOnly: true + }) + .then(() => setNextEnabled(true)) + .catch(() => setNextEnabled(false)) + }, [newGCodeFileForm, newGCodeFileFormUpdateValues]) + + const summaryItems = [ + { + key: 'name', + label: 'Name', + children: newGCodeFileFormValues.name + }, + { + key: 'price', + label: 'Price / Cost', + children: '£' + newGCodeFileFormValues.price.toFixed(2) + }, + { + key: 'sparse_infill_density', + label: 'Infill Density', + children: newGCodeFileFormValues.gcodeFileInfo.sparseInfillDensity + }, + { + key: 'sparse_infill_pattern', + label: 'Infill Pattern', + children: capitalizeFirstLetter( + newGCodeFileFormValues.gcodeFileInfo.sparseInfillPattern + ) + }, + { + key: 'layer_height', + label: 'Layer Height', + children: newGCodeFileFormValues.gcodeFileInfo.layerHeight + 'mm' + }, + { + key: 'filamentType', + label: 'Filament Material', + children: newGCodeFileFormValues.gcodeFileInfo.filamentType + }, + { + key: 'filamentUsedG', + label: 'Filament Used (g)', + children: newGCodeFileFormValues.gcodeFileInfo.filamentUsedG + 'g' + }, + { + key: 'filamentVendor', + label: 'Filament Brand', + children: newGCodeFileFormValues.gcodeFileInfo.filamentVendor + }, + + { + key: 'hotendTemperature', + label: 'Hotend Temperature', + children: newGCodeFileFormValues.gcodeFileInfo.nozzleTemperature + '°' + }, + { + key: 'bedTemperature', + label: 'Bed Temperature', + children: newGCodeFileFormValues.gcodeFileInfo.hotPlateTemp + '°' + }, + { + key: 'estimated_printing_time_normal_mode', + label: 'Est. Print Time', + children: + newGCodeFileFormValues.gcodeFileInfo.estimatedPrintingTimeNormalMode + } + ] + + React.useEffect(() => { + if (reset) { + setCurrentStep(0) + newGCodeFileForm.resetFields() + } + }, [reset, newGCodeFileForm]) + + const handleNewGCodeFileUpload = async (id) => { + setNewGCodeFileLoading(true) + const formData = new FormData() + formData.append('gcodeFile', gcodeFile) + try { + await axios.post( + `http://localhost:8080/gcodefiles/${id}/content`, + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: `Bearer ${token}` + } + } + ) + messageApi.success('Finished uploading!') + resetForm() + onOk() + } catch (error) { + messageApi.error('Error creating new gcode file: ' + error.message) + } finally { + setNewGCodeFileLoading(false) + } + } + + const handleNewGCodeFile = async () => { + setNewGCodeFileLoading(true) + try { + const request = await axios.post( + `http://localhost:8080/gcodefiles`, + newGCodeFileFormValues, + { + headers: { + Authorization: `Bearer ${token}` + } + } + ) + messageApi.info('New G Code file created successfully. Uploading...') + handleNewGCodeFileUpload(request.data._id) + } catch (error) { + messageApi.error('Error creating new gcode file: ' + error.message) + } finally { + setNewGCodeFileLoading(false) + } + } + + const handleGetGCodeFileInfo = async (file) => { + try { + setGcodeParsing(true) + // Create a FormData object to send the file + const formData = new FormData() + formData.append('gcodeFile', file) + + // Call the API to extract and parse the config block + const request = await axios.post( + `http://localhost:8080/gcodefiles/content`, + formData, + { + withCredentials: true // Important for including cookies + }, + { + headers: { + Accept: 'application/json' + } + } + ) + + // Parse the API response + const parsedConfig = await request.data + + // Update state with the parsed config from API + setNewGCodeFileFormValues({ + ...newGCodeFileFormValues, + gcodeFileInfo: parsedConfig + }) + + console.log(parsedConfig) + + // Update filter settings if filament info is available + if (parsedConfig.filament_type && parsedConfig.filament_diameter) { + setFilamentSelectFilter({ + type: parsedConfig.filament_type, + diameter: parsedConfig.filament_diameter + }) + } + const fileName = file.name.replace(/\.[^/.]+$/, '') + newGCodeFileForm.setFieldValue('name', fileName) + setNewGCodeFileFormValues((prev) => ({ + ...prev, + name: fileName + })) + setGCodeFile(file) + setGcodeParsing(false) + setCurrentStep(currentStep + 1) + } catch (error) { + console.error('Error getting G-code file info:', error) + } + } + + const resetForm = () => { + newGCodeFileForm.setFieldsValue(initialNewGCodeFileForm) + setNewGCodeFileFormValues(initialNewGCodeFileForm) + setGCodeFile(null) + setGcodeParsing(false) + setCurrentStep(0) + } + + const steps = [ + { + title: 'Upload', + key: 'upload', + content: ( + <> + (Array.isArray(e) ? e : e && e.fileList)} + > + { + handleGetGCodeFileInfo(file) + setTimeout(() => { + onSuccess('ok') + }, 0) + }} + > + + {gcodeParsing == true ? ( + + } + /> + ) : ( + <> +

+ +

+

+ Click or drag gcode file here. +

+

+ Supported file extentions: .gcode, .gco, .g +

+ + )} +
+
+
+ + ) + }, + { + title: 'Details', + key: 'details', + content: ( + <> + + + + + + + + + + + { + setUseFilamentSelectFilter(e.target.checked) + }} + > + Filter + + + + + ) + }, + { + title: 'Summary', + key: 'done', + content: ( + <> + + + + + ) + } + ] + + return ( + + {contextHolder} +
+ +
+ + + + + + New G Code File + +
+ setNewGCodeFileFormValues((prevValues) => ({ + ...prevValues, + ...changedValues + })) + } + initialValues={initialNewGCodeFileForm} + > +
{steps[currentStep].content}
+ + + + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + +
+
+ ) +} + +NewGCodeFile.propTypes = { + reset: PropTypes.bool.isRequired, + onOk: PropTypes.func.isRequired +} + +export default NewGCodeFile diff --git a/src/components/Dashboard/Production/Overview.jsx b/src/components/Dashboard/Production/Overview.jsx new file mode 100644 index 0000000..30ab559 --- /dev/null +++ b/src/components/Dashboard/Production/Overview.jsx @@ -0,0 +1,273 @@ +import React, { useEffect, useState, useContext } from 'react' +import { + Descriptions, + Progress, + Space, + Flex, + Alert, + Statistic, + Typography +} from 'antd' +import { + PrinterOutlined, + LoadingOutlined, + CheckCircleOutlined, + PlayCircleOutlined +} from '@ant-design/icons' +import axios from 'axios' +import { SocketContext } from '../context/SocketContext' + +const { Title } = Typography + +const ProductionOverview = () => { + const [stats, setStats] = useState({ + totalPrinters: 0, + activePrinters: 0, + totalPrintJobs: 0, + activePrintJobs: 0, + completedPrintJobs: 0, + printerStatus: { + idle: 0, + printing: 0, + error: 0, + offline: 0 + } + }) + + const { socket } = useContext(SocketContext) + + useEffect(() => { + const fetchStats = async () => { + try { + const [printersResponse, printJobsResponse] = await Promise.all([ + axios.get('/api/printers'), + axios.get('/api/print-jobs') + ]) + + const printers = printersResponse.data + const printJobs = printJobsResponse.data + + const printerStatus = printers.reduce((acc, printer) => { + acc[printer.status] = (acc[printer.status] || 0) + 1 + return acc + }, {}) + + setStats({ + totalPrinters: printers.length, + activePrinters: printers.filter((p) => p.status === 'printing') + .length, + totalPrintJobs: printJobs.length, + activePrintJobs: printJobs.filter((job) => job.status === 'printing') + .length, + completedPrintJobs: printJobs.filter( + (job) => job.status === 'completed' + ).length, + printerStatus + }) + } catch (error) { + console.error('Error fetching production stats:', error) + } + } + + fetchStats() + + if (socket) { + socket.on('printerUpdate', fetchStats) + socket.on('printJobUpdate', fetchStats) + } + + return () => { + if (socket) { + socket.off('printerUpdate', fetchStats) + socket.off('printJobUpdate', fetchStats) + } + } + }, [socket]) + + const getPrinterStatusPercentage = (status) => { + const count = stats.printerStatus[status] || 0 + if (stats.totalPrinters > 0) { + return Math.round((count / stats.totalPrinters) * 100) + } + return 0 + } + + const getCompletionRate = () => { + if (stats.totalPrintJobs > 0) { + return Math.round((stats.completedPrintJobs / stats.totalPrintJobs) * 100) + } + return 0 + } + + return ( + + + + Overview + + + + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + + + + + + Printer Statistics + + + + + + Total Printers + + } + > + {stats.totalPrinters} + + + Active Printers + + } + > + {stats.activePrinters} + + + `${stats.printerStatus.printing || 0} Printing`} + /> + `${stats.printerStatus.idle || 0} Idle`} + /> + `${stats.printerStatus.error || 0} Error`} + /> + + + + + + Job Statistics + + + + + + Total Print Jobs + + } + > + {stats.totalPrintJobs} + + + Active Print Jobs + + } + > + {stats.activePrintJobs} + + + Completed Print Jobs + + } + > + {stats.completedPrintJobs} + + + 'Completion Rate'} + /> + + + + + + + ) +} + +export default ProductionOverview diff --git a/src/components/Dashboard/Production/PrintJobs.jsx b/src/components/Dashboard/Production/PrintJobs.jsx new file mode 100644 index 0000000..df502fa --- /dev/null +++ b/src/components/Dashboard/Production/PrintJobs.jsx @@ -0,0 +1,381 @@ +// src/PrintJobs.js + +import React, { useEffect, useState, useCallback, useContext } from 'react' +import { useNavigate } from 'react-router-dom' +import axios from 'axios' +import moment from 'moment' +import { + Table, + Button, + Flex, + Space, + Modal, + Dropdown, + message, + notification, + Input, + Typography +} from 'antd' +import { createStyles } from 'antd-style' +import { + EditOutlined, + PlusOutlined, + LoadingOutlined, + InfoCircleOutlined, + PlayCircleOutlined, + ReloadOutlined, + FilterOutlined, + CloseOutlined, + CheckCircleOutlined, + CloseCircleOutlined, + PauseCircleOutlined, + QuestionCircleOutlined +} from '@ant-design/icons' + +import { AuthContext } from '../../Auth/AuthContext' +import { SocketContext } from '../context/SocketContext' +import NewPrintJob from './PrintJobs/NewPrintJob' +import JobState from '../common/JobState' +import SubJobCounter from '../common/SubJobCounter' +import IdText from '../common/IdText' + +const { Text } = Typography + +const useStyle = createStyles(({ css, token }) => { + const { antCls } = token + return { + customTable: css` + ${antCls}-table { + ${antCls}-table-container { + ${antCls}-table-body, + ${antCls}-table-content { + scrollbar-width: thin; + scrollbar-color: #eaeaea transparent; + scrollbar-gutter: stable; + } + } + } + ` + } +}) + +const PrintJobs = () => { + const { styles } = useStyle() + const [messageApi, contextHolder] = message.useMessage() + const [notificationApi, notificationContextHolder] = + notification.useNotification() + const navigate = useNavigate() + const [printJobsData, setPrintJobsData] = useState([]) + + const [showFilters, setShowFilters] = useState(false) + const [filters, setFilters] = useState({ + id: '', + state: '' + }) + + const [newPrintJobOpen, setNewPrintJobOpen] = useState(false) + const [loading, setLoading] = useState(true) + + const { authenticated } = useContext(AuthContext) + const { socket } = useContext(SocketContext) + + const handleDeployPrintJob = (printJobId) => { + if (socket) { + messageApi.info(`Print job ${printJobId} deployment initiated`) + socket.emit('server.job_queue.deploy', { printJobId }, (response) => { + if (response == false) { + notificationApi.error({ + message: 'Print job deployment failed', + description: 'Please try again later' + }) + } else { + notificationApi.success({ + message: 'Print job deployment initiated', + description: 'Please wait for the print job to start' + }) + } + }) + navigate(`/production/printjobs/info?printJobId=${printJobId}`) + } else { + messageApi.error('Socket connection not available') + } + } + + const fetchPrintJobsData = useCallback(async () => { + if (!authenticated) { + return + } + try { + const response = await axios.get('http://localhost:8080/printjobs', { + params: { + page: 1, + limit: 25 + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true + }) + setLoading(false) + setPrintJobsData(response.data) + } catch (error) { + setLoading(false) + if (error.response) { + messageApi.error( + 'Error fetching print jobs data:', + error.response.status + ) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + }, [authenticated, messageApi]) + + useEffect(() => { + // Fetch initial data + if (authenticated) { + fetchPrintJobsData() + } + }, [authenticated, fetchPrintJobsData]) + + const handleFilterChange = (field, value) => { + setFilters((prev) => ({ + ...prev, + [field]: value + })) + } + + const filteredData = printJobsData.filter((printJob) => { + const matchesId = printJob.id + .toLowerCase() + .includes(filters.id.toLowerCase()) + const matchesState = printJob.state.type + .toLowerCase() + .includes(filters.state.toLowerCase()) + return matchesId && matchesState + }) + + // Column definitions + const columns = [ + { + title: '', + dataIndex: '', + key: '', + width: 40, + fixed: 'left', + render: () => + }, + { + title: 'GCode File Name', + dataIndex: 'gcodeFile', + key: 'gcodeFileName', + width: 200, + fixed: 'left', + render: (gcodeFile) => {gcodeFile.name} + }, + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 165, + render: (text) => + }, + { + title: 'State', + key: 'state', + width: 240, + render: (record) => { + return + } + }, + { + title: , + key: 'complete', + width: 70, + render: (record) => { + return + } + }, + { + title: , + key: 'queued', + width: 70, + render: (record) => { + return + } + }, + { + title: , + key: 'failed', + width: 70, + render: (record) => { + return + } + }, + { + title: , + key: 'draft', + width: 70, + render: (record) => { + return + } + }, + { + title: 'Started At', + dataIndex: 'startedAt', + key: 'startedAt', + width: 180, + render: (startedAt) => { + if (startedAt) { + const formattedDate = moment(startedAt).format('YYYY-MM-DD HH:mm:ss') + return {formattedDate} + } else { + return 'n/a' + } + } + }, + { + title: 'Actions', + key: 'operation', + fixed: 'right', + width: 150, + render: (record) => { + return ( + + {record.state.type === 'draft' ? ( + + + + ) + } + } + ] + + const getPrintJobActionItems = (printJobId) => { + return { + items: [ + { + label: 'Info', + key: 'info', + icon: + }, + { + label: 'Edit', + key: 'edit', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'edit') { + showNewPrintJobModal(printJobId) + } else if (key === 'info') { + navigate(`/production/printjobs/info?printJobId=${printJobId}`) + } + } + } + } + + const actionItems = { + items: [ + { + label: 'New Print Job', + key: 'newPrintJob', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'newPrintJob') { + showNewPrintJobModal() + } else if (key === 'reloadList') { + fetchPrintJobsData() + } + } + } + + const showNewPrintJobModal = () => { + setNewPrintJobOpen(true) + } + + return ( + <> + {notificationContextHolder} + + {contextHolder} + + + + +
}} + scroll={{ y: 'calc(100vh - 270px)' }} + /> + + { + setNewPrintJobOpen(false) + }} + > + { + setNewPrintJobOpen(false) + fetchPrintJobsData() + }} + reset={newPrintJobOpen} + /> + + + ) +} + +export default PrintJobs diff --git a/src/components/Dashboard/Production/PrintJobs/NewPrintJob.jsx b/src/components/Dashboard/Production/PrintJobs/NewPrintJob.jsx new file mode 100644 index 0000000..bfd2651 --- /dev/null +++ b/src/components/Dashboard/Production/PrintJobs/NewPrintJob.jsx @@ -0,0 +1,253 @@ +import React, { useState } from 'react' +import axios from 'axios' +import { + Form, + Button, + message, + Typography, + Flex, + Steps, + Col, + Row, + Divider, + Checkbox, + Descriptions, + InputNumber +} from 'antd' +import PropTypes from 'prop-types' + +import GCodeFileSelect from '../../common/GCodeFileSelect' +import PrinterSelect from '../../common/PrinterSelect' + +const { Title, Text } = Typography + +const initialNewPrintJobForm = {} + +const NewPrintJob = ({ onOk, reset }) => { + NewPrintJob.propTypes = { + onOk: PropTypes.func.isRequired, + reset: PropTypes.bool.isRequired + } + + const [messageApi, contextHolder] = message.useMessage() + const [newPrintJobLoading, setNewPrintJobLoading] = useState(false) + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + const [newPrintJobForm] = Form.useForm() + const [newPrintJobFormValues, setNewPrintJobFormValues] = useState( + initialNewPrintJobForm + ) + const [useAnyPrinter, setUseAnyPrinter] = useState(true) + + const newPrintJobFormUpdateValues = Form.useWatch([], newPrintJobForm) + + React.useEffect(() => { + newPrintJobForm + .validateFields({ + validateOnly: true + }) + .then(() => setNextEnabled(true)) + .catch(() => setNextEnabled(false)) + }, [newPrintJobForm, newPrintJobFormUpdateValues]) + + const summaryItems = [ + { + key: 'quantity', + label: 'Quantity', + children: newPrintJobFormValues.quantity + } + ] + + if (!useAnyPrinter && newPrintJobFormValues.printers) { + const printerList = newPrintJobFormValues.printers + + summaryItems.splice(2, 0, { + key: 'printer', + label: 'Printers', + children: `${printerList.length} printer(s) selected` + }) + } + + React.useEffect(() => { + if (reset) { + newPrintJobForm.resetFields() + } + }, [reset, newPrintJobForm]) + + const handleUseAnyPrinterChecked = (e) => { + const checked = e.target.checked + setUseAnyPrinter(checked) + if (checked === true) { + newPrintJobForm.resetFields(['printer']) + setNewPrintJobFormValues({ ...newPrintJobFormValues, printer: null }) + } + } + + const handleNewPrintJob = async () => { + setNewPrintJobLoading(true) + try { + await axios.post( + `http://localhost:8080/printjobs`, + newPrintJobFormValues, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + } + ) + messageApi.success('New print job created successfully.') + onOk() + } catch (error) { + messageApi.error('Error creating new print job: ' + error.message) + } finally { + setNewPrintJobLoading(false) + } + } + + const steps = [ + { + title: 'Required', + key: 'required', + content: ( + <> + + Please select a G Code File: + + + + + + + + + + + Use any printer configured. + + + + + + + ) + }, + { + title: 'Summary', + key: 'done', + content: ( + + + + ) + } + ] + + return ( + + {contextHolder} + + + + + + + + + + New PrintJob + +
+ setNewPrintJobFormValues((prevValues) => ({ + ...prevValues, + ...changedValues + })) + } + initialValues={initialNewPrintJobForm} + > + {steps[currentStep].content} + + + + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + +
+ + + ) +} + +export default NewPrintJob diff --git a/src/components/Dashboard/Production/PrintJobs/PrintJobInfo.jsx b/src/components/Dashboard/Production/PrintJobs/PrintJobInfo.jsx new file mode 100644 index 0000000..eabfdcd --- /dev/null +++ b/src/components/Dashboard/Production/PrintJobs/PrintJobInfo.jsx @@ -0,0 +1,174 @@ +import React, { useState, useEffect, useContext } from 'react' +import { useLocation } from 'react-router-dom' +import axios from 'axios' +import { + Descriptions, + Spin, + Space, + Button, + message, + Progress, + Typography +} from 'antd' +import { LoadingOutlined, ReloadOutlined } from '@ant-design/icons' +import moment from 'moment' +import JobState from '../../common/JobState' +import IdText from '../../common/IdText' +import SubJobsTree from '../../common/SubJobsTree' +import { SocketContext } from '../../context/SocketContext' + +const { Title } = Typography + +const PrintJobInfo = () => { + const [printJobData, setPrintJobData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const location = useLocation() + const [messageApi] = message.useMessage() + const printJobId = new URLSearchParams(location.search).get('printJobId') + const { socket } = useContext(SocketContext) + + useEffect(() => { + if (printJobId) { + fetchPrintJobDetails() + } + }, [printJobId]) + + useEffect(() => { + if (socket && printJobId) { + socket.on('notify_job_update', (updateData) => { + if (updateData.id === printJobId) { + setPrintJobData((prevData) => { + if (!prevData) return prevData + return { + ...prevData, + state: updateData.state, + ...updateData + } + }) + } + }) + } + + return () => { + if (socket) { + socket.off('notify_job_update') + } + } + }, [socket, printJobId]) + + const fetchPrintJobDetails = async () => { + try { + setLoading(true) + const response = await axios.get( + `http://localhost:8080/printjobs/${printJobId}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + } + ) + setPrintJobData(response.data) + setError(null) + } catch (err) { + setError('Failed to fetch print job details') + messageApi.error('Failed to fetch print job details') + } finally { + setLoading(false) + } + } + + if (loading) { + return ( +
+ } /> +
+ ) + } + + if (error || !printJobData) { + return ( + +

{error || 'Print job not found'}

+ +
+ ) + } + + return ( +
+ + + + + + + + + {printJobData.gcodeFile?.name || 'Not specified'} + + + + + + {printJobData.quantity || 1} + + + {(() => { + if (printJobData.createdat) { + return moment(printJobData.createdat.$date).format( + 'YYYY-MM-DD HH:mm:ss' + ) + } + return 'N/A' + })()} + + + {(() => { + if (printJobData.started_at) { + return moment(printJobData.started_at.$date).format( + 'YYYY-MM-DD HH:mm:ss' + ) + } + return 'N/A' + })()} + + {printJobData.state.type === 'printing' && ( + + + + )} + + {printJobData.printers?.length > 0 ? ( + {printJobData.printers.length} printers assigned + ) : ( + 'Any available printer' + )} + + + + Sub Job Information + + +
+ ) +} + +export default PrintJobInfo diff --git a/src/components/Dashboard/Production/Printers.jsx b/src/components/Dashboard/Production/Printers.jsx new file mode 100644 index 0000000..fe7b75d --- /dev/null +++ b/src/components/Dashboard/Production/Printers.jsx @@ -0,0 +1,329 @@ +// src/Printers.js + +import React, { useEffect, useState, useContext, useCallback } from 'react' +import { useNavigate } from 'react-router-dom' +import axios from 'axios' +import { + Table, + Button, + message, + Dropdown, + Space, + Flex, + Input, + Tag, + Modal +} from 'antd' +import { createStyles } from 'antd-style' +import { + InfoCircleOutlined, + EditOutlined, + ControlOutlined, + LoadingOutlined, + ReloadOutlined, + FilterOutlined, + CloseOutlined, + PlusOutlined, + PrinterOutlined +} from '@ant-design/icons' + +import { AuthContext } from '../../Auth/AuthContext' +import PrinterState from '../common/PrinterState' +import NewPrinter from './Printers/NewPrinter' +import IdText from '../common/IdText' + +const useStyle = createStyles(({ css, token }) => { + const { antCls } = token + return { + customTable: css` + ${antCls}-table { + ${antCls}-table-container { + ${antCls}-table-body, + ${antCls}-table-content { + scrollbar-width: thin; + scrollbar-color: #eaeaea transparent; + scrollbar-gutter: stable; + } + } + } + ` + } +}) + +const Printers = () => { + const { styles } = useStyle() + const [printerData, setPrinterData] = useState([]) + + const [messageApi] = message.useMessage() + const [showFilters, setShowFilters] = useState(false) + + const { authenticated } = useContext(AuthContext) + const [loading, setLoading] = useState(false) + const [filters, setFilters] = useState({ + printerName: '', + host: '', + tags: '' + }) + + const [newPrinterOpen, setNewPrinterOpen] = useState(false) + + const navigate = useNavigate() + + const fetchPrintersData = useCallback(async () => { + try { + const response = await axios.get('http://localhost:8080/printers', { + params: { + page: 1, + limit: 25 + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + }) + setLoading(false) + setPrinterData(response.data) + } catch (error) { + if (error.response) { + messageApi.error('Error fetching printer data:', error.response.status) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + }, [messageApi]) + + const handleFilterChange = (field, value) => { + setFilters((prev) => ({ + ...prev, + [field]: value + })) + } + + const getPrinterActionItems = (printerId) => { + return { + items: [ + { + label: 'Control', + key: 'control', + icon: + }, + { + type: 'divider' + }, + { + label: 'Info', + key: 'info', + icon: + }, + { + label: 'Edit', + key: 'edit', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'info') { + navigate(`/production/printers/info?printerId=${printerId}`) + } else if (key === 'control') { + navigate(`/production/printers/control?printerId=${printerId}`) + } + } + } + } + + useEffect(() => { + if (authenticated) { + // Fetch initial data + fetchPrintersData() + } + }, [fetchPrintersData, authenticated]) + + const filteredData = printerData.filter((printer) => { + const matchesName = printer.printerName + .toLowerCase() + .includes(filters.printerName.toLowerCase()) + const matchesHost = printer.moonraker.host + .toLowerCase() + .includes(filters.host.toLowerCase()) + const matchesTags = + !filters.tags || + (printer.tags && + printer.tags.some((tag) => + tag.toLowerCase().includes(filters.tags.toLowerCase()) + )) + return matchesName && matchesHost && matchesTags + }) + + const actionItems = { + items: [ + { + label: 'New Printer', + key: 'newPrinter', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + fetchPrintersData() + } else if (key === 'newPrinter') { + setNewPrinterOpen(true) + } + } + } + + // Column definitions + const columns = [ + { + title: '', + dataIndex: '', + key: '', + width: 40, + fixed: 'left', + render: () => + }, + { + title: 'Name', + dataIndex: 'printerName', + key: 'printerName', + width: 200, + fixed: 'left' + }, + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 165, + render: (text) => + }, + + { + title: 'State', + key: 'state', + width: 240, + render: (record) => { + return ( + + ) + } + }, + { + title: 'Tags', + dataIndex: 'tags', + key: 'tags', + width: 170, + render: (tags) => { + if (!tags || !Array.isArray(tags)) return null + return ( + + {tags.map((tag, index) => ( + + {tag} + + ))} + + ) + } + }, + { + title: 'Actions', + key: 'operation', + fixed: 'right', + width: 150, + render: (record) => { + return ( + + + + + ) + } + } + ] + + return ( + <> + + + + + +
}} + scroll={{ y: 'calc(100vh - 270px)' }} + /> + { + setNewPrinterOpen(false) + }} + > + { + setNewPrinterOpen(false) + fetchPrintersData() + }} + reset={newPrinterOpen} + /> + + + + ) +} + +export default Printers diff --git a/src/components/Dashboard/Production/Printers/ChangeFillament.jsx b/src/components/Dashboard/Production/Printers/ChangeFillament.jsx new file mode 100644 index 0000000..ca6363d --- /dev/null +++ b/src/components/Dashboard/Production/Printers/ChangeFillament.jsx @@ -0,0 +1,333 @@ +import React, { useState, useContext, useRef } from 'react' +import axios from 'axios' +import { + Form, + Input, + Button, + message, + Typography, + Flex, + Steps, + Col, + Row, + Divider, + Upload, + Descriptions +} from 'antd' + +import { AuthContext } from '../../Auth/AuthContext' + +import GCodeFileIcon from '../../Icons/GCodeFileIcon' + +import FilamentSelect from '../common/FilamentSelect' +import PrinterSelect from '../common/PrinterSelect' + +const { Dragger } = Upload + +const { Title, Text } = Typography + +const initialNewGCodeFileForm = { + name: '', + brand: '', + type: '', + price: 0, + color: '#FFFFFF', + diameter: '1.75', + image: null, + url: '', + barcode: '' +} + +const chunkSize = 5000 + +const NewGCodeFile = ({ onOk, reset }) => { + const [messageApi, contextHolder] = message.useMessage() + + const [newGCodeFileLoading, setNewGCodeFileLoading] = useState(false) + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + + const [newGCodeFileForm] = Form.useForm() + const [newGCodeFileFormValues, setNewGCodeFileFormValues] = useState( + initialNewGCodeFileForm + ) + + const [imageList, setImageList] = useState([]) + + const [gcode, setGCode] = useState('') + + const newGCodeFileFormUpdateValues = Form.useWatch([], newGCodeFileForm) + + const { token } = useContext(AuthContext) + + const gcodePreviewRef = useRef(null) + + React.useEffect(() => { + newGCodeFileForm + .validateFields({ + validateOnly: true + }) + .then(() => setNextEnabled(true)) + .catch(() => setNextEnabled(false)) + }, [newGCodeFileForm, newGCodeFileFormUpdateValues]) + + const summaryItems = [ + { + key: 'name', + label: 'Name', + children: newGCodeFileFormValues.name + }, + { + key: 'brand', + label: 'Brand', + children: newGCodeFileFormValues.brand + }, + { + key: 'type', + label: 'Material', + children: () => { + if (newGCodeFileFormValues.filament != null) { + return '1 selected.' + } else { + return '0 selected.' + } + } + }, + { + key: 'price', + label: 'Price', + children: '£' + newGCodeFileFormValues.price + ' per kg' + } + ] + + React.useEffect(() => { + if (reset) { + newGCodeFileForm.resetFields() + } + }, [reset, newGCodeFileForm]) + + const handleNewGCodeFile = async () => { + setNewGCodeFileLoading(true) + try { + await axios.post( + `http://localhost:8080/gcodefiles`, + newGCodeFileFormValues, + { + headers: { + Authorization: `Bearer ${token}` + } + } + ) + messageApi.success('New G Code file created successfully.') + onOk() + } catch (error) { + messageApi.error('Error creating new gcode file: ' + error.message) + } finally { + setNewGCodeFileLoading(false) + } + } + + const getBase64 = (file) => { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.readAsDataURL(file) + reader.onload = () => resolve(reader.result) + reader.onerror = (error) => reject(error) + }) + } + + const handleGCodeUpload = (file) => { + const reader = new FileReader() + reader.onload = () => { + console.log(reader.result) + setGCode(reader.result) + } + reader.readAsText(file) + } + + const steps = [ + { + title: 'Details', + key: 'details', + content: ( + <> + + Please provide the following information: + + + + + + + + + + ) + }, + { + title: 'Upload', + key: 'upload', + content: ( + <> + (Array.isArray(e) ? e : e && e.fileList)} + > + { + handleGCodeUpload(file) + setTimeout(() => { + onSuccess('ok') + }, 0) + }} + > +

+ +

+

+ Click or gcode instruction file here. +

+

+ Supported file extentions: .gcode, .gco, .g +

+
+
+ + ) + }, + { + title: 'Targets', + key: 'targets', + content: ( + <> + + + Please provide at least one target to deploy this G Code file: + + + + + + + ) + }, + { + title: 'Summary', + key: 'done', + content: ( + + + + ) + } + ] + + return ( + + {contextHolder} + + + + + + + + + + New G Code File + +
+ setNewGCodeFileFormValues((prevValues) => ({ + ...prevValues, + ...changedValues + })) + } + initialValues={initialNewGCodeFileForm} + > + {steps[currentStep].content} + + + + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + +
+ + + ) +} + +export default NewGCodeFile diff --git a/src/components/Dashboard/Production/Printers/ControlPrinter.jsx b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx new file mode 100644 index 0000000..453c4b1 --- /dev/null +++ b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx @@ -0,0 +1,357 @@ +import React, { useState, useContext, useCallback, useEffect } from 'react' +import axios from 'axios' +import { useLocation } from 'react-router-dom' +import { + Button, + message, + Spin, + Flex, + Card, + Dropdown, + Space, + Descriptions, + Progress +} from 'antd' +import { + LoadingOutlined, + PlayCircleOutlined, + ExclamationCircleOutlined, + ReloadOutlined, + EditOutlined, + PauseCircleOutlined, + CloseCircleOutlined +} from '@ant-design/icons' + +import { SocketContext } from '../../context/SocketContext' + +import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel' +import PrinterMovementPanel from '../../common/PrinterMovementPanel' +import PrinterState from '../../common/PrinterState' +import { AuthContext } from '../../../Auth/AuthContext' +import PrinterSubJobsTree from '../../common/PrinterJobsTree' +import IdText from '../../common/IdText' + +// Helper function to parse query parameters +const useQuery = () => { + return new URLSearchParams(useLocation().search) +} + +const ControlPrinter = () => { + const [messageApi] = message.useMessage() + const query = useQuery() + const printerId = query.get('printerId') + + const [printerData, setPrinterData] = useState(null) + const [initialized, setInitialized] = useState(false) + + const { socket } = useContext(SocketContext) + const { authenticated } = useContext(AuthContext) + + // Fetch printer details when the component mounts + const fetchPrinterDetails = useCallback(async () => { + if (printerId) { + try { + const response = await axios.get( + `http://localhost:8080/printers/${printerId}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + } + ) + + setPrinterData(response.data) + } catch (error) { + if (error.response) { + messageApi.error( + 'Error fetching printer data:', + error.response.status + ) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + } + }, [printerId, messageApi]) + + // Add WebSocket event listener for real-time updates + useEffect(() => { + if (socket && !initialized && printerId) { + setInitialized(true) + socket.on('notify_printer_update', (statusUpdate) => { + setPrinterData((prevData) => { + if (statusUpdate?.id === printerId) { + return { + ...prevData, + ...statusUpdate + } + } + return prevData + }) + }) + } + return () => { + if (socket && initialized) { + socket.off('notify_printer_update') + } + } + }, [socket, initialized, printerId]) + + function handleEmergencyStop() { + console.log('Emergency stop button clicked') + socket.emit('printer.emergency_stop', { printerId }) + } + + useEffect(() => { + if (authenticated) { + fetchPrinterDetails() + } + }, [authenticated, fetchPrinterDetails]) + + const actionItems = { + items: [ + { + label: 'Resume Print', + key: 'resumePrint', + icon: + }, + { + label: 'Pause Print', + key: 'pausePrint', + icon: + }, + { + label: 'Cancel Print', + key: 'cancelPrint', + icon: + }, + { + type: 'divider' + }, + { + label: 'Start Queue', + key: 'startQueue', + disabled: + printerData?.state?.type === 'printing' || + printerData?.state?.type === 'deploying' || + printerData?.state?.type === 'paused' || + printerData?.state?.type === 'error', + + icon: + }, + { + label: 'Pause Queue', + key: 'pauseQueue', + icon: + }, + { + type: 'divider' + }, + { + label: 'Restart Host', + key: 'restartHost', + icon: + }, + { + label: 'Restart Firmware', + key: 'restartFirmware', + icon: + }, + { + type: 'divider' + }, + { + label: 'Edit Printer', + key: 'edit', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'restartHost') { + socket.emit('printer.restart', { printerId }) + } else if (key === 'restartFirmware') { + socket.emit('printer.firmware_restart', { printerId }) + } else if (key === 'resumePrint') { + socket.emit('printer.print.resume', { printerId }) + } else if (key === 'pausePrint') { + socket.emit('printer.print.pause', { printerId }) + } else if (key === 'cancelPrint') { + socket.emit('printer.print.cancel', { printerId }) + } else if (key === 'startQueue') { + socket.emit('server.job_queue.start', { printerId }) + } else if (key === 'pauseQueue') { + socket.emit('server.job_queue.pause', { printerId }) + } + } + } + + return ( + <> + + + + + + + + + {printerData ? ( + + ) : ( + } size='small' /> + )} + + + + + + + +
+ {printerData ? ( + + + + + {printerData.printerName} + + + {printerData.currentJob?.id ? ( + + ) : ( + 'n/a' + )} + + + {printerData.currentJob?.gcodeFile?.name || 'n/a'} + + + {printerData.currentJob?.gcodeFile ? ( + + ) : ( + 'n/a' + )} + + + + {(() => { + if ( + printerData.currentJob?.gcodeFile?.gcodeFileInfo + .estimatedPrintingTimeNormalMode + ) { + return `${ + printerData.currentJob.gcodeFile.gcodeFileInfo + .estimatedPrintingTimeNormalMode + }` + } + return 'n/a' + })()} + + + + {(() => { + if ( + printerData?.currentJob?.gcodeFile.gcodeFileInfo + .printSettingsId + ) { + return `${printerData.currentJob.gcodeFile.gcodeFileInfo.printSettingsId.replaceAll('"', '')}` + } else { + return 'n/a' + } + })()} + + + {printerData.currentSubJob?.state.type === 'printing' && ( + + + + )} + + + + + + + + + + + + + + ) : ( + } size='large' /> + )} +
+
+ + ) +} + +export default ControlPrinter diff --git a/src/components/Dashboard/Production/Printers/NewPrinter.jsx b/src/components/Dashboard/Production/Printers/NewPrinter.jsx new file mode 100644 index 0000000..e10097b --- /dev/null +++ b/src/components/Dashboard/Production/Printers/NewPrinter.jsx @@ -0,0 +1,565 @@ +import React, { useState, useContext, useEffect, useCallback } from 'react' +import axios from 'axios' +import { + Form, + Button, + message, + Typography, + Flex, + Steps, + Divider, + Input, + Select, + Space, + Descriptions, + List, + InputNumber, + notification, + Progress, + Modal, + Radio +} from 'antd' +import { + SearchOutlined, + SettingOutlined, + EditOutlined +} from '@ant-design/icons' +import PropTypes from 'prop-types' +import { SocketContext } from '../../context/SocketContext' + +const { Title } = Typography + +const initialNewPrinterForm = { + moonraker: { + protocol: 'ws', + host: '', + port: '', + apiKey: '' + } +} + +const NewPrinter = ({ onOk, reset }) => { + NewPrinter.propTypes = { + onOk: PropTypes.func.isRequired, + reset: PropTypes.bool.isRequired + } + + const { socket } = useContext(SocketContext) + const [messageApi, contextHolder] = message.useMessage() + const [notificationApi, notificationContextHolder] = + notification.useNotification() + const [newPrinterLoading, setNewPrinterLoading] = useState(false) + const [currentStep, setCurrentStep] = useState(0) + const [nextEnabled, setNextEnabled] = useState(false) + const [newPrinterForm] = Form.useForm() + const [newPrinterFormValues, setNewPrinterFormValues] = useState( + initialNewPrinterForm + ) + const [discoveredPrinters, setDiscoveredPrinters] = useState([]) + const [discovering, setDiscovering] = useState(false) + const [showManualSetup, setShowManualSetup] = useState(false) + const [scanPort, setScanPort] = useState(7125) + const [scanProtocol, setScanProtocol] = useState('ws') + const [editingHostname, setEditingHostname] = useState(null) + const [hostnameInput, setHostnameInput] = useState('') + const [initialized, setInitialized] = useState(false) + + const newPrinterFormUpdateValues = Form.useWatch([], newPrinterForm) + + useEffect(() => { + newPrinterForm + .validateFields({ + validateOnly: true + }) + .then(() => { + if (currentStep === 0) { + const moonraker = newPrinterForm.getFieldValue('moonraker') + setNextEnabled( + !!(moonraker?.protocol && moonraker?.host && moonraker?.port) + ) + } else if (currentStep === 1) { + const printerName = newPrinterForm.getFieldValue('printerName') + setNextEnabled(!!printerName) + } else { + setNextEnabled(true) + } + }) + .catch(() => setNextEnabled(false)) + }, [newPrinterForm, newPrinterFormUpdateValues, currentStep]) + + const summaryItems = [ + { + key: 'name', + label: 'Name', + children: newPrinterFormValues.printerName + }, + { + key: 'protocol', + label: 'Protocol', + children: newPrinterFormValues.moonraker?.protocol + }, + { + key: 'host', + label: 'Host', + children: newPrinterFormValues.moonraker?.host + }, + { + key: 'port', + label: 'Port', + children: newPrinterFormValues.moonraker?.port + } + ] + + useEffect(() => { + if (reset) { + newPrinterForm.resetFields() + } + }, [reset, newPrinterForm]) + + const handlePrinterSelect = (printer) => { + newPrinterForm.setFieldsValue({ + moonraker: { + protocol: printer.protocol, + host: printer.host, + port: printer.port + } + }) + setNewPrinterFormValues({ + ...newPrinterFormValues, + moonraker: { + protocol: printer.protocol, + host: printer.host, + port: printer.port + } + }) + } + + const handleHostnameEdit = (printer, newHostname) => { + if (newHostname && newHostname.trim() !== '') { + const updatedPrinter = { + ...printer, + host: newHostname.trim() + } + setDiscoveredPrinters((prev) => + prev.map((p) => (p.host === printer.host ? updatedPrinter : p)) + ) + setEditingHostname(null) + setHostnameInput('') + } + } + + const showEditHostnameDialog = (printer) => { + setEditingHostname(printer.host) + setHostnameInput(printer.host) + } + + const handleNewPrinter = async () => { + setNewPrinterLoading(true) + try { + await axios.post( + 'http://localhost:8080/printers', + { + ...newPrinterFormValues + }, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true + } + ) + messageApi.success('New printer added successfully.') + onOk() + } catch (error) { + messageApi.error('Error adding new printer: ' + error.message) + } finally { + setNewPrinterLoading(false) + } + } + + const notifyScanNetworkFound = useCallback( + (data) => { + const newPrinter = { + protocol: scanProtocol, + host: data.hostname || data.ip, + port: scanPort + } + notificationApi.info({ + message: 'Printer Found', + description: `Printer found: ${data.hostname || data.ip}!` + }) + setDiscoveredPrinters((prev) => [...prev, newPrinter]) + }, + [scanProtocol, scanPort, notificationApi] + ) + + const notifyScanNetworkComplete = useCallback( + (data) => { + setDiscovering(false) + notificationApi.destroy('network-scan') + if (data == false) { + messageApi.error('Error discovering printers!') + } else { + messageApi.success('Finished discovering printers!') + } + }, + [messageApi, notificationApi] + ) + + const notifyScanNetworkProgress = useCallback( + (data) => { + notificationApi.info({ + message: 'Scanning Network', + description: ( +
+
+ Scanning IP: {data.currentIP} +
+ +
+ ), + duration: 0, + key: 'network-scan', + icon: null, + placement: 'bottomRight', + style: { + width: 360 + }, + className: 'network-scan-notification', + closeIcon: null, + onClose: () => {}, + btn: null + }) + }, + [notificationApi] + ) + + const discoverPrinters = useCallback(() => { + if (!discovering) { + setDiscovering(true) + setDiscoveredPrinters([]) + messageApi.info('Discovering printers...') + socket.off('notify_scan_network_found') + socket.off('notify_scan_network_progress') + socket.off('notify_scan_network_complete') + + socket.on('notify_scan_network_found', notifyScanNetworkFound) + socket.on('notify_scan_network_progress', notifyScanNetworkProgress) + socket.on('notify_scan_network_complete', notifyScanNetworkComplete) + + socket.emit('bridge.scan_network.start', { + port: scanPort, + protocol: scanProtocol + }) + } + }, [ + discovering, + socket, + scanPort, + scanProtocol, + messageApi, + notifyScanNetworkFound, + notifyScanNetworkProgress, + notifyScanNetworkComplete + ]) + + useEffect(() => { + setInitialized(true) + if (!initialized) { + discoverPrinters() + } + }, [initialized, discoverPrinters]) + + const stopDiscovery = () => { + if (discovering) { + setDiscovering(false) + notificationApi.destroy('network-scan') + messageApi.info('Stopping discovery...') + socket.off('notify_scan_network_found') + socket.off('notify_scan_network_progress') + socket.off('notify_scan_network_complete') + socket.emit('bridge.scan_network.stop', (response) => { + if (response == false) { + messageApi.error('Error stopping discovery!') + } + }) + } + } + + const handlePortChange = (value) => { + stopDiscovery() + setScanPort(value) + } + + const handleProtocolChange = (value) => { + stopDiscovery() + setScanProtocol(value) + } + + const steps = [ + { + title: 'Discovery', + key: 'discovery', + content: ( + <> + + {!showManualSetup ? ( + <> + + + + setHostnameInput(e.target.value)} + placeholder='Enter host' + autoFocus + /> + + + + ) : ( + <> + + + + + + + + + + + + + + + + )} + + + ) + }, + { + title: 'Required', + key: 'required', + content: ( + <> + + + + + ) + }, + { + title: 'Summary', + key: 'summary', + content: ( + + + + ) + } + ] + + return ( + + {contextHolder} + {notificationContextHolder} + +
+ +
+ + + + + + New Printer + +
+ setNewPrinterFormValues((prevValues) => ({ + ...prevValues, + ...changedValues + })) + } + initialValues={initialNewPrinterForm} + > +
{steps[currentStep].content}
+ + + + {currentStep < steps.length - 1 && ( + + )} + {currentStep === steps.length - 1 && ( + + )} + + +
+
+ ) +} + +export default NewPrinter diff --git a/src/components/Dashboard/Production/Printers/PrinterInfo.jsx b/src/components/Dashboard/Production/Printers/PrinterInfo.jsx new file mode 100644 index 0000000..6f0060b --- /dev/null +++ b/src/components/Dashboard/Production/Printers/PrinterInfo.jsx @@ -0,0 +1,359 @@ +import React, { useState, useEffect } from 'react' +import { useLocation } from 'react-router-dom' +import axios from 'axios' +import { + Descriptions, + Spin, + Space, + Button, + message, + Tag, + Typography, + Flex, + Form, + Input, + InputNumber, + Select +} from 'antd' +import { + LoadingOutlined, + ReloadOutlined, + EditOutlined, + CheckOutlined, + CloseOutlined, + PlusOutlined +} from '@ant-design/icons' +import PrinterState from '../../common/PrinterState' +import IdText from '../../common/IdText' +import PrinterSubJobsList from '../../common/PrinterJobsTree' + +const { Title } = Typography + +const PrinterInfo = () => { + const [printerData, setPrinterData] = useState(null) + const [fetchLoading, setFetchLoading] = useState(true) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const location = useLocation() + const printerId = new URLSearchParams(location.search).get('printerId') + const [messageApi, contextHolder] = message.useMessage() + const [isEditing, setIsEditing] = useState(false) + const [form] = Form.useForm() + + useEffect(() => { + if (printerId) { + fetchPrinterDetails() + } + }, [printerId]) + + useEffect(() => { + if (printerData) { + form.setFieldsValue(printerData) + } + }, [printerData, form]) + + const fetchPrinterDetails = async () => { + try { + setFetchLoading(true) + const response = await axios.get( + `http://localhost:8080/printers/${printerId}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true + } + ) + setPrinterData(response.data) + setError(null) + } catch (err) { + setError('Failed to fetch printer details') + messageApi.error('Failed to fetch printer details') + } finally { + setFetchLoading(false) + } + } + + const startEditing = () => { + setIsEditing(true) + } + + const cancelEditing = () => { + setIsEditing(false) + fetchPrinterDetails() + } + + const updatePrinterInfo = async () => { + try { + const values = await form.validateFields() + setLoading(true) + + await axios.put(`http://localhost:8080/printers/${printerId}`, values, { + headers: { + 'Content-Type': 'application/json' + }, + withCredentials: true + }) + setPrinterData((prev) => ({ ...prev, ...values })) + setIsEditing(false) + messageApi.success('Printer information updated successfully') + } catch (err) { + if (err.errorFields) { + // This is a form validation error + return + } + console.error('Failed to update printer information:', err) + messageApi.error('Failed to update printer information') + } finally { + setLoading(false) + } + } + + const handleTagClose = (removedTag) => { + const newTags = printerData.tags.filter((tag) => tag !== removedTag) + setPrinterData((prev) => ({ ...prev, tags: newTags })) + } + + const handleTagAdd = () => { + const input = form.getFieldValue('newTag') + if (input) { + const newTag = input.trim() + if (newTag && !printerData.tags.includes(newTag)) { + setPrinterData((prev) => ({ ...prev, tags: [...prev.tags, newTag] })) + form.setFieldValue('newTag', '') + } + } + } + + if (fetchLoading) { + return ( +
+ } /> +
+ ) + } + + if (error || !printerData) { + return ( + +

{error || 'Printer not found'}

+ +
+ ) + } + + return ( +
+ {contextHolder} + + + Printer Information + + + {isEditing ? ( + <> + + + + ) : ( + + )} + + + +
+ + {/* Read-only fields */} + + + + + {new Date(printerData.updatedAt).toLocaleString()} + + + {/* Editable fields */} + + {isEditing ? ( + + + + ) : ( + printerData.printerName || 'n/a' + )} + + + + {isEditing ? ( + + + + ) : ( + printerData.moonraker?.host || 'n/a' + )} + + + + {isEditing ? ( + + + + ) : ( + printerData.moonraker.port + )} + + + + {isEditing ? ( + + + +
+ ) +} + +export default PrinterInfo diff --git a/src/components/Dashboard/common/FilamentSelect.jsx b/src/components/Dashboard/common/FilamentSelect.jsx new file mode 100644 index 0000000..3d43077 --- /dev/null +++ b/src/components/Dashboard/common/FilamentSelect.jsx @@ -0,0 +1,170 @@ +// FilamentSelect.js +import { TreeSelect, Badge } from 'antd' +import React, { useEffect, useState, useContext, useRef } from 'react' +import PropTypes from 'prop-types' +import axios from 'axios' +import { AuthContext } from '../../Auth/AuthContext' + +const propertyOrder = ['diameter', 'type', 'brand'] + +const FilamentSelect = ({ onChange, filter, useFilter }) => { + const [filamentsTreeData, setFilamentsTreeData] = useState([]) + const { token } = useContext(AuthContext) + const tokenRef = useRef(token) + const [loading, setLoading] = useState(true) + + const fetchFilamentsData = async (property, filter) => { + setLoading(true) + try { + const response = await axios.get('http://localhost:8080/filaments', { + params: { + ...filter, + property + }, + headers: { + Authorization: `Bearer ${tokenRef.current}` + } + }) + setLoading(false) + return response.data + //setPagination({ ...pagination, total: response.data.totalItems }); // Update total count + } catch (err) { + console.error(err) + } + } + + const getFilter = (node) => { + var filter = {} + var currentId = node.id + while (currentId != 0) { + const currentNode = filamentsTreeData.filter( + (treeData) => treeData['id'] === currentId + )[0] + filter[propertyOrder[currentNode.propertyId]] = + currentNode.value.split('-')[0] + currentId = currentNode.pId + } + return filter + } + + const generateFilamentTreeNodes = async (node = null, filter = null) => { + if (!node) { + return + } + + if (filter === null) { + filter = getFilter(node) + } + + const filamentData = await fetchFilamentsData(null, filter) + + let newNodeList = [] + + for (var i = 0; i < filamentData.length; i++) { + const filament = filamentData[i] + const random = Math.random().toString(36).substring(2, 6) + + const newNode = { + id: random, + pId: node.id, + value: filament._id, + key: filament._id, + title: , + isLeaf: true + } + + newNodeList.push(newNode) + } + + setFilamentsTreeData(filamentsTreeData.concat(newNodeList)) + } + + const generateFilamentCategoryTreeNodes = async (node = null) => { + var filter = {} + + var propertyId = 0 + + if (!node) { + node = {} + node.id = 0 + } else { + filter = getFilter(node) + propertyId = node.propertyId + 1 + } + + const propertyName = propertyOrder[propertyId] + + const propertyData = await fetchFilamentsData(propertyName, filter) + + const newNodeList = [] + + for (var i = 0; i < propertyData.length; i++) { + const property = propertyData[i][propertyName] + const random = Math.random().toString(36).substring(2, 6) + + const newNode = { + id: random, + pId: node.id, + value: property + '-' + random, + key: property + '-' + random, + propertyId: propertyId, + title: property, + isLeaf: false, + selectable: false + } + + newNodeList.push(newNode) + } + + setFilamentsTreeData(filamentsTreeData.concat(newNodeList)) + } + + const handleFilamentsTreeLoad = async (node) => { + if (node) { + if (node.propertyId !== propertyOrder.length - 1) { + await generateFilamentCategoryTreeNodes(node) + } else { + await generateFilamentTreeNodes(node) // End of properties + } + } else { + await generateFilamentCategoryTreeNodes(null) // First property + } + } + + useEffect(() => { + setFilamentsTreeData([]) + }, [token, filter, useFilter]) + + useEffect(() => { + if (filamentsTreeData.length === 0) { + if (useFilter === true) { + generateFilamentTreeNodes({ id: 0 }, filter) + } else { + handleFilamentsTreeLoad(null) + } + } + }, [filamentsTreeData]) + + return ( + + ) +} + +FilamentSelect.propTypes = { + onChange: PropTypes.func.isRequired, + filter: PropTypes.object, + useFilter: PropTypes.bool +} + +FilamentSelect.defaultProps = { + filter: {}, + useFilter: false +} + +export default FilamentSelect diff --git a/src/components/Dashboard/common/GCodeFileSelect.jsx b/src/components/Dashboard/common/GCodeFileSelect.jsx new file mode 100644 index 0000000..c605842 --- /dev/null +++ b/src/components/Dashboard/common/GCodeFileSelect.jsx @@ -0,0 +1,206 @@ +// GCodeFileSelect.js +import PropTypes from 'prop-types' +import { TreeSelect, Badge, Space, message } from 'antd' +import React, { useEffect, useState, useContext } from 'react' +import axios from 'axios' +import GCodeFileIcon from '../../Icons/GCodeFileIcon' +import { AuthContext } from '../../Auth/AuthContext' + +const propertyOrder = ['filament.diameter', 'filament.type', 'filament.brand'] + +const GCodeFileSelect = ({ onChange, filter, useFilter }) => { + const [gcodeFilesTreeData, setGCodeFilesTreeData] = useState(null) + const [loading, setLoading] = useState(true) + const [searchValue, setSearchValue] = useState('') + const [messageApi] = message.useMessage() + + const { authenticated } = useContext(AuthContext) + + const fetchGCodeFilesData = async (property, filter, search) => { + if (!authenticated) { + return + } + setLoading(true) + try { + const response = await axios.get('http://localhost:8080/gcodefiles', { + params: { + ...filter, + search, + property + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + }) + setLoading(false) + return response.data + // setPagination({ ...pagination, total: response.data.totalItems }); // Update total count + } catch (error) { + if (error.response) { + // For other errors, show a message + messageApi.error('Error fetching GCode files:', error.response.status) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + } + + const getFilter = (node) => { + const filter = {} + let currentId = node.id + while (currentId != 0) { + const currentNode = gcodeFilesTreeData.filter( + (treeData) => treeData['id'] === currentId + )[0] + filter[propertyOrder[currentNode.propertyId]] = + currentNode.value.split('-')[0] + currentId = currentNode.pId + } + return filter + } + + const generateGCodeFileTreeNodes = async (node = null, filter = null) => { + if (!node) { + return + } + + if (filter === null) { + filter = getFilter(node) + } + + let search = null + if (searchValue != '') { + search = searchValue + } + + const gcodeFileData = await fetchGCodeFilesData(null, filter, search) + + let newNodeList = [] + + for (var i = 0; i < gcodeFileData.length; i++) { + const gcodeFile = gcodeFileData[i] + const random = Math.random().toString(36).substring(2, 6) + + const newNode = { + id: random, + pId: node.id, + value: gcodeFile._id, + key: gcodeFile._id, + title: ( + + + + + ), + isLeaf: true + } + + newNodeList.push(newNode) + } + return newNodeList + } + + const generateGCodeFileCategoryTreeNodes = async (node = null) => { + var filter = {} + + var propertyId = 0 + + if (!node) { + node = {} + node.id = 0 + } else { + filter = getFilter(node) + propertyId = node.propertyId + 1 + } + + const propertyName = propertyOrder[propertyId] + + const propertyData = await fetchGCodeFilesData(propertyName, filter) + + const newNodeList = [] + + for (var i = 0; i < propertyData.length; i++) { + const property = + propertyData[i][propertyName.split('.')[0]][propertyName.split('.')[1]] + const random = Math.random().toString(36).substring(2, 6) + + const newNode = { + id: random, + pId: node.id, + value: property + '-' + random, + key: property + '-' + random, + propertyId: propertyId, + title: property, + isLeaf: false, + selectable: false + } + + newNodeList.push(newNode) + } + + return newNodeList + } + + const handleGCodeFilesTreeLoad = async (node) => { + if (node) { + if (node.propertyId !== propertyOrder.length - 1) { + setGCodeFilesTreeData( + gcodeFilesTreeData.concat( + await generateGCodeFileCategoryTreeNodes(node) + ) + ) + } else { + setGCodeFilesTreeData( + gcodeFilesTreeData.concat(await generateGCodeFileTreeNodes(node)) + ) // End of properties + } + } else { + setGCodeFilesTreeData(await generateGCodeFileCategoryTreeNodes(null)) // First property + } + } + + const handleGCodeFilesSearch = (value) => { + setSearchValue(value) + setGCodeFilesTreeData(null) + } + + useEffect(() => { + setGCodeFilesTreeData([]) + }, [filter, useFilter]) + + useEffect(() => { + if (gcodeFilesTreeData === null) { + if (useFilter === true || searchValue != '') { + setGCodeFilesTreeData(generateGCodeFileTreeNodes({ id: 0 }, filter)) + } else { + handleGCodeFilesTreeLoad(null) + } + } + }, [gcodeFilesTreeData]) + + return ( + + ) +} + +GCodeFileSelect.propTypes = { + onChange: PropTypes.func.isRequired, + filter: PropTypes.string.isRequired, + useFilter: PropTypes.bool.isRequired +} + +export default GCodeFileSelect diff --git a/src/components/Dashboard/common/IdText.jsx b/src/components/Dashboard/common/IdText.jsx new file mode 100644 index 0000000..1321d45 --- /dev/null +++ b/src/components/Dashboard/common/IdText.jsx @@ -0,0 +1,127 @@ +// PrinterSelect.js +import React from 'react' +import PropTypes from 'prop-types' +import { Flex, Typography, Button, Tooltip, message } from 'antd' +import { useNavigate } from 'react-router-dom' +import { CopyOutlined } from '@ant-design/icons' + +const { Text, Link } = Typography + +const IdText = ({ + id, + type, + showCopy = true, + longId = true, + showHyperlink = false +}) => { + const [messageApi, contextHolder] = message.useMessage() + const navigate = useNavigate() + + var prefix = 'UNK' + var hyperlink = '#' + + switch (type) { + case 'printer': + prefix = 'PRN' + hyperlink = `/production/printers/info?printerId=${id}` + break + case 'filament': + prefix = 'FIL' + hyperlink = `/management/filaments/info?filamentId=${id}` + break + case 'spool': + prefix = 'SPL' + hyperlink = `/inventory/spool/info?spoolId=${id}` + break + case 'gcodeFile': + prefix = 'GCF' + hyperlink = `/production/gcodefiles/info?gcodeFileId=${id}` + break + case 'job': + prefix = 'JOB' + hyperlink = `/production/printjobs/info?printJobId=${id}` + break + case 'part': + prefix = 'PRT' + hyperlink = `/management/parts/info?partId=${id}` + break + case 'product': + prefix = 'PRD' + hyperlink = `/management/products/info?productId=${id}` + break + case 'vendor': + prefix = 'VEN' + hyperlink = `/management/vendors/info?vendorId=${id}` + break + case 'subjob': + prefix = 'SJB' + hyperlink = `#` + break + default: + hyperlink = `#` + prefix = 'UNK' + } + + id = id.toString().toUpperCase() + var displayId = prefix + ':' + id + var copyId = prefix + ':' + id + + if (longId == false) { + displayId = prefix + ':' + id.toString().slice(-6) + } + + return ( + + {contextHolder} + + {showHyperlink && ( + { + if (showHyperlink) { + navigate(hyperlink) + } + }} + > + + {displayId} + + + )} + + {!showHyperlink && ( + + {displayId} + + )} + {showCopy && ( + + + + ) + } + + return ( + + + + ) +} + +PrinterJobsTree.propTypes = { + subJobs: PropTypes.arrayOf( + PropTypes.shape({ + state: PropTypes.object.isRequired, + _id: PropTypes.string.isRequired, + printer: PropTypes.string.isRequired, + printJob: PropTypes.shape({ + state: PropTypes.object.isRequired, + _id: PropTypes.string.isRequired, + printers: PropTypes.arrayOf(PropTypes.string).isRequired, + createdAt: PropTypes.string.isRequired, + updatedAt: PropTypes.string.isRequired, + startedAt: PropTypes.string.isRequired, + gcodeFile: PropTypes.string.isRequired, + quantity: PropTypes.number.isRequired, + subJobs: PropTypes.arrayOf(PropTypes.string).isRequired + }).isRequired, + subJobId: PropTypes.string.isRequired, + number: PropTypes.number.isRequired, + createdAt: PropTypes.string.isRequired, + updatedAt: PropTypes.string.isRequired + }) + ) +} + +export default PrinterJobsTree diff --git a/src/components/Dashboard/common/PrinterMovementPanel.jsx b/src/components/Dashboard/common/PrinterMovementPanel.jsx new file mode 100644 index 0000000..13f88cb --- /dev/null +++ b/src/components/Dashboard/common/PrinterMovementPanel.jsx @@ -0,0 +1,256 @@ +// PrinterMovementPanel.js +import React, { useContext, useState } from 'react' +import { + Flex, + Space, + InputNumber, + Button, + Radio, + Dropdown, + Card, + message // eslint-disable-line +} from 'antd' +import { + ArrowUpOutlined, + ArrowLeftOutlined, + HomeOutlined, + ArrowRightOutlined, + ArrowDownOutlined +} from '@ant-design/icons' +import { SocketContext } from '../context/SocketContext' +import UnloadIcon from '../../Icons/UnloadIcon' +import PropTypes from 'prop-types' +import LevelBedIcon from '../../Icons/LevelBedIcon' + +const PrinterMovementPanel = ({ printerId }) => { + const [posValue, setPosValue] = useState(10) + const [rateValue, setRateValue] = useState(1000) + const { socket } = useContext(SocketContext) + + //const messageApi = message.useMessage() + + const handlePosRadioChange = (e) => { + const value = e.target.value + setPosValue(value) // Update posValue state when radio button changes + } + + const handlePosInputChange = (value) => { + setPosValue(value) // Update posValue state when input changes + } + + const handleRateInputChange = (value) => { + setRateValue(value) // Update rateValue state when input changes + } + + const handleHomeAxisClick = (axis) => { + if (socket) { + console.log('Homeing Axis:', axis) + socket.emit('printer.gcode.script', { + printerId, + script: `G28 ${axis}` + }) + } + } + + const handleMoveAxisClick = (axis, minus) => { + const distanceValue = !minus ? posValue * -1 : posValue + if (socket) { + console.log('Moving Axis:', axis, distanceValue) + socket.emit('printer.gcode.script', { + printerId, + script: `_CLIENT_LINEAR_MOVE ${axis}=${distanceValue} F=${rateValue}` + }) + } + //sendCommand('moveAxis', { axis, pos, rate }) + } + + const handleLevelBedClick = () => { + //sendCommand('levelBed') + } + + const handleUnloadFilamentClick = () => { + if (socket) { + socket.emit('printer.gcode.script', { + printerId, + script: `UNLOAD_FILAMENT TEMP=` + }) + } + } + + const homeAxisButtonItems = [ + { + key: 'homeXYZ', + label: 'Home XYZ', + onClick: () => handleHomeAxisClick('ALL') + }, + { + key: 'homeXY', + label: 'Home XY', + onClick: () => handleHomeAxisClick('X Y') + }, + { + key: 'homeX', + label: 'Home X', + onClick: () => handleHomeAxisClick('X') + }, + { + key: 'homeY', + label: 'Home Y', + onClick: () => handleHomeAxisClick('Y') + }, + { + key: 'homeZ', + label: 'Home Z', + onClick: () => handleHomeAxisClick('Z') + } + ] + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + 0.1 + 1 + 10 + 100 + + + + `${value} mm`} + parser={(value) => value?.replace(' mm', '')} + onChange={handlePosInputChange} + placeholder='10 mm' + name='posInput' + style={{ flexGrow: 1 }} + /> + `${value} mm/s`} + parser={(value) => value?.replace(' mm/s', '')} + onChange={handleRateInputChange} + placeholder='100 mm/s' + name='rateInput' + style={{ flexGrow: 1 }} + /> + + + +
+ ) +} + +PrinterMovementPanel.propTypes = { + printerId: PropTypes.string.isRequired +} + +export default PrinterMovementPanel diff --git a/src/components/Dashboard/common/PrinterSelect.jsx b/src/components/Dashboard/common/PrinterSelect.jsx new file mode 100644 index 0000000..030a730 --- /dev/null +++ b/src/components/Dashboard/common/PrinterSelect.jsx @@ -0,0 +1,115 @@ +// PrinterSelect.js +import PropTypes from 'prop-types' +import { TreeSelect, message, Tag } from 'antd' +import React, { useEffect, useState, useContext } from 'react' +import axios from 'axios' +import PrinterState from './PrinterState' +import { AuthContext } from '../../Auth/AuthContext' + +const PrinterSelect = ({ onChange, disabled, checkable }) => { + const [printersData, setPrintersData] = useState([]) + const [loading, setLoading] = useState(true) + const [messageApi] = message.useMessage() + + const { authenticated } = useContext(AuthContext) + + const fetchPrintersData = async () => { + if (!authenticated) { + return + } + setLoading(true) + + try { + const response = await axios.get('http://localhost:8080/printers', { + headers: { + Accept: 'application/json' + }, + withCredentials: true // Important for including cookies + }) + setLoading(false) + return response.data + } catch (error) { + if (error.response) { + // For other errors, show a message + messageApi.error('Error fetching printers data:', error.response.status) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + } + } + + const generatePrinterItems = async () => { + const printerData = await fetchPrintersData() + + // Create a map to store tags and their printers + const tagMap = new Map() + + // Add printers to their respective tag groups + printerData.forEach((printer) => { + if (printer.tags && printer.tags.length > 0) { + printer.tags.forEach((tag) => { + if (!tagMap.has(tag)) { + tagMap.set(tag, []) + } + tagMap.get(tag).push(printer) + }) + } else { + // If no tags, add to "Untagged" group + if (!tagMap.has('Untagged')) { + tagMap.set('Untagged', []) + } + tagMap.get('Untagged').push(printer) + } + }) + + // Convert the map to tree data structure + const treeData = Array.from(tagMap.entries()).map(([tag, printers]) => ({ + title: tag === 'Untagged' ? tag : {tag}, + value: `tag-${tag}`, + key: `tag-${tag}`, + children: printers.map((printer) => ({ + title: ( + + ), + value: printer._id, + key: printer._id + })) + })) + + setPrintersData(treeData) + } + + useEffect(() => { + if (printersData.length === 0) { + generatePrinterItems() + } + }, []) + + return ( + + ) +} + +PrinterSelect.propTypes = { + onChange: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired, + checkable: PropTypes.bool +} + +export default PrinterSelect diff --git a/src/components/Dashboard/common/PrinterState.jsx b/src/components/Dashboard/common/PrinterState.jsx new file mode 100644 index 0000000..e0dc1e4 --- /dev/null +++ b/src/components/Dashboard/common/PrinterState.jsx @@ -0,0 +1,189 @@ +// PrinterSelect.js +import PropTypes from 'prop-types' +import { Badge, Progress, Flex, Space, Tag, Typography, Button } from 'antd' +import React, { useState, useContext, useEffect } from 'react' +import { SocketContext } from '../context/SocketContext' +import { + CloseOutlined, + PauseOutlined, + CaretRightOutlined +} from '@ant-design/icons' + +const PrinterState = ({ + printer, + showProgress = true, + showStatus = true, + showPrinterName = true, + showControls = true +}) => { + const { socket } = useContext(SocketContext) + const [badgeStatus, setBadgeStatus] = useState('unknown') + const [badgeText, setBadgeText] = useState('Unknown') + const [currentState, setCurrentState] = useState( + printer?.state || { + type: 'unknown', + progress: 0 + } + ) + const [initialized, setInitialized] = useState(false) + const { Text } = Typography + + useEffect(() => { + if (socket && !initialized && printer?.id) { + setInitialized(true) + socket.on('notify_printer_update', (statusUpdate) => { + if (statusUpdate?.id === printer.id && statusUpdate?.state) { + setCurrentState(statusUpdate.state) + } + }) + } + return () => { + if (socket && initialized) { + socket.off('notify_printer_update') + } + } + }, [socket, initialized, printer?.id]) + + useEffect(() => { + switch (currentState.type) { + case 'online': + setBadgeStatus('success') + setBadgeText('Online') + break + case 'standby': + setBadgeStatus('success') + setBadgeText('Standby') + break + case 'complete': + setBadgeStatus('success') + setBadgeText('Complete') + break + case 'offline': + setBadgeStatus('default') + setBadgeText('Offline') + break + case 'shutdown': + setBadgeStatus('default') + setBadgeText('Shutdown') + break + case 'initializing': + setBadgeStatus('warning') + setBadgeText('Initializing') + break + case 'printing': + setBadgeStatus('processing') + setBadgeText('Printing') + break + case 'paused': + setBadgeStatus('warning') + setBadgeText('Paused') + break + case 'cancelled': + setBadgeStatus('warning') + setBadgeText('Cancelled') + break + case 'loading': + setBadgeStatus('processing') + setBadgeText('Uploading') + break + case 'processing': + setBadgeStatus('processing') + setBadgeText('Processing') + break + case 'ready': + setBadgeStatus('success') + setBadgeText('Ready') + break + case 'error': + setBadgeStatus('error') + setBadgeText('Error') + break + default: + setBadgeStatus('default') + setBadgeText(currentState.type) + } + }, [currentState]) + + return ( + + {showPrinterName && {printer.printerName}} + {showStatus && ( + + + + + {badgeText} + + + + )} + {showProgress && + (currentState.type === 'printing' || + currentState.type === 'deploying') ? ( + + ) : null} + {showControls && currentState.type === 'printing' ? ( + + + + + + + )} + + )} + + {temperatureData.heatedBed && ( + + + Heated Bed: {temperatureData.heatedBed.current}°C /{' '} + {temperatureData.heatedBed.target}°C + + + {showControls === true && ( + + + setHeatedBedTemperature(value)} + onPressEnter={() => + handleSetTemperatureClick( + 'heater_bed', + heatedBedTemperature + ) + } + /> + + + + + )} + + )} + {showMoreInfo === true && ( + + )} +
+ ) : ( + + } size='large' /> + + )} + + ) +} + +PrinterTemperaturePanel.propTypes = { + printerId: PropTypes.string.isRequired, + showControls: PropTypes.bool, + showMoreInfo: PropTypes.bool +} + +export default PrinterTemperaturePanel diff --git a/src/components/Dashboard/common/ProductionSidebar.jsx b/src/components/Dashboard/common/ProductionSidebar.jsx new file mode 100644 index 0000000..e7e47e8 --- /dev/null +++ b/src/components/Dashboard/common/ProductionSidebar.jsx @@ -0,0 +1,78 @@ +import React, { useState, useEffect } from 'react' +import { Link, useLocation } from 'react-router-dom' +import { Layout, Menu, Flex, Button } from 'antd' +import { + DashboardOutlined, + PrinterOutlined, + PlayCircleOutlined, + MenuFoldOutlined, + MenuUnfoldOutlined +} from '@ant-design/icons' +import GCodeFileIcon from '../../Icons/GCodeFileIcon' + +const { Sider } = Layout + +const ProductionSidebar = () => { + const location = useLocation() + const [selectedKey, setSelectedKey] = useState('production') + const [collapsed, setCollapsed] = useState(false) + + useEffect(() => { + const pathParts = location.pathname.split('/').filter(Boolean) + if (pathParts.length > 1) { + setSelectedKey(pathParts[1]) // Return the section (production/management) + } + }, [location.pathname]) + const items = [ + { + key: 'overview', + label: Overview, + icon: + }, + { + key: 'printers', + label: Printers, + icon: + }, + { + key: 'printjobs', + label: Print Jobs, + icon: + }, + { + key: 'gcodefiles', + label: G Code Files, + icon: + } + ] + return ( + + + + + + + + ) + } + + return ( + + + + ) +} + +SubJobsTree.propTypes = { + printJobData: PropTypes.object.isRequired +} + +export default SubJobsTree diff --git a/src/components/Dashboard/context/SpotlightContext.js b/src/components/Dashboard/context/SpotlightContext.js new file mode 100644 index 0000000..d97b367 --- /dev/null +++ b/src/components/Dashboard/context/SpotlightContext.js @@ -0,0 +1,376 @@ +import { Input, Flex, List, Typography, Modal, Spin, message, Form } from 'antd' +import React, { createContext, useEffect, useState, useRef } from 'react' +import axios from 'axios' +import { + LoadingOutlined, + PrinterOutlined, + PlayCircleOutlined +} from '@ant-design/icons' +import PropTypes from 'prop-types' +import PrinterState from '../common/PrinterState' +import JobState from '../common/JobState' +import IdText from '../common/IdText' + +const SpotlightContext = createContext() + +const SpotlightProvider = ({ children }) => { + const { Text } = Typography + const [showModal, setShowModal] = useState(false) + const [loading, setLoading] = useState(false) + const [query, setQuery] = useState('') + const [listData, setListData] = useState([]) + const [messageApi, contextHolder] = message.useMessage() + const [inputPrefix, setInputPrefix] = useState('') + + // Refs for throttling/debouncing + const lastFetchTime = useRef(0) + const pendingQuery = useRef(null) + const fetchTimeoutRef = useRef(null) + const inputRef = useRef(null) + const formRef = useRef(null) + + const showSpotlight = (defaultQuery = '') => { + setQuery(defaultQuery) + setShowModal(true) + + // Set prefix based on default query if provided + if (defaultQuery) { + detectAndSetPrefix(defaultQuery) + checkAndFetchData(defaultQuery) + } else { + setInputPrefix('') + } + + // Focus will be handled in useEffect for proper timing after modal renders + } + + const fetchData = async (searchQuery) => { + if (!searchQuery || !searchQuery.trim()) return + + try { + // Update last fetch time + lastFetchTime.current = Date.now() + // Clear any pending queries + pendingQuery.current = null + + setLoading(true) + setListData([]) + const response = await axios.get( + `http://localhost:8080/spotlight/${encodeURIComponent(searchQuery.trim())}`, + { + headers: { + Accept: 'application/json' + }, + withCredentials: true + } + ) + setLoading(false) + setListData(response.data) + + // Check if there's a pending query after this fetch completes + if (pendingQuery.current !== null) { + const timeToNextFetch = Math.max( + 0, + 1000 - (Date.now() - lastFetchTime.current) + ) + scheduleNextFetch(timeToNextFetch) + } + } catch (error) { + setLoading(false) + messageApi.error('An error occurred while fetching data.') + console.error('Spotlight fetch error:', error) + } + } + + const checkAndFetchData = (searchQuery) => { + // Store the latest query + pendingQuery.current = searchQuery + + // Calculate time since last fetch + const now = Date.now() + const timeSinceLastFetch = now - lastFetchTime.current + + // If we've waited at least 1 second since last fetch, fetch immediately + if (timeSinceLastFetch >= 1000) { + if (fetchTimeoutRef.current) { + clearTimeout(fetchTimeoutRef.current) + fetchTimeoutRef.current = null + } + fetchData(searchQuery) + } else { + // Otherwise, schedule fetch for when 1 second has passed + if (!fetchTimeoutRef.current) { + const timeToWait = 1000 - timeSinceLastFetch + scheduleNextFetch(timeToWait) + } + // We don't need to do anything if a fetch is already scheduled + // as the latest query is already stored in pendingQuery + } + } + + const scheduleNextFetch = (delay) => { + if (fetchTimeoutRef.current) { + clearTimeout(fetchTimeoutRef.current) + } + + fetchTimeoutRef.current = setTimeout(() => { + fetchTimeoutRef.current = null + if (pendingQuery.current !== null) { + fetchData(pendingQuery.current) + } + }, delay) + } + + // Detect and set the appropriate prefix based on input + const detectAndSetPrefix = (text) => { + if (!text || text.trim() === '') { + setInputPrefix('') + return + } + + console.log('Detecting prefix') + const upperText = text.toUpperCase() + + if (upperText.startsWith('JOB:')) { + setInputPrefix('JOB:') + return true + } else if (upperText.startsWith('PRN:')) { + setInputPrefix('PRN:') + return true + } else if (upperText.startsWith('FIL:')) { + setInputPrefix('FIL') + return true + } else if (upperText.startsWith('GCF:')) { + setInputPrefix('GCF:') + return true + } + + // Default behavior if no match + setInputPrefix('') + return false + } + + const handleSpotlightChange = (formData) => { + const newQuery = formData.query || '' + setQuery(newQuery) + + // Detect and set the appropriate prefix + detectAndSetPrefix(inputPrefix + newQuery) + + // Check if we need to fetch data + checkAndFetchData(inputPrefix + newQuery) + } + + // Focus the input element + const focusInput = () => { + setTimeout(() => { + if (inputRef.current) { + const input = inputRef.current.input + if (input) { + input.focus() + } + } + }, 50) + } + + // Custom handler for input changes to handle prefix logic + const handleInputChange = (e) => { + const value = e.target.value + + // If the input is empty or being cleared + if (!value || value.trim() === '') { + // Only clear the prefix if the input is completely empty + if (value === '') { + console.log('Clearning prefix') + setInputPrefix('') + } + if (formRef.current) { + formRef.current.setFieldsValue({ query: value }) + } + } + // If the user is typing and it doesn't have a prefix yet + else if (!inputPrefix) { + console.log('No prefix') + // Check for prefixes at the beginning of the input + const upperValue = value.toUpperCase() + + if (upperValue.startsWith('JOB:') || upperValue.startsWith('PRN:')) { + const parts = upperValue.split(':') + const prefix = parts[0] + ':' + const restOfInput = value.substring(prefix.length) + + // Set the prefix and update the input without the prefix + setInputPrefix(prefix) + if (formRef.current) { + formRef.current.setFieldsValue({ query: restOfInput }) + // Ensure input gets focus after prefix is set + focusInput() + } + return + } + } + } + + // Handle key down events for backspace behavior + const handleKeyDown = (e) => { + // If backspace is pressed and there's a prefix but the input is empty + + if (e.key === 'Backspace' && inputPrefix && query == inputPrefix) { + console.log('Query', query) + // Clear the prefix + setInputPrefix('') + // Prevent the default backspace behavior in this case + e.preventDefault() + } + } + + // Add keyboard shortcut listener + useEffect(() => { + const handleKeyPress = (e) => { + if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'p') { + e.preventDefault() // Prevent browser's default behavior + showSpotlight() + } + } + + // Add event listener + window.addEventListener('keydown', handleKeyPress) + + // Clean up + return () => { + window.removeEventListener('keydown', handleKeyPress) + } + }, []) + + // Focus and select text in input when modal becomes visible + useEffect(() => { + if (showModal && inputRef.current) { + // Use a small timeout to ensure the modal is fully rendered and visible + setTimeout(() => { + const input = inputRef.current.input + if (input) { + input.focus() + input.select() // Select all text + } + }, 50) + } + }, [showModal]) + + // Focus input when inputPrefix changes + useEffect(() => { + if (showModal) { + focusInput() + } + }, [inputPrefix, showModal]) + + // Cleanup on unmount + useEffect(() => { + return () => { + if (fetchTimeoutRef.current) { + clearTimeout(fetchTimeoutRef.current) + } + } + }, []) + + return ( + + {contextHolder} + setShowModal(false)} + closeIcon={null} + footer={null} + styles={{ content: { backgroundColor: 'transparent' } }} + > + +
+ + } + spinning={loading} + size='small' + /> + } + onChange={handleInputChange} + onKeyDown={handleKeyDown} + /> + + + + {listData.length > 0 && ( + ( + + + + {item.printer ? ( + + ) : null} + {item.job ? ( + + ) : null} + + + {item.name} + + {item.printer ? ( + + + + + ) : null} + {item.job ? ( + + {item.job.state.type ? ( + + ) : null} + + + + ) : null} + +
+ } + /> + ENTER + + )} + > + )} +
+ + {children} + + ) +} + +SpotlightProvider.propTypes = { + children: PropTypes.node.isRequired +} + +export { SpotlightProvider, SpotlightContext } diff --git a/src/components/Dashboard/utils/GCode.js b/src/components/Dashboard/utils/GCode.js new file mode 100644 index 0000000..e147d20 --- /dev/null +++ b/src/components/Dashboard/utils/GCode.js @@ -0,0 +1,30 @@ +export default class GCode { + constructor(configString) { + this.configString = configString + } + + async parse(onProgress) { + return new Promise((resolve, reject) => { + const worker = new Worker('../gcode-worker.js') + + worker.onmessage = (event) => { + const { type, progress, configObject } = event.data + + if (type === 'progress') { + // Report progress to the caller + if (onProgress) onProgress(progress) + } else if (type === 'result') { + resolve(configObject) + worker.terminate() + } + } + + worker.onerror = (error) => { + reject(error) + worker.terminate() + } + + worker.postMessage({ configString: this.configString }) + }) + } +} diff --git a/src/components/Dashboard/utils/Utils.js b/src/components/Dashboard/utils/Utils.js new file mode 100644 index 0000000..36a908f --- /dev/null +++ b/src/components/Dashboard/utils/Utils.js @@ -0,0 +1,31 @@ +export function capitalizeFirstLetter(string) { + try { + return string[0].toUpperCase() + string.slice(1) + } catch { + return '' + } +} + +export function timeStringToMinutes(timeString) { + // Extract hours, minutes, and seconds using a regular expression + const regex = /(\d+h)?\s*(\d+m)?\s*(\d+s)?/ + const matches = timeString.match(regex) + + // Initialize hours, minutes, and seconds to 0 + let hours = 0 + let minutes = 0 + let seconds = 0 + + // If matches are found, extract the values + if (matches) { + if (matches[1]) hours = parseInt(matches[1]) + if (matches[2]) minutes = parseInt(matches[2]) + if (matches[3]) seconds = parseInt(matches[3]) + } + + // Convert everything to minutes + const totalMinutes = hours * 60 + minutes + seconds / 60 + + // Return the integer value of total minutes + return Math.floor(totalMinutes) +} diff --git a/src/components/Icons/FilamentIcon.jsx b/src/components/Icons/FilamentIcon.jsx new file mode 100644 index 0000000..92067cc --- /dev/null +++ b/src/components/Icons/FilamentIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/filamenticon.svg' + +const FilamentIcon = (props) => + +export default FilamentIcon diff --git a/src/components/Icons/LevelBedIcon.jsx b/src/components/Icons/LevelBedIcon.jsx new file mode 100644 index 0000000..6d45c42 --- /dev/null +++ b/src/components/Icons/LevelBedIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/levelbedicon.svg' + +const LevelBedIcon = (props) => + +export default LevelBedIcon diff --git a/src/components/Icons/NewWindowIcon.jsx b/src/components/Icons/NewWindowIcon.jsx new file mode 100644 index 0000000..4e42a99 --- /dev/null +++ b/src/components/Icons/NewWindowIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/newwindowicon.svg' + +const NewWindowIcon = (props) => + +export default NewWindowIcon diff --git a/src/components/Icons/PartIcon.jsx b/src/components/Icons/PartIcon.jsx new file mode 100644 index 0000000..4644308 --- /dev/null +++ b/src/components/Icons/PartIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/particon.svg' + +const PartIcon = (props) => + +export default PartIcon diff --git a/src/components/Icons/ProductIcon.jsx b/src/components/Icons/ProductIcon.jsx new file mode 100644 index 0000000..cd82c6e --- /dev/null +++ b/src/components/Icons/ProductIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/producticon.svg' + +const ProductIcon = (props) => + +export default ProductIcon diff --git a/src/components/Icons/UnloadIcon.jsx b/src/components/Icons/UnloadIcon.jsx new file mode 100644 index 0000000..747361e --- /dev/null +++ b/src/components/Icons/UnloadIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/unloadicon.svg' + +const UnloadIcon = (props) => + +export default UnloadIcon