add yaml import and export

This commit is contained in:
Sean Sube 2025-06-17 20:18:04 -05:00
parent 3c37a94644
commit dbf19eeb60
No known key found for this signature in database
GPG Key ID: 3EED7B957D362AF1
9 changed files with 665 additions and 55 deletions

View File

@ -2,7 +2,8 @@ import { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/clien
import { onError } from '@apollo/client/link/error'; import { onError } from '@apollo/client/link/error';
const httpLink = createHttpLink({ const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql', uri: 'http://10.2.2.68:4000/graphql',
//uri: 'http://localhost:4000/graphql',
}); });
const errorLink = onError(({ graphQLErrors, networkError, operation }) => { const errorLink = onError(({ graphQLErrors, networkError, operation }) => {

397
package-lock.json generated
View File

@ -9,7 +9,13 @@
"server", "server",
"client", "client",
"shared" "shared"
] ],
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.1.1",
"@mui/material": "^7.1.1"
}
}, },
"client": { "client": {
"version": "0.0.0", "version": "0.0.0",
@ -17,11 +23,12 @@
"@apollo/client": "^3.9.5", "@apollo/client": "^3.9.5",
"@emotion/react": "^11.11.3", "@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/material": "^5.15.10", "@mui/material": "^5.17.1",
"@task-receipts/shared": "file:../shared", "@task-receipts/shared": "file:../shared",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
"zustand": "^5.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.55", "@types/react": "^18.2.55",
@ -36,6 +43,194 @@
"vite": "^5.1.0" "vite": "^5.1.0"
} }
}, },
"client/node_modules/@mui/material": {
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.17.1.tgz",
"integrity": "sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/core-downloads-tracker": "^5.17.1",
"@mui/system": "^5.17.1",
"@mui/types": "~7.2.15",
"@mui/utils": "^5.17.1",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.10",
"clsx": "^2.1.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1",
"react-is": "^19.0.0",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"client/node_modules/@mui/material/node_modules/@mui/system": {
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.17.1.tgz",
"integrity": "sha512-aJrmGfQpyF0U4D4xYwA6ueVtQcEMebET43CUmKMP7e7iFh3sMIF3sBR0l8Urb4pqx1CBjHAaWgB0ojpND4Q3Jg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/private-theming": "^5.17.1",
"@mui/styled-engine": "^5.16.14",
"@mui/types": "~7.2.15",
"@mui/utils": "^5.17.1",
"clsx": "^2.1.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"client/node_modules/@mui/material/node_modules/@mui/system/node_modules/@mui/private-theming": {
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz",
"integrity": "sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/utils": "^5.17.1",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"client/node_modules/@mui/material/node_modules/@mui/system/node_modules/@mui/styled-engine": {
"version": "5.16.14",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.14.tgz",
"integrity": "sha512-UAiMPZABZ7p8mUW4akDV6O7N3+4DatStpXMZwPlt+H/dA0lt67qawN021MNND+4QTpjaiMYxbhKZeQcyWCbuKw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@emotion/cache": "^11.13.5",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
}
}
},
"client/node_modules/@mui/material/node_modules/@mui/types": {
"version": "7.2.24",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz",
"integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"client/node_modules/@mui/material/node_modules/@mui/utils": {
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.17.1.tgz",
"integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.9",
"@mui/types": "~7.2.15",
"@types/prop-types": "^15.7.12",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-is": "^19.0.0"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"client/node_modules/@typescript-eslint/eslint-plugin": { "client/node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.18.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
@ -290,6 +485,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"client/node_modules/react-is": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
"integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
"license": "MIT"
},
"client/node_modules/semver": { "client/node_modules/semver": {
"version": "7.7.2", "version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
@ -2491,27 +2692,53 @@
"url": "https://opencollective.com/mui-org" "url": "https://opencollective.com/mui-org"
} }
}, },
"node_modules/@mui/material": { "node_modules/@mui/icons-material": {
"version": "5.17.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.17.1.tgz", "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.1.tgz",
"integrity": "sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw==", "integrity": "sha512-X37+Yc8QpEnl0sYmz+WcLFy2dWgNRzbswDzLPXG7QU1XDVlP5TPp1HXjdmCupOWLL/I9m1fyhcyZl8/HPpp/Cg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.23.9", "@babel/runtime": "^7.27.1"
"@mui/core-downloads-tracker": "^5.17.1", },
"@mui/system": "^5.17.1", "engines": {
"@mui/types": "~7.2.15", "node": ">=14.0.0"
"@mui/utils": "^5.17.1", },
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@mui/material": "^7.1.1",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.1.tgz",
"integrity": "sha512-mTpdmdZCaHCGOH3SrYM41+XKvNL0iQfM9KlYgpSjgadXx/fEKhhvOktxm8++Xw6FFeOHoOiV+lzOI8X1rsv71A==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/core-downloads-tracker": "^7.1.1",
"@mui/system": "^7.1.1",
"@mui/types": "^7.4.3",
"@mui/utils": "^7.1.1",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.10", "@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.0", "clsx": "^2.1.1",
"csstype": "^3.1.3", "csstype": "^3.1.3",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react-is": "^19.0.0", "react-is": "^19.1.0",
"react-transition-group": "^4.4.5" "react-transition-group": "^4.4.5"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=14.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -2520,6 +2747,7 @@
"peerDependencies": { "peerDependencies": {
"@emotion/react": "^11.5.0", "@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0", "@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^7.1.1",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@ -2531,11 +2759,24 @@
"@emotion/styled": { "@emotion/styled": {
"optional": true "optional": true
}, },
"@mui/material-pigment-css": {
"optional": true
},
"@types/react": { "@types/react": {
"optional": true "optional": true
} }
} }
}, },
"node_modules/@mui/material/node_modules/@mui/core-downloads-tracker": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.1.tgz",
"integrity": "sha512-yBckQs4aQ8mqukLnPC6ivIRv6guhaXi8snVl00VtyojBbm+l6VbVhyTSZ68Abcx7Ah8B+GZhrB7BOli+e+9LkQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
}
},
"node_modules/@mui/material/node_modules/react-is": { "node_modules/@mui/material/node_modules/react-is": {
"version": "19.1.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
@ -2543,17 +2784,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@mui/private-theming": { "node_modules/@mui/private-theming": {
"version": "5.17.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.1.tgz",
"integrity": "sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==", "integrity": "sha512-M8NbLUx+armk2ZuaxBkkMk11ultnWmrPlN0Xe3jUEaBChg/mcxa5HWIWS1EE4DF36WRACaAHVAvyekWlDQf0PQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.23.9", "@babel/runtime": "^7.27.1",
"@mui/utils": "^5.17.1", "@mui/utils": "^7.1.1",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=14.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -2570,18 +2811,20 @@
} }
}, },
"node_modules/@mui/styled-engine": { "node_modules/@mui/styled-engine": {
"version": "5.16.14", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.14.tgz", "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.1.tgz",
"integrity": "sha512-UAiMPZABZ7p8mUW4akDV6O7N3+4DatStpXMZwPlt+H/dA0lt67qawN021MNND+4QTpjaiMYxbhKZeQcyWCbuKw==", "integrity": "sha512-R2wpzmSN127j26HrCPYVQ53vvMcT5DaKLoWkrfwUYq3cYytL6TQrCH8JBH3z79B6g4nMZZVoaXrxO757AlShaw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.23.9", "@babel/runtime": "^7.27.1",
"@emotion/cache": "^11.13.5", "@emotion/cache": "^11.13.5",
"@emotion/serialize": "^1.3.3",
"@emotion/sheet": "^1.4.0",
"csstype": "^3.1.3", "csstype": "^3.1.3",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=14.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -2602,22 +2845,22 @@
} }
}, },
"node_modules/@mui/system": { "node_modules/@mui/system": {
"version": "5.17.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.17.1.tgz", "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.1.tgz",
"integrity": "sha512-aJrmGfQpyF0U4D4xYwA6ueVtQcEMebET43CUmKMP7e7iFh3sMIF3sBR0l8Urb4pqx1CBjHAaWgB0ojpND4Q3Jg==", "integrity": "sha512-Kj1uhiqnj4Zo7PDjAOghtXJtNABunWvhcRU0O7RQJ7WOxeynoH6wXPcilphV8QTFtkKaip8EiNJRiCD+B3eROA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.23.9", "@babel/runtime": "^7.27.1",
"@mui/private-theming": "^5.17.1", "@mui/private-theming": "^7.1.1",
"@mui/styled-engine": "^5.16.14", "@mui/styled-engine": "^7.1.1",
"@mui/types": "~7.2.15", "@mui/types": "^7.4.3",
"@mui/utils": "^5.17.1", "@mui/utils": "^7.1.1",
"clsx": "^2.1.0", "clsx": "^2.1.1",
"csstype": "^3.1.3", "csstype": "^3.1.3",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=14.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -2642,10 +2885,13 @@
} }
}, },
"node_modules/@mui/types": { "node_modules/@mui/types": {
"version": "7.2.24", "version": "7.4.3",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.3.tgz",
"integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", "integrity": "sha512-2UCEiK29vtiZTeLdS2d4GndBKacVyxGvReznGXGr+CzW/YhjIX+OHUdCIczZjzcRAgKBGmE9zCIgoV9FleuyRQ==",
"license": "MIT", "license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.1"
},
"peerDependencies": { "peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}, },
@ -2656,20 +2902,20 @@
} }
}, },
"node_modules/@mui/utils": { "node_modules/@mui/utils": {
"version": "5.17.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.17.1.tgz", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.1.tgz",
"integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==", "integrity": "sha512-BkOt2q7MBYl7pweY2JWwfrlahhp+uGLR8S+EhiyRaofeRYUWL2YKbSGQvN4hgSN1i8poN0PaUiii1kEMrchvzg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.23.9", "@babel/runtime": "^7.27.1",
"@mui/types": "~7.2.15", "@mui/types": "^7.4.3",
"@types/prop-types": "^15.7.12", "@types/prop-types": "^15.7.14",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react-is": "^19.0.0" "react-is": "^19.1.0"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=14.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -3411,6 +3657,12 @@
"pretty-format": "^29.0.0" "pretty-format": "^29.0.0"
} }
}, },
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"license": "MIT"
},
"node_modules/@types/long": { "node_modules/@types/long": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
@ -11795,6 +12047,35 @@
"zen-observable": "0.8.15" "zen-observable": "0.8.15"
} }
}, },
"node_modules/zustand": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz",
"integrity": "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
},
"server": { "server": {
"name": "task-receipts-server", "name": "task-receipts-server",
"version": "1.0.0", "version": "1.0.0",
@ -11803,11 +12084,13 @@
"@node-escpos/core": "^0.6.0", "@node-escpos/core": "^0.6.0",
"@node-escpos/usb-adapter": "^0.3.1", "@node-escpos/usb-adapter": "^0.3.1",
"@task-receipts/shared": "file:../shared", "@task-receipts/shared": "file:../shared",
"@types/js-yaml": "^4.0.9",
"apollo-server-express": "^3.13.0", "apollo-server-express": "^3.13.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.18.3", "express": "^4.18.3",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"js-yaml": "^4.1.0",
"knex": "^3.1.0", "knex": "^3.1.0",
"pg": "^8.11.3", "pg": "^8.11.3",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",
@ -12021,6 +12304,12 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"server/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"server/node_modules/brace-expansion": { "server/node_modules/brace-expansion": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
@ -12059,6 +12348,18 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"server/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"server/node_modules/minimatch": { "server/node_modules/minimatch": {
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",

View File

@ -12,5 +12,11 @@
"test": "npm run test --workspaces", "test": "npm run test --workspaces",
"lint": "npm run lint --workspaces", "lint": "npm run lint --workspaces",
"lint:fix": "npm run lint:fix --workspaces" "lint:fix": "npm run lint:fix --workspaces"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.1.1",
"@mui/material": "^7.1.1"
} }
} }

