@mxpicture/tesla-superchargers-http 0.1.62 → 0.1.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +46 -2
- package/dist/commands.js.map +1 -1
- package/dist/params.d.ts +29 -1
- package/dist/params.d.ts.map +1 -1
- package/dist/params.js +73 -0
- package/dist/params.js.map +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +287 -0
- package/dist/ui.js.map +1 -1
- package/package.json +3 -3
package/dist/commands.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,YAAY,CAAC;AAmD1D;;;;GAIG;AACH,eAAO,MAAM,aAAa,QAAO,eA4I/B,CAAC"}
|
package/dist/commands.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { chargersApi, chargingProcessesApi, clustersApi, failuresApi, invoicesApi, refsApi, slugsApi, zipCodesApi, } from "@mxpicture/tesla-superchargers-api/api";
|
|
1
|
+
import { additionalCostsApi, carsApi, chargersApi, chargingProcessesApi, clustersApi, failuresApi, invoicesApi, refsApi, slugsApi, zipCodesApi, } from "@mxpicture/tesla-superchargers-api/api";
|
|
2
2
|
import { HttpError } from "./errors.js";
|
|
3
|
-
import { chargerSelector, coordinate, countries, favourite, flag, id, locale, maxFetches, optString, optTimestamp, radiusKm, source, threshold, } from "./params.js";
|
|
3
|
+
import { carId, category, chargerSelector, coordinate, cost, countries, description, favourite, flag, id, isoDate, locale, maxFetches, optIsoDate, optString, optTimestamp, radiusKm, recurrence, source, threshold, } from "./params.js";
|
|
4
4
|
/** reset-failures: exactly one of `id` (a single row) or `all` (the source). */
|
|
5
5
|
const resetFailures = async (params) => {
|
|
6
6
|
const src = source(params);
|
|
@@ -16,6 +16,30 @@ const resetFailures = async (params) => {
|
|
|
16
16
|
}
|
|
17
17
|
return await failuresApi().resetAllFailed(src);
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* One additional-cost row from a JSON body. The two `end_date` rules are
|
|
21
|
+
* checked here rather than left to the table's own CHECK, so a wrong
|
|
22
|
+
* combination comes back as a readable 400 instead of a 500; the constraint
|
|
23
|
+
* stays the backstop.
|
|
24
|
+
*/
|
|
25
|
+
const additionalCost = (params) => {
|
|
26
|
+
const date = isoDate(params, "date");
|
|
27
|
+
const end_date = optIsoDate(params, "end_date");
|
|
28
|
+
const every = recurrence(params);
|
|
29
|
+
if (end_date !== null && every !== "monthly")
|
|
30
|
+
throw new HttpError(400, "'end_date' requires recurrence 'monthly'");
|
|
31
|
+
if (end_date !== null && end_date < date)
|
|
32
|
+
throw new HttpError(400, "'end_date' must not be before 'date'");
|
|
33
|
+
return {
|
|
34
|
+
car_id: carId(params),
|
|
35
|
+
category: category(params),
|
|
36
|
+
date,
|
|
37
|
+
description: description(params),
|
|
38
|
+
cost: cost(params),
|
|
39
|
+
recurrence: every,
|
|
40
|
+
end_date,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
19
43
|
/**
|
|
20
44
|
* The HTTP command registry, each entry wired to the api layer directly (never
|
|
21
45
|
* the CLI). Read-only lookups answer GET with query-string params; the
|
|
@@ -46,6 +70,26 @@ export const buildCommands = () => ({
|
|
|
46
70
|
method: "POST",
|
|
47
71
|
run: (p) => chargersApi().updateFavourite(id(p), favourite(p)),
|
|
48
72
|
},
|
|
73
|
+
"query-additional-costs": {
|
|
74
|
+
method: "GET",
|
|
75
|
+
run: () => additionalCostsApi().query(),
|
|
76
|
+
},
|
|
77
|
+
"create-additional-cost": {
|
|
78
|
+
method: "POST",
|
|
79
|
+
run: (p) => additionalCostsApi().create(additionalCost(p)),
|
|
80
|
+
},
|
|
81
|
+
"update-additional-cost": {
|
|
82
|
+
method: "POST",
|
|
83
|
+
run: (p) => additionalCostsApi().update(id(p), additionalCost(p)),
|
|
84
|
+
},
|
|
85
|
+
"delete-additional-cost": {
|
|
86
|
+
method: "POST",
|
|
87
|
+
run: (p) => additionalCostsApi().remove(id(p)),
|
|
88
|
+
},
|
|
89
|
+
"query-cars": {
|
|
90
|
+
method: "GET",
|
|
91
|
+
run: () => carsApi().query(),
|
|
92
|
+
},
|
|
49
93
|
"fetch-charger": {
|
|
50
94
|
method: "GET",
|
|
51
95
|
run: (p) => {
|
package/dist/commands.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,WAAW,EACX,OAAO,EACP,QAAQ,EACR,WAAW,GACZ,MAAM,wCAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/params.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { COST_CATEGORIES, COST_RECURRENCES, type FailureSource } from "@mxpicture/tesla-superchargers-api/types";
|
|
2
2
|
import type { Params } from "./types.js";
|
|
3
3
|
export declare const boolValue: (params: Params, key: string) => boolean;
|
|
4
4
|
export declare const stringValue: (params: Params, key: string) => string;
|
|
@@ -41,4 +41,32 @@ export declare const threshold: (params: Params) => number;
|
|
|
41
41
|
export declare const maxFetches: (params: Params) => number;
|
|
42
42
|
/** A boolean flag: true only for an explicit true / "true" / "1". */
|
|
43
43
|
export declare const flag: (params: Params, key: string) => boolean;
|
|
44
|
+
/** The `car_id` an entry is booked against: a positive integer. */
|
|
45
|
+
export declare const carId: (params: Params) => number;
|
|
46
|
+
/** The cost category; anything outside the `pic_cost_category` enum → 400. */
|
|
47
|
+
export declare const category: (params: Params) => (typeof COST_CATEGORIES)[number];
|
|
48
|
+
/** The recurrence; anything outside the `pic_cost_recurrence` enum → 400. */
|
|
49
|
+
export declare const recurrence: (params: Params) => (typeof COST_RECURRENCES)[number];
|
|
50
|
+
/**
|
|
51
|
+
* A calendar day as `YYYY-MM-DD`, the shape the store binds straight to a
|
|
52
|
+
* `DATE` — never a JS `Date`, which would carry a time zone the column has no
|
|
53
|
+
* room for. The round-trip through `Date.UTC` is what rejects an impossible
|
|
54
|
+
* day like 2026-02-31, which the pattern alone accepts.
|
|
55
|
+
*/
|
|
56
|
+
export declare const isoDate: (params: Params, key: string) => string;
|
|
57
|
+
/** The same, but absent or empty means "no end date" rather than a 400. */
|
|
58
|
+
export declare const optIsoDate: (params: Params, key: string) => string | null;
|
|
59
|
+
/**
|
|
60
|
+
* The entry's label. Non-blank mirrors the table's own CHECK, so an empty one
|
|
61
|
+
* fails as a readable 400 instead of a 500 from a constraint violation.
|
|
62
|
+
*/
|
|
63
|
+
export declare const description: (params: Params) => string;
|
|
64
|
+
/**
|
|
65
|
+
* The amount in EUR, as the NUMERIC(10,2) string the store binds. Any sign: a
|
|
66
|
+
* refund or a `funding` credit is negative. Bounded like the column, for the
|
|
67
|
+
* same reason {@link description} mirrors its CHECK: the cost field is a plain
|
|
68
|
+
* number input, so an extra digit is a plausible typo and a 400 says more than
|
|
69
|
+
* "internal error". Rounded first, since that is the value Postgres sees.
|
|
70
|
+
*/
|
|
71
|
+
export declare const cost: (params: Params) => string;
|
|
44
72
|
//# sourceMappingURL=params.d.ts.map
|
package/dist/params.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,eAAe,EAEf,gBAAgB,EAChB,KAAK,aAAa,EACnB,MAAM,0CAA0C,CAAC;AAElD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAMzC,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,OASvD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,MAOzD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,SAOhE,CAAC;AAqBF,0EAA0E;AAC1E,eAAO,MAAM,UAAU,GACrB,QAAQ,MAAM,KACb;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAGtC,CAAC;AAEH,eAAO,MAAM,EAAE,GAAI,QAAQ,MAAM,WAA8B,CAAC;AAEhE,iEAAiE;AACjE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,WAAqC,CAAC;AAE9E,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,eAgBhD,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,YAAmC,CAAC;AAE5E,qFAAqF;AACrF,eAAO,MAAM,QAAQ,GAAI,QAAQ,MAAM,KAAG,MAAM,GAAG,SACpB,CAAC;AAEhC,eAAO,MAAM,MAAM,GAAI,QAAQ,MAAM,KAAG,MACF,CAAC;AAEvC,qEAAqE;AACrE,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,IAAI,GAAG,SASjE,CAAC;AAIF,uEAAuE;AACvE,eAAO,MAAM,MAAM,GAAI,QAAQ,MAAM,KAAG,aAAa,GAAG,KAMvD,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,MAQ1C,CAAC;AAEF,sEAAsE;AACtE,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,KAAG,MAQ3C,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,IAAI,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,OAGlD,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,KAAK,GAAI,QAAQ,MAAM,KAAG,MAMtC,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,GAAI,QAAQ,MAAM,KAAG,CAAC,OAAO,eAAe,EAAE,MAAM,CAQxE,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,UAAU,GACrB,QAAQ,MAAM,KACb,CAAC,OAAO,gBAAgB,EAAE,MAAM,CAQlC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,OAAO,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,MAarD,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,IAIjE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,KAAG,MAI5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAI,QAAQ,MAAM,KAAG,MAKrC,CAAC"}
|
package/dist/params.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FAILURE_DEPRIORITIZE_THRESHOLD, MAX_CHARGER_FETCHES, } from "@mxpicture/tesla-superchargers-api/common";
|
|
2
|
+
import { COST_CATEGORIES, COST_MAX, COST_RECURRENCES, } from "@mxpicture/tesla-superchargers-api/types";
|
|
2
3
|
import { HttpError } from "./errors.js";
|
|
3
4
|
const bad = (message) => {
|
|
4
5
|
throw new HttpError(400, message);
|
|
@@ -126,4 +127,76 @@ export const flag = (params, key) => {
|
|
|
126
127
|
const value = params[key];
|
|
127
128
|
return value === true || value === "true" || value === "1";
|
|
128
129
|
};
|
|
130
|
+
/** The `car_id` an entry is booked against: a positive integer. */
|
|
131
|
+
export const carId = (params) => {
|
|
132
|
+
const value = params["car_id"];
|
|
133
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
134
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
135
|
+
return bad("'car_id' must be a positive integer");
|
|
136
|
+
return n;
|
|
137
|
+
};
|
|
138
|
+
/** The cost category; anything outside the `pic_cost_category` enum → 400. */
|
|
139
|
+
export const category = (params) => {
|
|
140
|
+
const value = optString(params, "category");
|
|
141
|
+
if (value !== undefined &&
|
|
142
|
+
COST_CATEGORIES.includes(value))
|
|
143
|
+
return value;
|
|
144
|
+
return bad(`'category' must be one of ${COST_CATEGORIES.join(", ")}`);
|
|
145
|
+
};
|
|
146
|
+
/** The recurrence; anything outside the `pic_cost_recurrence` enum → 400. */
|
|
147
|
+
export const recurrence = (params) => {
|
|
148
|
+
const value = optString(params, "recurrence");
|
|
149
|
+
if (value !== undefined &&
|
|
150
|
+
COST_RECURRENCES.includes(value))
|
|
151
|
+
return value;
|
|
152
|
+
return bad(`'recurrence' must be one of ${COST_RECURRENCES.join(", ")}`);
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* A calendar day as `YYYY-MM-DD`, the shape the store binds straight to a
|
|
156
|
+
* `DATE` — never a JS `Date`, which would carry a time zone the column has no
|
|
157
|
+
* room for. The round-trip through `Date.UTC` is what rejects an impossible
|
|
158
|
+
* day like 2026-02-31, which the pattern alone accepts.
|
|
159
|
+
*/
|
|
160
|
+
export const isoDate = (params, key) => {
|
|
161
|
+
const value = optString(params, key);
|
|
162
|
+
if (value === undefined || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
163
|
+
return bad(`'${key}' must be a date as YYYY-MM-DD`);
|
|
164
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
165
|
+
const parsed = new Date(Date.UTC(year, month - 1, day));
|
|
166
|
+
if (parsed.getUTCFullYear() !== year ||
|
|
167
|
+
parsed.getUTCMonth() !== month - 1 ||
|
|
168
|
+
parsed.getUTCDate() !== day)
|
|
169
|
+
return bad(`'${key}' is not a real date`);
|
|
170
|
+
return value;
|
|
171
|
+
};
|
|
172
|
+
/** The same, but absent or empty means "no end date" rather than a 400. */
|
|
173
|
+
export const optIsoDate = (params, key) => {
|
|
174
|
+
const value = params[key];
|
|
175
|
+
if (value === undefined || value === null || value === "")
|
|
176
|
+
return null;
|
|
177
|
+
return isoDate(params, key);
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* The entry's label. Non-blank mirrors the table's own CHECK, so an empty one
|
|
181
|
+
* fails as a readable 400 instead of a 500 from a constraint violation.
|
|
182
|
+
*/
|
|
183
|
+
export const description = (params) => {
|
|
184
|
+
const value = stringValue(params, "description");
|
|
185
|
+
if (value.trim() === "")
|
|
186
|
+
return bad("'description' must not be blank");
|
|
187
|
+
return value;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* The amount in EUR, as the NUMERIC(10,2) string the store binds. Any sign: a
|
|
191
|
+
* refund or a `funding` credit is negative. Bounded like the column, for the
|
|
192
|
+
* same reason {@link description} mirrors its CHECK: the cost field is a plain
|
|
193
|
+
* number input, so an extra digit is a plausible typo and a 400 says more than
|
|
194
|
+
* "internal error". Rounded first, since that is the value Postgres sees.
|
|
195
|
+
*/
|
|
196
|
+
export const cost = (params) => {
|
|
197
|
+
const rounded = numberValue(params, "cost").toFixed(2);
|
|
198
|
+
if (Math.abs(Number(rounded)) > COST_MAX)
|
|
199
|
+
return bad(`'cost' must be between -${COST_MAX} and ${COST_MAX}`);
|
|
200
|
+
return rounded;
|
|
201
|
+
};
|
|
129
202
|
//# sourceMappingURL=params.js.map
|
package/dist/params.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"params.js","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"params.js","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"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"}
|
package/dist/ui.d.ts
CHANGED
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
* The inner <script> deliberately avoids template literals so it can live inside
|
|
14
14
|
* this outer template string without escaping.
|
|
15
15
|
*/
|
|
16
|
-
export declare const UI_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Tesla Superchargers</title>\n<style>\n :root {\n --bg: #f6f7f9; --card: #ffffff; --ink: #1a1d21; --muted: #6b7280;\n --line: #e4e7eb; --accent: #2563eb; --action: #b45309; --ok: #047857;\n --err: #b91c1c; --code-bg: #0f172a; --code-ink: #e2e8f0;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --bg: #0f1216; --card: #171b21; --ink: #e6e8eb; --muted: #9aa4b2;\n --line: #262b33; --accent: #60a5fa; --action: #f59e0b; --ok: #34d399;\n --err: #f87171; --code-bg: #0b0f16; --code-ink: #dbe2ea;\n }\n }\n * { box-sizing: border-box; }\n body {\n margin: 0; background: var(--bg); color: var(--ink);\n font: 15px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n }\n header {\n padding: 20px 24px; border-bottom: 1px solid var(--line);\n display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;\n }\n header h1 { margin: 0; font-size: 18px; font-weight: 650; }\n header p { margin: 0; color: var(--muted); font-size: 13px; }\n main {\n max-width: 1100px; margin: 0 auto; padding: 24px;\n display: grid; gap: 16px;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n }\n .card {\n background: var(--card); border: 1px solid var(--line); border-radius: 10px;\n padding: 16px; display: flex; flex-direction: column; gap: 10px;\n }\n .card .title { display: flex; align-items: center; gap: 8px; }\n .card h2 { margin: 0; font-size: 15px; font-weight: 600; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n .badge {\n font-size: 11px; font-weight: 700; letter-spacing: .03em; padding: 2px 6px;\n border-radius: 5px; color: #fff;\n }\n .badge.get { background: var(--accent); }\n .badge.post { background: var(--action); }\n .desc { margin: 0; color: var(--muted); font-size: 13px; }\n .fields { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }\n .field { display: flex; flex-direction: column; gap: 3px; }\n .field.wide { grid-column: 1 / -1; }\n .field label { font-size: 12px; color: var(--muted); }\n .field input, .field select {\n padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px;\n background: var(--bg); color: var(--ink); font-size: 14px; width: 100%;\n }\n .row { display: flex; align-items: center; gap: 8px; margin-top: 2px; }\n button {\n align-self: flex-start; padding: 7px 14px; border: 0; border-radius: 7px;\n background: var(--accent); color: #fff; font-size: 14px; font-weight: 600;\n cursor: pointer;\n }\n .card.action button { background: var(--action); }\n button:disabled { opacity: .55; cursor: default; }\n pre.result {\n margin: 0; padding: 10px 12px; border-radius: 8px; background: var(--code-bg);\n color: var(--code-ink); font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;\n max-height: 340px; overflow: auto; white-space: pre-wrap; word-break: break-word;\n border-left: 3px solid var(--line);\n }\n pre.result.ok { border-left-color: var(--ok); }\n pre.result.err { border-left-color: var(--err); }\n pre.result.running { border-left-color: var(--accent); color: var(--muted); }\n #favourites { max-width: 1100px; margin: 0 auto; padding: 24px 24px 0; }\n #favourites h2 { margin: 0; font-size: 15px; font-weight: 600; }\n .fav-wrap { overflow-x: auto; }\n table.fav { width: 100%; border-collapse: collapse; font-size: 13px; }\n table.fav th, table.fav td {\n text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--line);\n white-space: nowrap;\n }\n table.fav th { color: var(--muted); font-weight: 600; font-size: 12px; }\n table.fav td.num { text-align: right; font-variant-numeric: tabular-nums; }\n table.fav td.msg { color: var(--muted); }\n button.star {\n padding: 0 4px; background: none; color: var(--muted);\n font-size: 18px; line-height: 1;\n }\n button.star.on { color: var(--accent); }\n .fav-controls { display: flex; gap: 8px; }\n .fav-controls input {\n padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px;\n background: var(--bg); color: var(--ink); font-size: 14px; width: 100%; max-width: 260px;\n }\n table.fav th.sortable { cursor: pointer; user-select: none; }\n table.fav th.sortable::after { content: \" \u2195\"; opacity: .35; }\n table.fav th.sortable.asc::after { content: \" \u2191\"; opacity: 1; }\n table.fav th.sortable.desc::after { content: \" \u2193\"; opacity: 1; }\n .fav-pager { display: flex; align-items: center; gap: 10px; }\n .fav-pager button { align-self: center; padding: 5px 12px; font-size: 13px; }\n .fav-pager span { color: var(--muted); font-size: 13px; }\n</style>\n</head>\n<body>\n<header>\n <h1>Tesla Superchargers</h1>\n <p>Run a command against the price-history database. Actions are highlighted and ask to confirm.</p>\n</header>\n<section id=\"favourites\">\n <div class=\"card\">\n <div class=\"title\"><h2>Favourites</h2></div>\n <p class=\"desc\">Charger favourites. Click a star to toggle; the change persists immediately.</p>\n <div class=\"fav-controls\">\n <input id=\"fav-filter\" type=\"text\" placeholder=\"Filter name or site\u2026\" />\n </div>\n <div class=\"fav-wrap\">\n <table class=\"fav\">\n <thead>\n <tr>\n <th class=\"sortable\" data-key=\"favourite\">\u2605</th>\n <th class=\"sortable\" data-key=\"name\">Name</th>\n <th class=\"sortable\" data-key=\"common_site_name\">Site</th>\n <th class=\"num sortable\" data-key=\"public_stall_count\">Stalls</th>\n <th class=\"num sortable\" data-key=\"failure_count\">Failures</th>\n <th class=\"num\">Lat / Lon</th>\n </tr>\n </thead>\n <tbody id=\"fav-body\"></tbody>\n </table>\n </div>\n <div class=\"fav-pager\">\n <button id=\"fav-prev\" type=\"button\">Prev</button>\n <span id=\"fav-pageinfo\"></span>\n <button id=\"fav-next\" type=\"button\">Next</button>\n </div>\n <pre id=\"fav-status\" class=\"result\" hidden></pre>\n </div>\n</section>\n<main id=\"commands\"></main>\n<script>\n(function () {\n // \"value\" pre-fills the field with the default the endpoint would apply\n // anyway; \"placeholder\" is only a hint for fields that have no fixed default.\n var F = {\n lat: { name: \"lat\", label: \"Latitude\", type: \"text\", value: \"49.3135644\", required: true },\n lon: { name: \"lon\", label: \"Longitude\", type: \"text\", value: \"12.1445587\", required: true },\n radiusKm: { name: \"radiusKm\", label: \"Radius (km)\", type: \"number\", value: \"0.2625\" },\n locale: { name: \"locale\", label: \"Locale\", type: \"text\", value: \"de-DE\" },\n maxFetches: { name: \"maxFetches\", label: \"Max fetches\", type: \"number\", value: \"3\" },\n at: { name: \"at\", label: \"At (local)\", type: \"datetime-local\", wide: true },\n source: { name: \"source\", label: \"Source\", type: \"select\", options: [\"all\", \"charges\", \"chargers\", \"slugs\"] },\n sourceCon: { name: \"source\", label: \"Source\", type: \"select\", options: [\"charges\", \"chargers\", \"slugs\", \"all\"] },\n threshold: { name: \"threshold\", label: \"Threshold\", type: \"number\", value: \"3\" },\n id: { name: \"id\", label: \"Id\", type: \"text\", placeholder: \"reset all if empty\", wide: true },\n latOpt: { name: \"lat\", label: \"Latitude\", type: \"text\", placeholder: \"49.3135644\" },\n lonOpt: { name: \"lon\", label: \"Longitude\", type: \"text\", placeholder: \"12.1445587\" },\n slug: { name: \"slug\", label: \"Slug\", type: \"text\", placeholder: \"location_url_slug\", wide: true },\n chargerId: { name: \"id\", label: \"Charger id\", type: \"text\", placeholder: \"pic_chargers id\" },\n countries: { name: \"countries\", label: \"Countries\", type: \"text\", placeholder: \"DE or DE,AT\", required: true, wide: true }\n };\n\n var COMMANDS = [\n { name: \"query-slugs\", method: \"GET\", kind: \"read\", fields: [F.lat, F.lon, F.radiusKm],\n desc: \"Nearest location slugs (tesla_uuid) to a coordinate.\" },\n { name: \"query-chargers\", method: \"GET\", kind: \"read\", fields: [F.lat, F.lon, F.radiusKm],\n desc: \"Chargers already stored near a coordinate. Never fetches tesla.com \u2014 fast.\" },\n { name: \"fetch-charger\", method: \"GET\", kind: \"read\", fields: [F.latOpt, F.lonOpt, F.slug, F.radiusKm, F.locale],\n desc: \"One charger by slug, or the nearest to a coordinate. May fetch tesla.com on demand.\" },\n { name: \"fetch-chargers\", method: \"GET\", kind: \"read\", fields: [F.lat, F.lon, F.locale, F.radiusKm, F.maxFetches],\n desc: \"Nearest chargers to a coordinate. May fetch tesla.com on demand \u2014 can be slow.\" },\n { name: \"fetch-prices\", method: \"GET\", kind: \"read\", fields: [F.latOpt, F.lonOpt, F.slug, F.chargerId, F.at, F.radiusKm],\n desc: \"Supercharger prices valid at a time (default now), by coordinate, slug or charger id.\" },\n { name: \"query-unpriced-charges\", method: \"GET\", kind: \"read\", fields: [],\n desc: \"TeslaMate Supercharger charges with no cost yet.\" },\n { name: \"query-failures\", method: \"GET\", kind: \"read\", fields: [F.source, F.threshold],\n desc: \"Rows with repeated failures at or above the threshold.\" },\n { name: \"query-unmatched-invoices\", method: \"GET\", kind: \"read\", fields: [],\n desc: \"Supercharger charges without a matched invoice.\" },\n { name: \"sync-invoices-dropbox\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Import Tesla charging-history CSV exports from Dropbox.\" },\n { name: \"import-zip-codes\", method: \"POST\", kind: \"action\", fields: [F.countries],\n desc: \"Import postal codes so rural addresses render their post town, not their hamlet.\" },\n { name: \"run-unpriced-charges\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Backfill cost on unpriced charges from stored Supercharger prices.\" },\n { name: \"build-refs\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Rebuild pic_refs, the drive/idle/charge cost table the cost dashboards read.\" },\n { name: \"assign-clusters\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Cluster the TeslaMate addresses that have no cluster yet.\" },\n { name: \"sync-slugs\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Snapshot-sync pic_slugs against the live Tesla map feed.\" },\n { name: \"fetch-fav-chargers\", method: \"POST\", kind: \"action\", fields: [F.locale],\n desc: \"Refresh the favourite chargers whose cached row went stale.\" },\n { name: \"reset-failures\", method: \"POST\", kind: \"action\", fields: [F.sourceCon, F.id],\n desc: \"Clear failure state. Leave id empty to reset ALL rows of the source.\",\n buildBody: function (v) { return v.id ? { source: v.source, id: v.id } : { source: v.source, all: true }; } },\n { name: \"notify-failures\", method: \"POST\", kind: \"action\", fields: [F.threshold],\n desc: \"Send a Pushover alert for not-yet-notified failures.\" },\n { name: \"notify-unmatched-invoices\", method: \"POST\", kind: \"action\", fields: [],\n desc: \"Send a Pushover alert for Supercharger charges without a matched invoice.\" }\n ];\n\n function el(tag, attrs, text) {\n var e = document.createElement(tag);\n if (attrs) for (var k in attrs) e.setAttribute(k, attrs[k]);\n if (text != null) e.textContent = text;\n return e;\n }\n\n function fieldNode(f) {\n var wrap = el(\"div\", { class: \"field\" + (f.wide ? \" wide\" : \"\") });\n wrap.appendChild(el(\"label\", null, f.label));\n var input;\n if (f.type === \"select\") {\n input = el(\"select\", { name: f.name });\n f.options.forEach(function (o) { input.appendChild(el(\"option\", { value: o }, o)); });\n } else {\n input = el(\"input\", { name: f.name, type: f.type });\n if (f.placeholder) input.setAttribute(\"placeholder\", f.placeholder);\n if (f.value) input.setAttribute(\"value\", f.value);\n }\n wrap.appendChild(input);\n return wrap;\n }\n\n function collect(cmd, form) {\n var values = {};\n cmd.fields.forEach(function (f) {\n var input = form.elements[f.name];\n var val = input ? String(input.value).trim() : \"\";\n if (f.type === \"datetime-local\" && val) val = new Date(val).toISOString();\n if (val !== \"\") values[f.name] = val;\n });\n return values;\n }\n\n function show(out, cls, text) {\n out.hidden = false;\n out.className = \"result \" + cls;\n out.textContent = text;\n }\n\n function pretty(text) {\n try { return JSON.stringify(JSON.parse(text), null, 2); } catch (e) { return text; }\n }\n\n function run(cmd, form, out, btn) {\n var values = collect(cmd, form);\n for (var i = 0; i < cmd.fields.length; i++) {\n var f = cmd.fields[i];\n if (f.required && !values[f.name]) { show(out, \"err\", \"Please fill in \" + f.label + \".\"); return; }\n }\n\n var url = \"/\" + cmd.name;\n var opts = { method: cmd.method, headers: {} };\n if (cmd.method === \"GET\") {\n var qs = new URLSearchParams(values).toString();\n if (qs) url += \"?\" + qs;\n } else {\n if (!window.confirm(\"Run \" + cmd.name + \"? This performs a real action.\")) return;\n var body = cmd.buildBody ? cmd.buildBody(values) : values;\n opts.headers[\"content-type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n\n btn.disabled = true;\n show(out, \"running\", \"Running\u2026\");\n fetch(url, opts).then(function (res) {\n return res.text().then(function (text) {\n if (res.ok) show(out, \"ok\", pretty(text));\n else if (res.status === 401) show(out, \"err\", \"401 Unauthorized \u2014 sign in via the SSO portal (running locally, the command endpoints need the bearer token).\");\n else show(out, \"err\", res.status + \" \u2014 \" + pretty(text));\n });\n }).catch(function (e) {\n show(out, \"err\", \"Request failed: \" + (e && e.message ? e.message : e));\n }).then(function () { btn.disabled = false; });\n }\n\n function card(cmd) {\n var form = el(\"form\", { class: \"card \" + cmd.kind });\n var title = el(\"div\", { class: \"title\" });\n title.appendChild(el(\"span\", { class: \"badge \" + (cmd.method === \"GET\" ? \"get\" : \"post\") }, cmd.method));\n title.appendChild(el(\"h2\", null, cmd.name));\n form.appendChild(title);\n form.appendChild(el(\"p\", { class: \"desc\" }, cmd.desc));\n\n if (cmd.fields.length) {\n var fields = el(\"div\", { class: \"fields\" });\n cmd.fields.forEach(function (f) { fields.appendChild(fieldNode(f)); });\n form.appendChild(fields);\n }\n\n var btn = el(\"button\", { type: \"submit\" }, cmd.kind === \"action\" ? \"Run action\" : \"Run\");\n form.appendChild(btn);\n var out = el(\"pre\", { class: \"result\", hidden: \"hidden\" });\n form.appendChild(out);\n\n form.addEventListener(\"submit\", function (e) { e.preventDefault(); run(cmd, form, out, btn); });\n return form;\n }\n\n var favBody = document.getElementById(\"fav-body\");\n var favStatus = document.getElementById(\"fav-status\");\n var favFilterInput = document.getElementById(\"fav-filter\");\n var favPrev = document.getElementById(\"fav-prev\");\n var favNext = document.getElementById(\"fav-next\");\n var favPageInfo = document.getElementById(\"fav-pageinfo\");\n var favSortables = document.querySelectorAll(\"#favourites th.sortable\");\n\n var FAV_PAGE_SIZE = 10;\n var favRows = []; // full dataset from the server\n var favFilter = \"\"; // lower-cased name/site filter\n // Favourites first by default; the sort is stable, so within a group the\n // server's own order survives.\n var favSort = { key: \"favourite\", dir: -1 }; // dir: 1 asc, -1 desc\n var favPage = 0; // zero-based page index\n\n function favMessage(text) {\n favBody.innerHTML = \"\";\n var tr = el(\"tr\");\n tr.appendChild(el(\"td\", { colspan: \"6\", class: \"msg\" }, text));\n favBody.appendChild(tr);\n }\n\n function setStar(btn, on) {\n btn.textContent = on ? \"\u2605\" : \"\u2606\";\n btn.className = \"star\" + (on ? \" on\" : \"\");\n }\n\n function toggleFav(row, btn) {\n var next = !row.favourite;\n btn.disabled = true;\n setStar(btn, next); // optimistic\n fetch(\"/update-fav-charger\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ id: String(row.id), favourite: next }),\n }).then(function (res) {\n return res.text().then(function (text) {\n if (res.ok) {\n row.favourite = JSON.parse(text).favourite;\n setStar(btn, row.favourite);\n favStatus.hidden = true;\n } else {\n setStar(btn, row.favourite); // revert\n if (res.status === 401) show(favStatus, \"err\", \"401 Unauthorized \u2014 sign in via the SSO portal (running locally, the command endpoints need the bearer token).\");\n else show(favStatus, \"err\", res.status + \" \u2014 \" + pretty(text));\n }\n });\n }).catch(function (e) {\n setStar(btn, row.favourite); // revert\n show(favStatus, \"err\", \"Request failed: \" + (e && e.message ? e.message : e));\n }).then(function () { btn.disabled = false; });\n }\n\n function starButton(row) {\n var btn = el(\"button\", { type: \"button\", class: \"star\" + (row.favourite ? \" on\" : \"\"), title: \"Toggle favourite\" }, row.favourite ? \"\u2605\" : \"\u2606\");\n btn.addEventListener(\"click\", function () { toggleFav(row, btn); });\n return btn;\n }\n\n function coord(n) {\n return typeof n === \"number\" ? n.toFixed(4) : \"\u2014\";\n }\n\n function favCmp(a, b) {\n if (typeof a === \"boolean\") a = a ? 1 : 0;\n if (typeof b === \"boolean\") b = b ? 1 : 0;\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n return String(a).localeCompare(String(b));\n }\n\n function favRowNode(row) {\n var tr = el(\"tr\");\n var star = el(\"td\");\n star.appendChild(starButton(row));\n tr.appendChild(star);\n tr.appendChild(el(\"td\", null, row.name));\n tr.appendChild(el(\"td\", null, row.common_site_name));\n tr.appendChild(el(\"td\", { class: \"num\" }, String(row.public_stall_count)));\n tr.appendChild(el(\"td\", { class: \"num\" }, String(row.failure_count)));\n tr.appendChild(el(\"td\", { class: \"num\" }, coord(row.latitude) + \" / \" + coord(row.longitude)));\n return tr;\n }\n\n // filter -> sort against the full dataset, returning the rows to page through.\n function favView() {\n var rows = favRows;\n if (favFilter) {\n rows = rows.filter(function (r) {\n return String(r.name || \"\").toLowerCase().indexOf(favFilter) >= 0 ||\n String(r.common_site_name || \"\").toLowerCase().indexOf(favFilter) >= 0;\n });\n }\n if (favSort.key) {\n var k = favSort.key, dir = favSort.dir;\n rows = rows.slice().sort(function (a, b) { return favCmp(a[k], b[k]) * dir; });\n }\n return rows;\n }\n\n function updateFavPager(total, pages) {\n favPrev.disabled = favPage <= 0;\n favNext.disabled = favPage >= pages - 1;\n if (!total) { favPageInfo.textContent = \"0 of 0\"; return; }\n var from = favPage * FAV_PAGE_SIZE + 1;\n var to = Math.min(total, (favPage + 1) * FAV_PAGE_SIZE);\n favPageInfo.textContent = from + \"\u2013\" + to + \" of \" + total;\n }\n\n function updateFavSortIndicators() {\n Array.prototype.forEach.call(favSortables, function (th) {\n th.classList.remove(\"asc\", \"desc\");\n if (th.getAttribute(\"data-key\") === favSort.key) {\n th.classList.add(favSort.dir > 0 ? \"asc\" : \"desc\");\n }\n });\n }\n\n function renderFav() {\n var rows = favView();\n var pages = Math.max(1, Math.ceil(rows.length / FAV_PAGE_SIZE));\n if (favPage > pages - 1) favPage = pages - 1;\n if (favPage < 0) favPage = 0;\n\n favBody.innerHTML = \"\";\n if (!favRows.length) favMessage(\"No favourites yet.\");\n else if (!rows.length) favMessage(\"No matches.\");\n else {\n var start = favPage * FAV_PAGE_SIZE;\n rows.slice(start, start + FAV_PAGE_SIZE).forEach(function (row) {\n favBody.appendChild(favRowNode(row));\n });\n }\n updateFavPager(rows.length, pages);\n updateFavSortIndicators();\n }\n\n function loadFavourites() {\n show(favStatus, \"running\", \"Loading favourites\u2026\");\n fetch(\"/query-fav-chargers\").then(function (res) {\n return res.text().then(function (text) {\n if (res.ok) { favRows = JSON.parse(text); favPage = 0; renderFav(); favStatus.hidden = true; }\n else if (res.status === 401) show(favStatus, \"err\", \"401 Unauthorized \u2014 sign in via the SSO portal (running locally, the command endpoints need the bearer token).\");\n else show(favStatus, \"err\", res.status + \" \u2014 \" + pretty(text));\n });\n }).catch(function (e) {\n show(favStatus, \"err\", \"Request failed: \" + (e && e.message ? e.message : e));\n });\n }\n\n favFilterInput.addEventListener(\"input\", function () {\n favFilter = favFilterInput.value.trim().toLowerCase();\n favPage = 0;\n renderFav();\n });\n favPrev.addEventListener(\"click\", function () { favPage -= 1; renderFav(); });\n favNext.addEventListener(\"click\", function () { favPage += 1; renderFav(); });\n Array.prototype.forEach.call(favSortables, function (th) {\n th.addEventListener(\"click\", function () {\n var key = th.getAttribute(\"data-key\");\n if (favSort.key === key) favSort.dir = -favSort.dir;\n else { favSort.key = key; favSort.dir = 1; }\n favPage = 0;\n renderFav();\n });\n });\n\n var root = document.getElementById(\"commands\");\n COMMANDS.forEach(function (cmd) { root.appendChild(card(cmd)); });\n loadFavourites();\n})();\n</script>\n</body>\n</html>\n";
|
|
16
|
+
export declare const UI_HTML: string;
|
|
17
17
|
//# sourceMappingURL=ui.d.ts.map
|
package/dist/ui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,OAAO,QA2vBnB,CAAC"}
|
package/dist/ui.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FAILURE_DEPRIORITIZE_THRESHOLD, MAX_CHARGER_FETCHES, } from "@mxpicture/tesla-superchargers-api/common";
|
|
2
|
+
import { COST_CATEGORIES, COST_RECURRENCES, } from "@mxpicture/tesla-superchargers-api/types";
|
|
2
3
|
import { COVERED_MARGIN, COVERED_RADIUS_KM, } from "@mxpicture/tesla-superchargers-mate/common";
|
|
3
4
|
/**
|
|
4
5
|
* The web UI, served at `GET /` by the server (see `server.ts`). It is one
|
|
@@ -116,6 +117,22 @@ export const UI_HTML = `<!doctype html>
|
|
|
116
117
|
.fav-pager { display: flex; align-items: center; gap: 10px; }
|
|
117
118
|
.fav-pager button { align-self: center; padding: 5px 12px; font-size: 13px; }
|
|
118
119
|
.fav-pager span { color: var(--muted); font-size: 13px; }
|
|
120
|
+
#addcosts { max-width: 1100px; margin: 0 auto; padding: 24px 24px 0; }
|
|
121
|
+
#addcosts h2 { margin: 0; font-size: 15px; font-weight: 600; }
|
|
122
|
+
.ac-wrap { overflow-x: auto; }
|
|
123
|
+
table.ac input, table.ac select {
|
|
124
|
+
padding: 5px 7px; border: 1px solid var(--line); border-radius: 6px;
|
|
125
|
+
background: var(--bg); color: var(--ink); font-size: 13px;
|
|
126
|
+
}
|
|
127
|
+
table.ac input:disabled { opacity: .5; }
|
|
128
|
+
table.ac input.descr { min-width: 180px; }
|
|
129
|
+
table.ac input.cost { width: 100px; text-align: right; }
|
|
130
|
+
table.ac .dates { display: flex; align-items: center; gap: 4px; }
|
|
131
|
+
table.ac tbody.form td { border-bottom-width: 2px; }
|
|
132
|
+
table.ac td.actions { white-space: nowrap; }
|
|
133
|
+
table.ac button.small { align-self: center; padding: 5px 12px; font-size: 13px; }
|
|
134
|
+
button.icon { padding: 0 4px; background: none; color: var(--muted); font-size: 15px; line-height: 1; }
|
|
135
|
+
button.icon:hover { color: var(--accent); }
|
|
119
136
|
</style>
|
|
120
137
|
</head>
|
|
121
138
|
<body>
|
|
@@ -153,6 +170,33 @@ export const UI_HTML = `<!doctype html>
|
|
|
153
170
|
<pre id="fav-status" class="result" hidden></pre>
|
|
154
171
|
</div>
|
|
155
172
|
</section>
|
|
173
|
+
<section id="addcosts">
|
|
174
|
+
<div class="card">
|
|
175
|
+
<div class="title"><h2>Additional costs</h2></div>
|
|
176
|
+
<p class="desc">
|
|
177
|
+
Costs TeslaMate never sees — service, repairs, tyres, insurance, tax, subsidies.
|
|
178
|
+
A monthly cost is one entry: its date is the first month, its end date the last
|
|
179
|
+
(leave it empty while it still runs) and its cost the amount per month.
|
|
180
|
+
</p>
|
|
181
|
+
<div class="ac-wrap">
|
|
182
|
+
<table class="fav ac">
|
|
183
|
+
<thead>
|
|
184
|
+
<tr>
|
|
185
|
+
<th>Car</th>
|
|
186
|
+
<th>Category</th>
|
|
187
|
+
<th>Date</th>
|
|
188
|
+
<th>Description</th>
|
|
189
|
+
<th class="num">Cost (€)</th>
|
|
190
|
+
<th></th>
|
|
191
|
+
</tr>
|
|
192
|
+
</thead>
|
|
193
|
+
<tbody id="ac-form" class="form"></tbody>
|
|
194
|
+
<tbody id="ac-body"></tbody>
|
|
195
|
+
</table>
|
|
196
|
+
</div>
|
|
197
|
+
<pre id="ac-status" class="result" hidden></pre>
|
|
198
|
+
</div>
|
|
199
|
+
</section>
|
|
156
200
|
<main id="commands"></main>
|
|
157
201
|
<script>
|
|
158
202
|
(function () {
|
|
@@ -485,9 +529,252 @@ export const UI_HTML = `<!doctype html>
|
|
|
485
529
|
});
|
|
486
530
|
});
|
|
487
531
|
|
|
532
|
+
// --- Additional costs -----------------------------------------------
|
|
533
|
+
// Deliberately no filter, no sort control and no pager: this is a handful
|
|
534
|
+
// of rows per car per year. Not part of the COMMANDS grid either — its
|
|
535
|
+
// confirm-on-every-POST would fire on every inline save.
|
|
536
|
+
|
|
537
|
+
// Interpolated from the generated enums, so the DDL's own value lists are
|
|
538
|
+
// what the dropdowns offer — the same values the endpoints validate against.
|
|
539
|
+
var AC_CATEGORIES = ${JSON.stringify(COST_CATEGORIES)};
|
|
540
|
+
var AC_RECURRENCES = ${JSON.stringify(COST_RECURRENCES)};
|
|
541
|
+
|
|
542
|
+
var acForm = document.getElementById("ac-form");
|
|
543
|
+
var acBody = document.getElementById("ac-body");
|
|
544
|
+
var acStatus = document.getElementById("ac-status");
|
|
545
|
+
|
|
546
|
+
var acRows = []; // every stored entry
|
|
547
|
+
var acCars = []; // TeslaMate's cars, for the picker and the row labels
|
|
548
|
+
|
|
549
|
+
function acFail(res, text) {
|
|
550
|
+
if (res.status === 401) show(acStatus, "err", "401 Unauthorized — sign in via the SSO portal (running locally, the command endpoints need the bearer token).");
|
|
551
|
+
else show(acStatus, "err", res.status + " — " + pretty(text));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function acSend(url, body, btn, done) {
|
|
555
|
+
show(acStatus, "running", "Saving…");
|
|
556
|
+
fetch(url, {
|
|
557
|
+
method: "POST",
|
|
558
|
+
headers: { "content-type": "application/json" },
|
|
559
|
+
body: JSON.stringify(body),
|
|
560
|
+
}).then(function (res) {
|
|
561
|
+
return res.text().then(function (text) {
|
|
562
|
+
if (res.ok) { acStatus.hidden = true; done(JSON.parse(text)); }
|
|
563
|
+
else acFail(res, text);
|
|
564
|
+
});
|
|
565
|
+
}).catch(function (e) {
|
|
566
|
+
show(acStatus, "err", "Request failed: " + (e && e.message ? e.message : e));
|
|
567
|
+
}).then(function () { btn.disabled = false; });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function carLabel(id) {
|
|
571
|
+
for (var i = 0; i < acCars.length; i++)
|
|
572
|
+
if (acCars[i].id === id) return acCars[i].name || ("Car #" + acCars[i].id);
|
|
573
|
+
return "Car #" + id; // a car TeslaMate no longer has; nothing links them
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// The five editable cells, shared by the add row and an inline edit: the
|
|
577
|
+
// same inputs, populated from the row when editing one.
|
|
578
|
+
function acEditor(row) {
|
|
579
|
+
var car = el("select", { name: "car_id" });
|
|
580
|
+
acCars.forEach(function (c) {
|
|
581
|
+
car.appendChild(el("option", { value: String(c.id) }, c.name || ("Car #" + c.id)));
|
|
582
|
+
});
|
|
583
|
+
var category = el("select", { name: "category" });
|
|
584
|
+
AC_CATEGORIES.forEach(function (c) { category.appendChild(el("option", { value: c }, c)); });
|
|
585
|
+
var recurrence = el("select", { name: "recurrence" });
|
|
586
|
+
AC_RECURRENCES.forEach(function (r) { recurrence.appendChild(el("option", { value: r }, r)); });
|
|
587
|
+
var date = el("input", { type: "date", name: "date", title: "Invoice date, or the first month of a recurring cost" });
|
|
588
|
+
var endDate = el("input", { type: "date", name: "end_date", title: "Last month; empty means still running" });
|
|
589
|
+
var descr = el("input", { type: "text", name: "description", class: "descr", placeholder: "What it was for" });
|
|
590
|
+
var cost = el("input", { type: "number", step: "0.01", name: "cost", class: "cost", placeholder: "0.00" });
|
|
591
|
+
|
|
592
|
+
// The table rejects an end date on a one-off row, so do not offer one.
|
|
593
|
+
function syncRecurrence() {
|
|
594
|
+
endDate.disabled = recurrence.value !== "monthly";
|
|
595
|
+
if (endDate.disabled) endDate.value = "";
|
|
596
|
+
}
|
|
597
|
+
recurrence.addEventListener("change", syncRecurrence);
|
|
598
|
+
|
|
599
|
+
if (row) {
|
|
600
|
+
car.value = String(row.car_id);
|
|
601
|
+
category.value = row.category;
|
|
602
|
+
recurrence.value = row.recurrence;
|
|
603
|
+
date.value = row.date;
|
|
604
|
+
endDate.value = row.end_date || "";
|
|
605
|
+
descr.value = row.description;
|
|
606
|
+
cost.value = row.cost;
|
|
607
|
+
}
|
|
608
|
+
syncRecurrence();
|
|
609
|
+
|
|
610
|
+
var dates = el("div", { class: "dates" });
|
|
611
|
+
dates.appendChild(date);
|
|
612
|
+
dates.appendChild(recurrence);
|
|
613
|
+
dates.appendChild(endDate);
|
|
614
|
+
|
|
615
|
+
var carCell = el("td");
|
|
616
|
+
carCell.appendChild(car);
|
|
617
|
+
var categoryCell = el("td");
|
|
618
|
+
categoryCell.appendChild(category);
|
|
619
|
+
var dateCell = el("td");
|
|
620
|
+
dateCell.appendChild(dates);
|
|
621
|
+
var descrCell = el("td");
|
|
622
|
+
descrCell.appendChild(descr);
|
|
623
|
+
var costCell = el("td", { class: "num" });
|
|
624
|
+
costCell.appendChild(cost);
|
|
625
|
+
|
|
626
|
+
return {
|
|
627
|
+
cells: [carCell, categoryCell, dateCell, descrCell, costCell],
|
|
628
|
+
// The request body, or null after saying which field is still missing.
|
|
629
|
+
body: function () {
|
|
630
|
+
if (!car.value) { show(acStatus, "err", "Pick a car first."); return null; }
|
|
631
|
+
if (!date.value) { show(acStatus, "err", "Please fill in a date."); return null; }
|
|
632
|
+
if (!descr.value.trim()) { show(acStatus, "err", "Please fill in a description."); return null; }
|
|
633
|
+
if (cost.value === "") { show(acStatus, "err", "Please fill in a cost."); return null; }
|
|
634
|
+
return {
|
|
635
|
+
car_id: Number(car.value),
|
|
636
|
+
category: category.value,
|
|
637
|
+
date: date.value,
|
|
638
|
+
description: descr.value.trim(),
|
|
639
|
+
cost: Number(cost.value),
|
|
640
|
+
recurrence: recurrence.value,
|
|
641
|
+
end_date: endDate.value || null,
|
|
642
|
+
};
|
|
643
|
+
},
|
|
644
|
+
clear: function () { descr.value = ""; cost.value = ""; },
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function acMessage(text) {
|
|
649
|
+
var tr = el("tr");
|
|
650
|
+
tr.appendChild(el("td", { colspan: "6", class: "msg" }, text));
|
|
651
|
+
acBody.appendChild(tr);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// A monthly entry shows the period it covers and the rate it is charged at;
|
|
655
|
+
// the UI never multiplies the two out.
|
|
656
|
+
function acPeriod(row) {
|
|
657
|
+
if (row.recurrence !== "monthly") return row.date;
|
|
658
|
+
return row.date + " → " + (row.end_date || "…");
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function acAmount(row) {
|
|
662
|
+
return row.recurrence === "monthly" ? row.cost + " / mo" : row.cost;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function acRowNode(row) {
|
|
666
|
+
var tr = el("tr");
|
|
667
|
+
tr.appendChild(el("td", null, carLabel(row.car_id)));
|
|
668
|
+
tr.appendChild(el("td", null, row.category));
|
|
669
|
+
tr.appendChild(el("td", null, acPeriod(row)));
|
|
670
|
+
tr.appendChild(el("td", null, row.description));
|
|
671
|
+
tr.appendChild(el("td", { class: "num" }, acAmount(row)));
|
|
672
|
+
|
|
673
|
+
var actions = el("td", { class: "actions" });
|
|
674
|
+
var edit = el("button", { type: "button", class: "icon", title: "Edit" }, "✎");
|
|
675
|
+
edit.addEventListener("click", function () { acEditRow(tr, row); });
|
|
676
|
+
var remove = el("button", { type: "button", class: "icon", title: "Delete" }, "🗑");
|
|
677
|
+
remove.addEventListener("click", function () {
|
|
678
|
+
if (!window.confirm('Delete "' + row.description + '"? Nothing can rebuild it.')) return;
|
|
679
|
+
remove.disabled = true;
|
|
680
|
+
acSend("/delete-additional-cost", { id: String(row.id) }, remove, function () {
|
|
681
|
+
acRows = acRows.filter(function (r) { return r.id !== row.id; });
|
|
682
|
+
renderAc();
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
actions.appendChild(edit);
|
|
686
|
+
actions.appendChild(remove);
|
|
687
|
+
tr.appendChild(actions);
|
|
688
|
+
return tr;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function acEditRow(tr, row) {
|
|
692
|
+
var editor = acEditor(row);
|
|
693
|
+
var editing = el("tr");
|
|
694
|
+
editor.cells.forEach(function (td) { editing.appendChild(td); });
|
|
695
|
+
|
|
696
|
+
var actions = el("td", { class: "actions" });
|
|
697
|
+
var save = el("button", { type: "button", class: "small" }, "Save");
|
|
698
|
+
save.addEventListener("click", function () {
|
|
699
|
+
var body = editor.body();
|
|
700
|
+
if (!body) return;
|
|
701
|
+
body.id = String(row.id);
|
|
702
|
+
save.disabled = true;
|
|
703
|
+
acSend("/update-additional-cost", body, save, function (updated) {
|
|
704
|
+
if (!updated) { show(acStatus, "err", "Entry " + row.id + " is gone — reload the page."); return; }
|
|
705
|
+
acRows = acRows.map(function (r) { return r.id === row.id ? updated : r; });
|
|
706
|
+
renderAc();
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
var cancel = el("button", { type: "button", class: "icon", title: "Cancel" }, "✕");
|
|
710
|
+
cancel.addEventListener("click", function () { renderAc(); });
|
|
711
|
+
actions.appendChild(save);
|
|
712
|
+
actions.appendChild(cancel);
|
|
713
|
+
editing.appendChild(actions);
|
|
714
|
+
|
|
715
|
+
tr.replaceWith(editing);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function renderAc() {
|
|
719
|
+
// Newest day first, and within a day the newest entry — what the endpoint
|
|
720
|
+
// orders by, kept up after a local add or edit.
|
|
721
|
+
acRows.sort(function (a, b) {
|
|
722
|
+
if (a.date !== b.date) return a.date < b.date ? 1 : -1;
|
|
723
|
+
return Number(b.id) - Number(a.id);
|
|
724
|
+
});
|
|
725
|
+
acBody.innerHTML = "";
|
|
726
|
+
if (!acRows.length) acMessage("No entries yet.");
|
|
727
|
+
else acRows.forEach(function (row) { acBody.appendChild(acRowNode(row)); });
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function renderAcForm() {
|
|
731
|
+
var editor = acEditor(null);
|
|
732
|
+
var tr = el("tr");
|
|
733
|
+
editor.cells.forEach(function (td) { tr.appendChild(td); });
|
|
734
|
+
var actions = el("td", { class: "actions" });
|
|
735
|
+
var add = el("button", { type: "button", class: "small" }, "Add");
|
|
736
|
+
add.addEventListener("click", function () {
|
|
737
|
+
var body = editor.body();
|
|
738
|
+
if (!body) return;
|
|
739
|
+
add.disabled = true;
|
|
740
|
+
acSend("/create-additional-cost", body, add, function (created) {
|
|
741
|
+
acRows.push(created);
|
|
742
|
+
editor.clear();
|
|
743
|
+
renderAc();
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
actions.appendChild(add);
|
|
747
|
+
tr.appendChild(actions);
|
|
748
|
+
acForm.innerHTML = "";
|
|
749
|
+
acForm.appendChild(tr);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// The cars first: the add form's picker and every row's car label read them.
|
|
753
|
+
function loadAdditionalCosts() {
|
|
754
|
+
show(acStatus, "running", "Loading additional costs…");
|
|
755
|
+
fetch("/query-cars").then(function (res) {
|
|
756
|
+
return res.text().then(function (text) {
|
|
757
|
+
if (!res.ok) return acFail(res, text);
|
|
758
|
+
acCars = JSON.parse(text);
|
|
759
|
+
return fetch("/query-additional-costs").then(function (costRes) {
|
|
760
|
+
return costRes.text().then(function (costText) {
|
|
761
|
+
if (!costRes.ok) return acFail(costRes, costText);
|
|
762
|
+
acRows = JSON.parse(costText);
|
|
763
|
+
acStatus.hidden = true;
|
|
764
|
+
renderAcForm();
|
|
765
|
+
renderAc();
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
}).catch(function (e) {
|
|
770
|
+
show(acStatus, "err", "Request failed: " + (e && e.message ? e.message : e));
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
488
774
|
var root = document.getElementById("commands");
|
|
489
775
|
COMMANDS.forEach(function (cmd) { root.appendChild(card(cmd)); });
|
|
490
776
|
loadFavourites();
|
|
777
|
+
loadAdditionalCosts();
|
|
491
778
|
})();
|
|
492
779
|
</script>
|
|
493
780
|
</body>
|
package/dist/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,4CAA4C,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,eAAe,EACf,gBAAgB,GACjB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,4CAA4C,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+FA6LwE,iBAAiB,GAAG,cAAc;;+FAElC,mBAAmB;;;;+FAInB,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAqUrG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC;yBAC9B,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkPxD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mxpicture/tesla-superchargers-http",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.64",
|
|
4
4
|
"description": "Http server for interacting with superchargers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "MXPicture",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@mxpicture/tesla-superchargers-api": "^0.1.
|
|
36
|
-
"@mxpicture/tesla-superchargers-mate": "^0.1.
|
|
35
|
+
"@mxpicture/tesla-superchargers-api": "^0.1.64",
|
|
36
|
+
"@mxpicture/tesla-superchargers-mate": "^0.1.64"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^25.2.3",
|