Compare commits

...

4 Commits

Author SHA1 Message Date
054ff43229 basic page content
All checks were successful
App Image CI / Build app image (push) Successful in 33s
NPM Audit Check / Check NPM audit (push) Successful in -2m7s
2025-10-15 23:51:53 -05:00
f653af501f minor html changes
All checks were successful
App Image CI / Build app image (push) Successful in 35s
NPM Audit Check / Check NPM audit (push) Successful in -2m9s
2025-10-15 00:12:39 -05:00
893eb6f801 tooling for browser js 2025-10-15 00:06:45 -05:00
b809e647e8 chore done endpoint 2025-10-15 00:03:20 -05:00
13 changed files with 150 additions and 32 deletions

View File

@@ -5,4 +5,5 @@ assets/css/
assets/js/
assets/images/webp/
assets/images/avif/
scripts/*.js
docker-compose.yml

1
.gitignore vendored
View File

@@ -108,3 +108,4 @@ assets/js/
assets/webp/
config/
distribution/
scripts/*.js

View File

@@ -12,7 +12,7 @@ RUN npm install
COPY . .
RUN npx tsc && npm run-script build && \
RUN npm run-script tsc && npm run-script build && \
npm ci --only=production && \
ln -sf /usr/share/fonts assets/ && \
chown -R node .

22
package-lock.json generated
View File

@@ -18,7 +18,7 @@
"@sindresorhus/tsconfig": "8.0.1",
"@types/express": "^5.0.3",
"@types/node": "^24.7.0",
"forking-build-shit": "1.0.4",
"forking-build-shit": "1.0.5",
"typescript": "5.9.3"
}
},
@@ -864,14 +864,14 @@
}
},
"node_modules/forking-build-shit": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/forking-build-shit/-/forking-build-shit-1.0.4.tgz",
"integrity": "sha512-pknWCgvJWSkP30sMllWzFxJhjFtXCRuILgYT1N7pJLAyI/SXQ0RXyhTcg+hjCnKaH4aVpxWUFUF2afwTDRO6RA==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/forking-build-shit/-/forking-build-shit-1.0.5.tgz",
"integrity": "sha512-b9HVQF1f2qXkgB+yTJe3U2kLfggvbUZjU1aqljI0E8O+sxo+cQH/HsyMGdHNPaUoMgSe/qfOsL6CPumu0jrrdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"csso": "5.0.5",
"sass": "1.90.0",
"sass": "1.93.2",
"uglify-js": "3.19.3"
},
"bin": {
@@ -1017,9 +1017,9 @@
}
},
"node_modules/immutable": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
"integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
"dev": true,
"license": "MIT"
},
@@ -1426,9 +1426,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.90.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz",
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"version": "1.93.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz",
"integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -33,11 +33,12 @@
"@sindresorhus/tsconfig": "8.0.1",
"@types/express": "^5.0.3",
"@types/node": "^24.7.0",
"forking-build-shit": "1.0.4",
"forking-build-shit": "1.0.5",
"typescript": "5.9.3"
},
"scripts": {
"build": "npx build-shit",
"watch": "npx build-shit --watch"
"tsc": "tsc && tsc -p tsconfig.web.json",
"build": "build-shit",
"watch": "build-shit --watch"
}
}

43
scripts/content.ts Normal file
View File

@@ -0,0 +1,43 @@
import type { GrocyChore } from '../distribution/Grocy';
document.addEventListener("DOMContentLoaded", async function () {
async function loadChores() {
try {
const response = await fetch('/api/chores');
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const result = await response.json();
if (!('chores' in result)) {
throw new Error(JSON.stringify(result));
}
createChores(result.chores as GrocyChore[]);
} catch (error: any) {
console.error(error?.message);
}
}
function createChores(chores: GrocyChore[]) {
const root = document.getElementById('approot');
if (!root) {
throw new Error('app root not found');
}
chores.forEach(ch => {
const btn = document.createElement('div');
const leftText = document.createElement('span');
leftText.appendChild(document.createTextNode(ch.chore_name));
btn.appendChild(leftText);
btn.classList.add('chore');
if (ch.next_execution_assigned_user?.display_name) {
const rightText = document.createElement('span');
rightText.appendChild(document.createTextNode(ch.next_execution_assigned_user.display_name));
rightText.classList.add('right');
btn.appendChild(rightText);
}
root.appendChild(btn);
});
}
loadChores();
});

View File

@@ -0,0 +1 @@

View File

@@ -56,11 +56,11 @@ class Web {
});
app.get('/', (_, res) => {
res.render('index', {
res.render('app', {
page: {
title: 'Web',
titlesuffix: 'Home',
description: 'Homepage'
title: 'Chore Chart',
titlesuffix: 'View Grocy Chores',
description: 'chore-chart displays chores from a Grocy instance.'
}
});
});
@@ -96,6 +96,33 @@ class Web {
});
});
app.post('/api/chores/:id', async (req, res) => {
const doer = req.body?.done_by;
const chore = req.params?.id;
if (typeof doer != 'number' || doer < 0) {
res.status(400).send({
error: 'done_by value is invalid.'
});
return;
}
if (typeof chore != 'number' || chore < 0) {
res.status(400).send({
error: 'chore id value is invalid.'
});
return;
}
try {
const resp = await this.grocy.doChore(chore, doer);
res.send(resp);
}
catch (err) {
console.error(err);
res.status(400).send({
error: 'grocy didn\'t like that.'
});
}
});
app.get('/api/users/:id', async (req, res) => {
const user = await this.grocy.user(parseInt(req.params.id))
res.send({

19
styles/01-layout.scss Normal file
View File

@@ -0,0 +1,19 @@
body {
font-family: Arial, Helvetica, sans-serif;
}
.right {
float: right;
}
.content {
width: 80%;
width: calc(100% - 150px);
margin: auto;
}
.chore {
line-height: 1.2em;
padding: .75em;
margin: 1em 0;
}

8
styles/02-colors.scss Normal file
View File

@@ -0,0 +1,8 @@
body {
background-color: #212121;
}
.chore {
color: #FAFAFA;
background-color: #4527A0;
}

26
tsconfig.web.json Normal file
View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "bundler",
"lib": [
"DOM",
"DOM.Iterable",
"ES2020"
],
"outDir": "scripts",
"rootDir": "scripts",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"allowJs": false,
"resolveJsonModule": true
},
"include": [
"scripts/**/*"
],
"exclude": [
"node_modules"
]
}

View File

@@ -6,9 +6,11 @@
</head>
<body class="preload">
<%- include("navigation", locals) %>
<div class="content">
<h1>Hello World</h1>
<div id="approot">
</div>
</div>
<script nonce="<%= cspNonce %>" src="/assets/js/content.js" defer></script>
</body>
</html>

View File

@@ -1,11 +0,0 @@
<div class="navigation">
<div class="nav_logo">
<a href="/"><img src="/assets/svg/logo.svg" alt="Logo" /></a>
</div>
<nav>
<ul class="nav_links">
<li><a href="/">Home</a></li>
<li><a href="/ky">Away</a></li>
</ul>
</nav>
</div>