View File

@ -22,11 +22,13 @@
"@node-escpos/core": "^0.6.0", "@node-escpos/core": "^0.6.0",
"@node-escpos/usb-adapter": "^0.3.1", "@node-escpos/usb-adapter": "^0.3.1",
"@task-receipts/shared": "file:../shared", "@task-receipts/shared": "file:../shared",
"@types/js-yaml": "^4.0.9",
"apollo-server-express": "^3.13.0", "apollo-server-express": "^3.13.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.18.3", "express": "^4.18.3",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"js-yaml": "^4.1.0",
"knex": "^3.1.0", "knex": "^3.1.0",
"pg": "^8.11.3", "pg": "^8.11.3",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",

View File

@ -0,0 +1,75 @@
import { YamlService } from '../yaml-service';
import { UserRepository } from '../db/repositories/user-repository';
import knex from 'knex';
import config from '../db/knexfile';
const MIGRATIONS_DIR = config.test.migrations?.directory;
// Helper to truncate all tables
async function truncateAll(db: any) {
await db.raw('PRAGMA foreign_keys = OFF');
await db('notes').truncate();
await db('steps').truncate();
await db('tasks').truncate();
await db('groups').truncate();
await db('users').truncate();
await db('images').truncate();
await db('print_history').truncate();
await db.raw('PRAGMA foreign_keys = ON');
}
describe('YamlService', () => {
let db: ReturnType<typeof knex>;
let yamlService: YamlService;
beforeEach(async () => {
db = knex(config.test);
await db.migrate.latest({ directory: MIGRATIONS_DIR });
// Create a user for notes
const userRepo = new UserRepository(db);
await userRepo.create({ name: 'Test User' });
yamlService = new YamlService(db);
});
afterEach(async () => {
await db.destroy();
});
it('should export empty database to YAML', async () => {
const yamlContent = await yamlService.exportToYaml();
expect(yamlContent).toContain('groups: []');
});
it('should import and export YAML correctly', async () => {
const testYaml = `
groups:
- name: Test Group
tasks:
- name: Test Task
steps:
- name: Test Step
instructions: Test instructions
notes:
- content: Test note
`;
// Import the YAML
await yamlService.importFromYaml(testYaml);
// Export it back
const exportedYaml = await yamlService.exportToYaml();
// Should contain the imported data
expect(exportedYaml).toContain('Test Group');
expect(exportedYaml).toContain('Test Task');
expect(exportedYaml).toContain('Test Step');
expect(exportedYaml).toContain('Test instructions');
expect(exportedYaml).toContain('Test note');
});
it('should handle invalid YAML', async () => {
const invalidYaml = 'invalid: yaml: content:';
await expect(yamlService.importFromYaml(invalidYaml)).rejects.toThrow();
});
});

