6 Commits

Author SHA1 Message Date
a4fe525b9e highlight due date
All checks were successful
CI / lint (push) Successful in 14s
CI / build (push) Successful in 17s
CI / build-and-push-docker (push) Successful in 31s
2025-08-28 22:51:18 +02:00
d37dbc4f62 sort chores using due date, color tasks according to due level 2025-08-28 22:51:18 +02:00
c7b03a93f0 fix code style, use names from api instead of hardcoding for flatmates users 2025-08-28 22:51:18 +02:00
c924ab893e Full tram stop names and final prompt in terminal 2025-08-28 22:48:06 +02:00
05c62ed6bb New Terminal 2025-08-28 22:37:20 +02:00
481f103ba0 Make clock divider blink 2025-08-28 21:36:53 +02:00
13 changed files with 199 additions and 116 deletions

View File

@@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Biome - name: Install Biome
run: bun install -g biome run: bun install -g biome
- name: Biome Check - name: Biome CI
run: biome ci run: biome ci
build: build:

View File

@@ -24,13 +24,6 @@ export default function Dashboard() {
</div> </div>
</div> </div>
<div className={style.card}>
<div className={style.cardHeaderInactive}>🧹 Flatastic</div>
<div className={style.cardContent}>
<Flatastic />
</div>
</div>
<div className={style.card}> <div className={style.card}>
<div className={style.terminal}> <div className={style.terminal}>
<div className={style.cardHeader}>🔔 Terminal</div> <div className={style.cardHeader}>🔔 Terminal</div>
@@ -38,14 +31,12 @@ export default function Dashboard() {
</div> </div>
</div> </div>
{/* <div className={style.card}> */} <div className={style.card}>
{/* <div className={style.cardHeaderInactive}> */} <div className={style.cardHeaderInactive}>🧹 Flatastic</div>
{/* 🏠 HomeAssistant [Tent] */} <div className={style.cardContent}>
{/* </div> */} <Flatastic />
{/* <div className={style.cardContent}> */} </div>
{/* <HomeAssistant /> */} </div>
{/* </div> */}
{/* </div> */}
</div> </div>
<div className={style.footer}> <div className={style.footer}>

View File

@@ -20,11 +20,8 @@ export default function Datetime() {
year: "numeric", year: "numeric",
}); });
const time = today.toLocaleTimeString(locale, { const hour = today.getHours().toString().padStart(2, "0");
hour: "numeric", const minute = today.getMinutes().toString().padStart(2, "0");
hour12: false,
minute: "numeric",
});
return ( return (
<div className={style.container}> <div className={style.container}>
@@ -32,7 +29,11 @@ export default function Datetime() {
<img src="src/assets/clock.png" alt="Clock" /> <img src="src/assets/clock.png" alt="Clock" />
</div> </div>
<div className={style.textContainer}> <div className={style.textContainer}>
<div className={style.time}>{time}</div> <div className={style.time}>
{hour}
<span className={style.divider}>:</span>
{minute}
</div>
<div className={style.date}>{date}</div> <div className={style.date}>{date}</div>
</div> </div>
</div> </div>

View File

@@ -17,3 +17,13 @@ img {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.divider {
animation: blink 3s step-end infinite;
}
@keyframes blink {
50% {
opacity: 0;
}
}

View File

@@ -8,7 +8,11 @@ import style from "./style.module.css";
export default function Flatastic() { export default function Flatastic() {
const fetchFlatasticData = useFlatasticStore((state) => state.fetch); const fetchFlatasticData = useFlatasticStore((state) => state.fetch);
const flatasticData = useFlatasticStore((state) => state.flatasticData); const flatasticData = useFlatasticStore((state) => state.flatasticData);
const chores = flatasticData?.chores; const chores = (flatasticData?.chores as FlatasticChore[]) || [];
chores.sort(
(a, b) =>
a.timeLeftNext - b.timeLeftNext && b.rotationTime - a.rotationTime,
);
const users = flatasticData?.users; const users = flatasticData?.users;
const idToNameMap: Record<number, string> = {}; const idToNameMap: Record<number, string> = {};
users.forEach((user: FlatasticUser) => { users.forEach((user: FlatasticUser) => {
@@ -23,19 +27,38 @@ export default function Flatastic() {
return () => clearInterval(interval); return () => clearInterval(interval);
}, [fetchFlatasticData]); }, [fetchFlatasticData]);
const choresRender = chores.map((chore) => {
let className = "";
let timeLeftInDays = null;
if (chore.rotationTime === -1) {
className = "irregular";
} else {
className = chore.timeLeftNext <= 0 ? "due" : "notDue";
timeLeftInDays = <span className={style.timeLeft}>
{Math.abs(
Math.floor(chore.timeLeftNext / (60 * 60 * 24)),
)}d
</span>;
}
return (
<li
key={chore.id}
className={[style.chore, style[className]].join(" ")}
>
<span className={style.userName}>
{`${idToNameMap[chore.currentUser]}`}
</span>
: {chore.title} {timeLeftInDays} {"🪙".repeat(chore.points)}
</li>
);
});
return ( return (
<div> <div>
<h1>Chores</h1> <h1>Chores</h1>
<ul className={style.choreList}> <ul className={style.choreList}>{choresRender}</ul>
{chores.map((chore: FlatasticChore) => (
<li key={chore.id} className={style.chore}>
<span className={style.userName}>
{idToNameMap[chore.currentUser]}
</span>
: {chore.title} - {"🪙".repeat(chore.points)}
</li>
))}
</ul>
</div> </div>
); );
} }

View File

@@ -20,3 +20,19 @@
.userName { .userName {
font-weight: bold; font-weight: bold;
} }
.irregular {
background-color: #aaaaaa;
}
.due {
background-color: #e4877e;
}
.notDue {
background-color: #a6cfa6;
}
.timeLeft {
font-weight: bold;
}

View File

@@ -5,21 +5,6 @@ import pasta from "./pasta.ts";
import style from "./style.module.css"; import style from "./style.module.css";
export default function Footer() { export default function Footer() {
const [index, setIndex] = useState(0);
// random shitpost every minute
useEffect(() => {
const timer = setInterval(() => {
setIndex(Math.floor(Math.random() * pasta.length));
console.log("sus!");
}, 60 * 1000);
return () => {
clearInterval(timer);
};
}, []);
const text = pasta[index];
return ( return (
<div className={style.container}> <div className={style.container}>
<div className={style.taskbar}> <div className={style.taskbar}>
@@ -33,16 +18,10 @@ export default function Footer() {
</div> </div>
<span className={style.divider}></span> <span className={style.divider}></span>
<div className={style.windows}> <div className={style.windows}>
<span className={style.window}>🧹 Flatastic</span>
<span className={style.window}>🚊 Timetable</span> <span className={style.window}>🚊 Timetable</span>
<span className={style.window}>🏠 HomeAssistant</span> <span className={style.window}>🕐 Clock</span>
</div> <span className={style.windowActive}>🔔 Terminal</span>
</div> <span className={style.window}>🧹 Flatastic</span>
<div className={style.newsticker}>
<div className={style.info}>BREAKING</div>
<div className={style.marquee}>
<Marquee>{text}</Marquee>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -57,19 +57,13 @@
border-right: 2px solid #828282; border-right: 2px solid #828282;
} }
.newsticker { .windowActive {
display: flex; min-width: 150px;
flex-direction: row; padding-left: 10px;
display: inline-flex;
align-items: center; align-items: center;
border-top: 2px solid white; border-bottom: 2px solid white;
border-left: 2px solid white; border-right: 2px solid white;
border-bottom: 2px solid #828282; border-left: 2px solid #828282;
border-right: 2px solid #828282; border-top: 2px solid #828282;
}
.info {
background-color: red;
color: white;
font-weight: bold;
padding: 5px;
} }

View File

@@ -1,10 +1,24 @@
import { useEffect } from "react";
import { useHomeAssistantStore } from "@/store/homeAssistant"; import { useHomeAssistantStore } from "@/store/homeAssistant";
import { useEffect, useState } from "react";
import pasta from "./pasta.ts";
import style from "./style.module.css"; import style from "./style.module.css";
import pasta from "./pasta.ts";
export default function Terminal() { export default function Terminal() {
const [index, setIndex] = useState(0);
// random shitpost every minute
useEffect(() => {
const timer = setInterval(() => {
setIndex(Math.floor(Math.random() * pasta.length));
}, 60 * 1000);
return () => {
clearInterval(timer);
};
}, []);
const text = pasta[index];
const fetchHomeAssistantData = useHomeAssistantStore( const fetchHomeAssistantData = useHomeAssistantStore(
(state) => state.fetch, (state) => state.fetch,
); );
@@ -23,19 +37,57 @@ export default function Terminal() {
return ( return (
<div className={style.container}> <div className={style.container}>
<span className={style.title}> <div className={style.input}>
-[#rauchen]---muehlburger-bubatz-buben.de- <span className={style.prompt}>[sus@home ~/hallway]{"$"}</span>{" "}
</span> tentfetch
<span className={style.infoMessage}> </div>
<span className={style.bubatzBot}>[BubatzBot]:</span> <div className={style.fetch}>
**UPDATE** Tent: <b>{tentTemperature}°C</b>,{" "} <span>
<b>{tentHumidity}%</b> <pre>
</span> {" "}-///:.{" "}
<span className={style.username}>tent</span>@
<span className={style.message}> <span className={style.hostname}>home</span>
<span className={style.pastaBot}>[anonymous]:</span> </pre>
<span className={style.pasta}>{pasta[0]}</span> </span>
</span> <span>
<pre>
{" "}smhhhhmh\`{" "}os{" "}Arch Linux
</pre>
</span>
<span>
<pre>
{" "}:N{" "}Ns{" "}temp{" "}
<span className={style.temp}>{tentTemperature}°C</span>
</pre>
</span>
<span>
<pre>
{" "}hNNmmmmNNN{" "}humidity{" "}
<span className={style.humidity}>{tentHumidity}%</span>
</pre>
</span>
<span>
<pre>
{" "}NNssussNNN{" "}plants{" "}
<span className={style.plants}>4</span>
</pre>
</span>
<span>
<pre>
{" "}sNn:{" "}sNNo
</pre>
</span>
</div>
<div className={style.input}>
<span className={style.prompt}>[sus@home ~/hallway]{"$"}</span>{" "}
cat msg.txt
</div>
<div className={style.msg}>{text}</div>
<div className={style.input}>
<span className={style.prompt}>
[sus@home ~/hallway]{"$"}
</span>{" "}
</div>
</div> </div>
); );
} }

View File

@@ -1,31 +1,49 @@
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-family: monospace; font-family: monospace;
font-size: 12pt; font-size: 10pt;
background-color: black;
background-color: black; color: white;
color: white; display: flex;
width: 100%; flex-direction: column;
height: 285px; width: 100%;
overflow: hidden; height: 290px;
overflow: hidden;
} }
.title { .fetch {
color: thistle; display: flex;
margin-bottom: 12pt; flex-direction: column;
padding-bottom: 10px;
} }
.bubatzBot { .prompt {
color: skyblue; color: lightgreen;
padding-right: 5px;
} }
.pastaBot { .username {
color: bisque; color: violet;
padding-right: 5px;
} }
.infoMessage { .hostname {
color: lightgreen; color: skyblue;
}
.temp {
color: pink;
font-weight: bold;
}
.humidity {
color: skyblue;
font-weight: bold;
}
.plants {
color: lightgreen;
}
pre {
margin: 0;
} }

View File

@@ -20,8 +20,14 @@ export default function Timetable() {
return ( return (
<div className={style.wrapper}> <div className={style.wrapper}>
<h1>Departures</h1> <h1>Departures</h1>
<DepartureList departures={pStreet.departureList} name="P-Street" /> <DepartureList
<DepartureList departures={hStreet.departureList} name="H-Street" /> departures={pStreet.departureList}
name="Philippstraße"
/>
<DepartureList
departures={hStreet.departureList}
name="Händelstraße"
/>
</div> </div>
); );
} }

View File

@@ -28,8 +28,6 @@ const useFlatasticStore = create(
const data = await flatastic.getTaskList(); const data = await flatastic.getTaskList();
const dataB = await flatastic.getInformation(); const dataB = await flatastic.getInformation();
console.log("Flatastic chores fetched:", data);
console.log("Flatastic information fetched:", dataB);
set({ set({
flatasticData: { flatasticData: {
chores: data as FlatasticChore[], chores: data as FlatasticChore[],

View File

@@ -16,11 +16,6 @@ const useKVVStore = create(
const hStreetJson = await hStreetData.json(); const hStreetJson = await hStreetData.json();
const pStreetJson = await pStreetData.json(); const pStreetJson = await pStreetData.json();
console.log("KVV departures fetched:", {
hStreet: hStreetJson,
pStreet: pStreetJson,
});
set({ set({
hStreet: hStreetJson as DepartureType[], hStreet: hStreetJson as DepartureType[],
pStreet: pStreetJson as DepartureType[], pStreet: pStreetJson as DepartureType[],