62 lines
2.1 KiB
JavaScript
62 lines
2.1 KiB
JavaScript
// Shared helpers: API access, DOM building, formatting
|
||
|
||
const API = '/api/v1';
|
||
|
||
export async function api(path, options) {
|
||
const response = await fetch(API + path, options);
|
||
if (!response.ok) {
|
||
const error = new Error(`HTTP ${response.status}`);
|
||
error.status = response.status;
|
||
throw error;
|
||
}
|
||
return response.json();
|
||
}
|
||
|
||
/** h('div', {class: 'x'}, 'text', childNode) -> element; strings become text nodes */
|
||
export function h(tag, attrs = {}, ...children) {
|
||
const element = document.createElement(tag);
|
||
for (const [name, value] of Object.entries(attrs)) {
|
||
if (value !== null && value !== undefined && value !== false) {
|
||
element.setAttribute(name, value === true ? '' : value);
|
||
}
|
||
}
|
||
element.append(...children.filter((child) => child !== null && child !== undefined));
|
||
return element;
|
||
}
|
||
|
||
export function alert(kind, ...children) {
|
||
return h('div', { class: `alert alert-${kind}` }, ...children);
|
||
}
|
||
|
||
/** "2026-10-21" -> "21.10.2026" (no Date: a timezone must not shift the day) */
|
||
export function formatDate(iso) {
|
||
const [year, month, day] = iso.split('-');
|
||
return `${day}.${month}.${year}`;
|
||
}
|
||
|
||
/** Rounded to one decimal without trailing zeros: 22.5 -> "22.5", 20 -> "20" */
|
||
export function num(value) {
|
||
return String(Math.round(value * 10) / 10);
|
||
}
|
||
|
||
/** Same with an explicit sign: "+12", "-3.5", "+0" */
|
||
export function signed(value) {
|
||
const rounded = Math.round(value * 10) / 10;
|
||
return (rounded > 0 ? '+' : '') + rounded;
|
||
}
|
||
|
||
/** Approaches as "weight × reps" chips */
|
||
export function approachChips(approaches) {
|
||
return approaches.map((approach) =>
|
||
h('span', { class: 'approach' },
|
||
`${approach.weight ? num(approach.weight) + ' кг' : 'без веса'} × ${approach.reps}`)
|
||
);
|
||
}
|
||
|
||
export function perArmBadge() {
|
||
return h('span', { class: 'badge text-bg-light border', title: 'Вес указан на каждую руку (гантель)' }, 'на руку');
|
||
}
|
||
|
||
export function tip(text, hint) {
|
||
return h('span', { class: 'has-tip', title: hint }, text);
|
||
}
|