View File

@ -8,16 +8,18 @@ import { createDb } from './db';
import logger from './logger'; import logger from './logger';
import { createPrinter } from './printer'; import { createPrinter } from './printer';
import { PrintHistoryRepository, StepRepository } from './db/repositories'; import { PrintHistoryRepository, StepRepository } from './db/repositories';
import { YamlService } from './yaml-service';
import cors from 'cors'; import cors from 'cors';
const app = express(); const app = express();
const port = process.env.PORT || 4000; const port = 4000; //process.env.PORT || 4000;
async function startServer() { async function startServer() {
const db = createDb(); const db = createDb();
const printHistoryRepo = new PrintHistoryRepository(db); const printHistoryRepo = new PrintHistoryRepository(db);
const stepRepo = new StepRepository(db); const stepRepo = new StepRepository(db);
const printer = createPrinter(printHistoryRepo, stepRepo); const printer = createPrinter(printHistoryRepo, stepRepo);
const yamlService = new YamlService(db);
const server = new ApolloServer({ const server = new ApolloServer({
typeDefs, typeDefs,
@ -28,7 +30,9 @@ async function startServer() {
// Enable CORS for the frontend // Enable CORS for the frontend
app.use(cors({ app.use(cors({
origin: 'http://localhost:5173', // origin: 'http://localhost:5173',
// allow all origins
origin: '*',
credentials: true credentials: true
})); }));
@ -40,8 +44,40 @@ async function startServer() {
}) })
); );
const httpServer = app.listen(port, () => { // YAML Export endpoint
app.get('/api/yaml/export', async (req, res) => {
try {
const yamlContent = await yamlService.exportToYaml();
res.setHeader('Content-Type', 'text/yaml');
res.setHeader('Content-Disposition', 'attachment; filename="database-export.yaml"');
res.send(yamlContent);
} catch (error) {
logger.error('Error exporting YAML:', error);
res.status(500).json({ error: 'Failed to export YAML' });
}
});
// YAML Import endpoint
app.post('/api/yaml/import', async (req, res) => {
try {
const { yamlContent } = req.body;
if (!yamlContent || typeof yamlContent !== 'string') {
return res.status(400).json({ error: 'YAML content is required' });
}
await yamlService.importFromYaml(yamlContent);
res.json({ message: 'Database imported successfully' });
} catch (error) {
logger.error('Error importing YAML:', error);
res.status(500).json({ error: 'Failed to import YAML' });
}
});
const httpServer = app.listen(port, '0.0.0.0', () => {
logger.info(`Server running at http://localhost:${port}/graphql`); logger.info(`Server running at http://localhost:${port}/graphql`);
logger.info(`YAML export endpoint: http://localhost:${port}/api/yaml/export`);
logger.info(`YAML import endpoint: http://localhost:${port}/api/yaml/import`);
}); });
// Graceful shutdown // Graceful shutdown

View File

@ -1,3 +1,5 @@
import { PAPER_CONFIG } from "./printer-constants";
export const formatUtils = { export const formatUtils = {
/** /**
* Creates a banner line with the specified character * Creates a banner line with the specified character
@ -5,7 +7,7 @@ export const formatUtils = {
* @param length Length of the banner * @param length Length of the banner
* @returns Formatted banner string * @returns Formatted banner string
*/ */
createBanner(char: string, length: number = 40): string { createBanner(char: string, length: number = PAPER_CONFIG.BANNER_LENGTH): string {
return char.repeat(length); return char.repeat(length);
}, },

View File

@ -111,7 +111,7 @@ export class SerialPrinter implements PrinterInterface {
const step = taskSteps[i]; const step = taskSteps[i];
const stepSection = formatUtils.formatSection( const stepSection = formatUtils.formatSection(
formatUtils.formatStepHeader(step.name, i + 1, task.name, true), formatUtils.formatStepHeader(step.name, i + 1, task.name, true),
step.instructions, step.instructions || 'No instructions provided',
'-' '-'
); );
@ -161,7 +161,7 @@ export class SerialPrinter implements PrinterInterface {
const stepSection = formatUtils.formatSection( const stepSection = formatUtils.formatSection(
formatUtils.formatStepHeader(step.name, stepNumber, task?.name), formatUtils.formatStepHeader(step.name, stepNumber, task?.name),
step.instructions step.instructions || 'No instructions provided'
); );
await this.printer await this.printer

187
server/src/yaml-service.ts Normal file
View File

@ -0,0 +1,187 @@
import * as yaml from 'js-yaml';
import { Knex } from 'knex';
import { GroupRepository, TaskRepository, StepRepository, NoteRepository } from './db/repositories';
import { Group, Task, Step, Note } from '@shared/index';
export interface YamlData {
groups: YamlGroup[];
}
export interface YamlGroup {
name: string;
tasks: YamlTask[];
}
export interface YamlTask {
name: string;
steps: YamlStep[];
notes?: YamlNote[];
}
export interface YamlStep {
name: string;
instructions: string;
notes?: YamlNote[];
}
export interface YamlNote {
content: string;
}
export class YamlService {
private groupRepo: GroupRepository;
private taskRepo: TaskRepository;
private stepRepo: StepRepository;
private noteRepo: NoteRepository;
private db: Knex;
constructor(db: Knex) {
this.db = db;
this.groupRepo = new GroupRepository(db);
this.taskRepo = new TaskRepository(db);
this.stepRepo = new StepRepository(db);
this.noteRepo = new NoteRepository(db);
}
async exportToYaml(): Promise<string> {
const groups = await this.groupRepo.findRootGroups();
const yamlData: YamlData = {
groups: []
};
for (const group of groups) {
const yamlGroup = await this.convertGroupToYaml(group);
yamlData.groups.push(yamlGroup);
}
return yaml.dump(yamlData, {
indent: 2,
lineWidth: 120,
noRefs: true
});
}
async importFromYaml(yamlContent: string): Promise<void> {
const yamlData = yaml.load(yamlContent) as YamlData;
if (!yamlData || !yamlData.groups) {
throw new Error('Invalid YAML format: missing groups');
}
// Clear existing data (optional - you might want to make this configurable)
await this.clearExistingData();
// Import groups and their nested data
for (const yamlGroup of yamlData.groups) {
await this.importGroup(yamlGroup);
}
}
private async convertGroupToYaml(group: Group): Promise<YamlGroup> {
const tasks = await this.taskRepo.findByGroupId(group.id);
const yamlTasks: YamlTask[] = [];
for (const task of tasks) {
const yamlTask = await this.convertTaskToYaml(task);
yamlTasks.push(yamlTask);
}
return {
name: group.name,
tasks: yamlTasks
};
}
private async convertTaskToYaml(task: Task): Promise<YamlTask> {
const steps = await this.stepRepo.findByTaskId(task.id);
const notes = await this.noteRepo.findByTaskId(task.id);
const yamlSteps: YamlStep[] = [];
for (const step of steps) {
const stepNotes = await this.noteRepo.findByStepId(step.id);
const yamlStep: YamlStep = {
name: step.name,
instructions: step.instructions,
notes: stepNotes.map(note => ({ content: note.content }))
};
yamlSteps.push(yamlStep);
}
const yamlTask: YamlTask = {
name: task.name,
steps: yamlSteps,
notes: notes.map(note => ({ content: note.content }))
};
return yamlTask;
}
private async importGroup(yamlGroup: YamlGroup): Promise<void> {
// Create the group
const group = await this.groupRepo.create({
name: yamlGroup.name,
parent_id: undefined
});
// Import tasks for this group
for (const yamlTask of yamlGroup.tasks) {
await this.importTask(yamlTask, group.id);
}
}
private async importTask(yamlTask: YamlTask, groupId: number): Promise<void> {
// Create the task
const task = await this.taskRepo.create({
name: yamlTask.name,
group_id: groupId,
print_count: 0
});
// Import task-level notes
if (yamlTask.notes) {
for (const yamlNote of yamlTask.notes) {
await this.noteRepo.create({
content: yamlNote.content,
task_id: task.id,
created_by: 1 // Default user ID
});
}
}
// Import steps
for (let i = 0; i < yamlTask.steps.length; i++) {
const yamlStep = yamlTask.steps[i];
await this.importStep(yamlStep, task.id, i + 1);
}
}
private async importStep(yamlStep: YamlStep, taskId: number, order: number): Promise<void> {
// Create the step
const step = await this.stepRepo.create({
name: yamlStep.name,
instructions: yamlStep.instructions,
task_id: taskId,
order: order,
print_count: 0
});
// Import step-level notes
if (yamlStep.notes) {
for (const yamlNote of yamlStep.notes) {
await this.noteRepo.create({
content: yamlNote.content,
step_id: step.id,
created_by: 1 // Default user ID
});
}
}
}
private async clearExistingData(): Promise<void> {
// Delete in reverse order to respect foreign key constraints
await this.db('notes').del();
await this.db('steps').del();
await this.db('tasks').del();
await this.db('groups').del();
}
}