1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// This approach uses Val Town's Blob storage for persistence and a simple HTML interface.
// We'll use a form with cat-specific buttons and a 'Feed now' button to input cat feeding data.
// The feeding history is moved to a separate page for a cleaner mobile interface.
// Tradeoffs: Simple implementation without authentication, assuming single household use.
import { blob } from "https://esm.town/v/std/blob";
const KEY = new URL(import.meta.url).pathname;
interface FeedingRecord {
timestamp: number;
feeder: string;
cats: string[];
}
export default async function main(req: Request): Promise<Response> {
const url = new URL(req.url);
if (req.method === "POST") {
const formData = await req.formData();
const feeder = formData.get("feeder") as string;
const cats = formData.getAll("cat") as string[];
if (feeder && cats.length > 0) {
let records: FeedingRecord[] = await blob.getJSON(KEY) || [];
records.push({ timestamp: Date.now(), feeder, cats });
await blob.setJSON(KEY, records);
}
return new Response("", { status: 303, headers: { "Location": "/?success=true" } });
}
if (url.pathname === "/log") {
const records: FeedingRecord[] = await blob.getJSON(KEY) || [];
const selectedDate = url.searchParams.get("date")
|| new Date().toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" }).split(",")[0];
const filteredRecords = records.filter(r => {
const recordDate = new Date(r.timestamp);
const formattedDate = recordDate.toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" }).split(",")[0];
return formattedDate === selectedDate;
});
const feedingList = filteredRecords
.sort((a, b) => b.timestamp - a.timestamp)
.map((r, index) =>
`<li class="mdc-list-item">
<span class="mdc-list-item__text">
<span class="mdc-list-item__primary-text">${
new Date(r.timestamp).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" })
}</span>
<span class="mdc-list-item__secondary-text">${r.feeder} fed ${r.cats.join(", ")}</span>
</span>
<button class="mdc-icon-button material-icons" onclick="deleteEntry(${records.indexOf(r)})">delete</button>
</li>`
)
.join("");
return new Response(
`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lista de alimentação</title>
<link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap" rel="stylesheet">
<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
<style>
body { margin: 0; font-family: 'Outfit', sans-serif; background-color: rgb(244, 222, 179); }
.mdc-top-app-bar { position: fixed; background-color: rgb(255, 138, 138); }
.page-content { padding-top: 64px; }
.mdc-list-item { background-color: rgb(240, 234, 172); margin-bottom: 8px; }
.date-filter { margin: 16px; }
.clear-all-btn { margin: 16px; }
</style>
</head>
<body>
<header class="mdc-top-app-bar">
<div class="mdc-top-app-bar__row">
<section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start">
<a href="/" class="material-icons mdc-top-app-bar__navigation-icon mdc-icon-button">arrow_back</a>
<span class="mdc-top-app-bar__title">Lista de alimentação</span>
</section>
</div>
</header>
<div class="page-content">
<div class="date-filter">
<label for="date-select">Selecione a data:</label>
<input type="date" id="date-select" value="${
selectedDate.split("/").reverse().join("-")
}" onchange="filterByDate(this.value)">
</div>
<ul class="mdc-list mdc-list--two-line">
${feedingList}
</ul>
<button class="mdc-button mdc-button--raised clear-all-btn" onclick="clearAll()">
<span class="mdc-button__label">Limpar tudo</span>