278 files
This commit is contained in:
+65
-154
@@ -14,14 +14,15 @@
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatInterval, formatRecurrence, formatNumber, langOf } from "../styles";
|
||||
import { sharedStyles, t, STATUS_COLORS, formatDate, formatInterval, formatRecurrence, formatCost, formatDuration, currencySymbolOf, langOf, syncCurrencyDecimals} from "../styles";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { isoDateLocal } from "../helpers/calendar-bucket";
|
||||
import { buildCompleteDialogArgs } from "../helpers/complete-dialog-args";
|
||||
import { phaseLabel } from "../helpers/phases";
|
||||
import { historyPhotoIds } from "../helpers/history-photos";
|
||||
import { entryReadingValues, readingSlotDelta } from "../helpers/reading-slots";
|
||||
import "./history-photo";
|
||||
import { buildHistoryEntryDraft } from "../helpers/history-draft";
|
||||
import { readingSlotDelta } from "../helpers/reading-slots";
|
||||
import { taskRef } from "../helpers/reference";
|
||||
import { renderHistoryEntry } from "../renderers/history";
|
||||
import { renderWeibullSection } from "../renderers/weibull";
|
||||
import { renderPredictionSection } from "../renderers/prediction";
|
||||
import { renderRecommendationBars } from "../renderers/recommendation";
|
||||
@@ -40,7 +41,7 @@ import type {
|
||||
|
||||
interface MaintenanceObjectFull {
|
||||
entry_id: string;
|
||||
object: { id: string; name: string };
|
||||
object: { id: string; name: string; ref_no?: number | null };
|
||||
tasks: MaintenanceTask[];
|
||||
}
|
||||
|
||||
@@ -52,6 +53,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
@state() private _taskId: string | null = null;
|
||||
@state() private _task: MaintenanceTask | null = null;
|
||||
@state() private _objectName = "";
|
||||
/** #170: "8.3" once the backend numbered the task — the history rows' "#8.3-n" chips. */
|
||||
@state() private _taskRef: string | null = null;
|
||||
@state() private _busy = false;
|
||||
@state() private _error = "";
|
||||
@state() private _showSkip = false;
|
||||
@@ -100,12 +103,13 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
try {
|
||||
const r = await this.hass.connection.sendMessagePromise<{
|
||||
features?: Partial<AdvancedFeatures>;
|
||||
budget?: { currency_symbol?: string };
|
||||
budget?: { currency_symbol?: string; currency_decimals?: number };
|
||||
}>({ type: "maintenance_supporter/settings" });
|
||||
if (r?.features) {
|
||||
this._features = { ...this._features, ...r.features };
|
||||
}
|
||||
this._currencySymbol = r?.budget?.currency_symbol || "";
|
||||
this._currencySymbol = currencySymbolOf(r?.budget);
|
||||
syncCurrencyDecimals(r?.budget);
|
||||
this._featuresLoaded = true;
|
||||
} catch {
|
||||
// Settings endpoint unavailable — leave defaults (all false), the
|
||||
@@ -129,6 +133,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
this._objectName = r.object?.name || "";
|
||||
const found = (r.tasks || []).find((t) => t.id === this._taskId);
|
||||
this._task = found ?? null;
|
||||
this._taskRef = taskRef(r.object, found);
|
||||
} catch (e) {
|
||||
this._error = describeWsError(e, this._lang);
|
||||
}
|
||||
@@ -311,7 +316,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
interval: this._task.suggested_interval,
|
||||
});
|
||||
if (ok) {
|
||||
this._toast = t("suggestion_applied", this._lang) || "Applied";
|
||||
this._toast = t("suggestion_applied", this._lang);
|
||||
this._notifyChanged("apply_suggestion");
|
||||
// Refresh local task so the recommendation card hides
|
||||
await this._loadTask();
|
||||
@@ -336,8 +341,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
this._toast = r.recommended_interval
|
||||
// The analyzer always works in DAYS (helpers/interval_analyzer.py), so
|
||||
// the unit is pinned here rather than taken from the task's own unit.
|
||||
? `${t("reanalyze_result", this._lang) || "Recomputed"}: ${formatInterval(r.recommended_interval, "days", this._lang)} (${r.data_points} pts)`
|
||||
: (t("reanalyze_insufficient_data", this._lang) || "Not enough data");
|
||||
? `${t("reanalyze_result", this._lang)}: ${formatInterval(r.recommended_interval, "days", this._lang)} (${r.data_points} pts)`
|
||||
: t("reanalyze_insufficient_data", this._lang);
|
||||
await this._loadTask();
|
||||
setTimeout(() => { this._toast = ""; }, 3500);
|
||||
} catch (e) {
|
||||
@@ -349,26 +354,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
|
||||
private _onEditHistoryEntry(entry: HistoryEntry): void {
|
||||
if (!this._entryId || !this._taskId) return;
|
||||
import("../dialog-mount").then(({ openHistoryEditDialog }) => {
|
||||
openHistoryEditDialog({
|
||||
entry_id: this._entryId!,
|
||||
task_id: this._taskId!,
|
||||
original_timestamp: entry.timestamp,
|
||||
type: entry.type,
|
||||
timestamp: entry.timestamp,
|
||||
notes: entry.notes ?? null,
|
||||
cost: entry.cost ?? null,
|
||||
duration: entry.duration ?? null,
|
||||
completed_by: entry.completed_by ?? null,
|
||||
used_parts: entry.used_parts ?? null,
|
||||
photo_doc_ids: historyPhotoIds(entry),
|
||||
reading_value: entry.reading_value ?? null,
|
||||
reading_values: entryReadingValues(entry),
|
||||
readings: this._task?.readings ?? [],
|
||||
task_type: this._task?.type ?? null,
|
||||
reading_unit: this._task?.reading_unit ?? null,
|
||||
});
|
||||
});
|
||||
const draft = buildHistoryEntryDraft(this._entryId, this._taskId, entry, this._task);
|
||||
import("../dialog-mount").then(({ openHistoryEditDialog }) => openHistoryEditDialog(draft));
|
||||
}
|
||||
|
||||
/** Inline recommendation card (Current vs Suggested with apply/reanalyze).
|
||||
@@ -426,7 +413,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
|
||||
if (!hasRecommendation && !hasPrediction && !hasWeibull && !hasSeasonal) {
|
||||
return html`<div class="adaptive-empty">
|
||||
${t("adaptive_no_data", L) || "Not enough completion history yet for adaptive analysis."}
|
||||
${t("adaptive_no_data", L)}
|
||||
</div>`;
|
||||
}
|
||||
return html`
|
||||
@@ -448,30 +435,6 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
/** #161: the entry's readings — per-slot lines with the delta against the
|
||||
* previous entry carrying the slot, or the single value of a scalar task.
|
||||
* Parity with the panel timeline for the Lovelace / strategy path. */
|
||||
private _renderHistoryReadings(entry: HistoryEntry, history: HistoryEntry[], L: string) {
|
||||
const values = entryReadingValues(entry);
|
||||
const fmt = (n: number) => formatNumber(n, L, { maximumFractionDigits: 3 });
|
||||
if (values.length > 0) {
|
||||
return html`<div class="history-readings">
|
||||
${values.map((v) => {
|
||||
const d = readingSlotDelta(history, entry, v.id);
|
||||
return html`<span class="history-reading"><span class="history-reading-name">${v.name}</span>
|
||||
<span class="history-reading-value">${fmt(v.value)}${v.unit ? ` ${v.unit}` : ""}${d == null ? "" : ` (${d >= 0 ? "+" : ""}${fmt(d)})`}</span></span>`;
|
||||
})}
|
||||
</div>`;
|
||||
}
|
||||
if (entry.reading_value != null) {
|
||||
const unit = this._task?.reading_unit ? ` ${this._task.reading_unit}` : "";
|
||||
return html`<div class="history-readings"><span class="history-reading">
|
||||
<span class="history-reading-name">${t("reading_label", L)}</span>
|
||||
<span class="history-reading-value">${fmt(entry.reading_value)}${unit}</span></span></div>`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
/** Read-only details panel: stats + history. Shown when the user clicks
|
||||
* "Show details" in the dialog. Edit-buttons on history entries open the
|
||||
* existing history-edit dialog (which lives in the same dialog-mount). */
|
||||
@@ -495,64 +458,37 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<div class="details">
|
||||
<div class="stats-grid">
|
||||
<div class="stat">
|
||||
<span class="stat-label">${t("times_performed", L) || "Performed"}</span>
|
||||
<span class="stat-label">${t("times_performed", L)}</span>
|
||||
<span class="stat-value">${completed.length}</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">${t("total_cost", L) || "Total cost"}</span>
|
||||
<span class="stat-value">${formatNumber(totalCost, L, 2)}</span>
|
||||
<span class="stat-label">${t("total_cost", L)}</span>
|
||||
<span class="stat-value">${formatCost(totalCost, this._currencySymbol, L)}</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">${t("avg_duration", L) || "Avg duration"}</span>
|
||||
<span class="stat-value">${avgDuration != null ? `${avgDuration}m` : "—"}</span>
|
||||
<span class="stat-label">${t("avg_duration", L)}</span>
|
||||
<span class="stat-value">${formatDuration(avgDuration, L)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="history-header">
|
||||
<strong>${t("history", L) || "History"}</strong>
|
||||
<strong>${t("history", L)}</strong>
|
||||
<span class="history-count">${history.length}</span>
|
||||
</div>
|
||||
${history.length === 0
|
||||
? html`<div class="history-empty">${t("history_empty", L) || "No history yet."}</div>`
|
||||
? html`<div class="history-empty">${t("history_empty", L)}</div>`
|
||||
: html`
|
||||
<div class="history-list">
|
||||
${[...history].reverse().slice(0, 20).map((entry) => {
|
||||
const editable = ["completed", "reset", "skipped"].includes(entry.type);
|
||||
return html`
|
||||
<div class="history-entry">
|
||||
<div class="history-line">
|
||||
<span class="history-type type-${entry.type}">${t(entry.type, L)}</span>
|
||||
<span class="history-date">${formatDateTime(entry.timestamp, L)}</span>
|
||||
${editable
|
||||
? html`<button class="history-edit"
|
||||
title="${t("history_edit_button", L) || "Edit"}"
|
||||
@click=${() => this._onEditHistoryEntry(entry)}>
|
||||
<ha-icon icon="mdi:pencil"></ha-icon>
|
||||
</button>`
|
||||
: nothing}
|
||||
</div>
|
||||
${entry.notes
|
||||
? html`<div class="history-notes">${entry.notes}</div>`
|
||||
: nothing}
|
||||
${this._renderHistoryReadings(entry, history, L)}
|
||||
${(() => {
|
||||
const photos = historyPhotoIds(entry);
|
||||
return photos.length
|
||||
? html`<div class="history-photos">
|
||||
${photos.map((docId) => html`<maintenance-history-photo .hass=${this.hass} .docId=${docId}></maintenance-history-photo>`)}
|
||||
</div>`
|
||||
: nothing;
|
||||
})()}
|
||||
${entry.cost != null || entry.duration != null
|
||||
? html`<div class="history-meta">
|
||||
${entry.cost != null ? html`<span>💰 ${formatNumber(entry.cost, L, 2)}</span>` : nothing}
|
||||
${entry.duration != null ? html`<span>⏱️ ${entry.duration}m</span>` : nothing}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
${[...history].reverse().slice(0, 20).map((entry) => renderHistoryEntry(entry, {
|
||||
lang: L,
|
||||
hass: this.hass,
|
||||
currencySymbol: this._currencySymbol,
|
||||
openEdit: (e) => this._onEditHistoryEntry(e),
|
||||
readingUnit: task.reading_unit,
|
||||
readingSlotDelta: (e, slotId) => readingSlotDelta(history, e, slotId),
|
||||
taskRef: this._taskRef,
|
||||
}, { compact: true }))}
|
||||
${history.length > 20
|
||||
? html`<div class="history-more">… +${history.length - 20} ${t("older_entries", L) || "older"}</div>`
|
||||
? html`<div class="history-more">… +${history.length - 20} ${t("older_entries", L)}</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`}
|
||||
@@ -587,13 +523,13 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
</div>
|
||||
<div class="quick-info">
|
||||
${task.next_due
|
||||
? html`<span><strong>${t("next_due", L) || "Next due"}:</strong> ${formatDate(task.next_due, L)}</span>`
|
||||
? html`<span><strong>${t("next_due", L)}:</strong> ${formatDate(task.next_due, L)}</span>`
|
||||
: nothing}
|
||||
${task.last_performed
|
||||
? html`<span><strong>${t("last_performed", L) || "Last"}:</strong> ${formatDate(task.last_performed, L)}</span>`
|
||||
? html`<span><strong>${t("last_performed", L)}:</strong> ${formatDate(task.last_performed, L)}</span>`
|
||||
: nothing}
|
||||
${(task.schedule?.kind && !["manual", "one_time"].includes(task.schedule.kind)) || task.interval_days != null
|
||||
? html`<span><strong>${t("interval", L) || "Interval"}:</strong> ${formatRecurrence(task, L)}</span>`
|
||||
? html`<span><strong>${t("interval", L)}:</strong> ${formatRecurrence(task, L)}</span>`
|
||||
: nothing}
|
||||
${phaseLabel(task)
|
||||
? html`<span><strong>${t("phase_current", L)}:</strong> ${phaseLabel(task)}</span>`
|
||||
@@ -608,15 +544,15 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
${this._showSkip
|
||||
? html`
|
||||
<div class="inline-form">
|
||||
<label>${t("skip_reason", L) || "Skip reason (optional)"}</label>
|
||||
<label>${t("skip_reason", L)}</label>
|
||||
<input type="text" .value=${this._skipReason}
|
||||
@input=${(e: Event) => { this._skipReason = (e.target as HTMLInputElement).value; }} />
|
||||
<div class="inline-actions">
|
||||
<button class="btn cancel" @click=${() => { this._showSkip = false; }} ?disabled=${this._busy}>
|
||||
${t("cancel", L) || "Cancel"}
|
||||
${t("cancel", L)}
|
||||
</button>
|
||||
<button class="btn primary" @click=${this._onSkipConfirm} ?disabled=${this._busy}>
|
||||
${t("skip", L) || "Skip"}
|
||||
${t("skip", L)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -624,7 +560,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
: this._showReset
|
||||
? html`
|
||||
<div class="inline-form">
|
||||
<label>${t("reset_to_date", L) || "Reset last_performed to"}</label>
|
||||
<label>${t("reset_to_date", L)}</label>
|
||||
<ms-date-field
|
||||
kind="date"
|
||||
.hass=${this.hass}
|
||||
@@ -634,10 +570,10 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
></ms-date-field>
|
||||
<div class="inline-actions">
|
||||
<button class="btn cancel" @click=${() => { this._showReset = false; }} ?disabled=${this._busy}>
|
||||
${t("cancel", L) || "Cancel"}
|
||||
${t("cancel", L)}
|
||||
</button>
|
||||
<button class="btn primary" @click=${this._onResetConfirm} ?disabled=${this._busy}>
|
||||
${t("reset", L) || "Reset"}
|
||||
${t("reset", L)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -646,19 +582,19 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<div class="actions primary-row">
|
||||
<ha-button appearance="accent" variant="success" @click=${this._onComplete} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:check"></ha-icon>
|
||||
${t("complete", L) || "Complete"}
|
||||
${t("complete", L)}
|
||||
</ha-button>
|
||||
${task.allow_skip !== false
|
||||
? html`
|
||||
<ha-button appearance="outlined" variant="warning" @click=${() => { this._showSkip = true; }} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:skip-next"></ha-icon>
|
||||
${t("skip", L) || "Skip"}
|
||||
${t("skip", L)}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-button appearance="outlined" variant="neutral" @click=${() => { this._showReset = true; }} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:restart"></ha-icon>
|
||||
${t("reset", L) || "Reset"}
|
||||
${t("reset", L)}
|
||||
</ha-button>
|
||||
</div>
|
||||
${isAdmin
|
||||
@@ -666,21 +602,21 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<div class="actions secondary-row">
|
||||
<ha-button size="small" appearance="outlined" variant="neutral" @click=${this._onEdit} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:pencil"></ha-icon>
|
||||
${t("edit", L) || "Edit"}
|
||||
${t("edit", L)}
|
||||
</ha-button>
|
||||
<ha-button size="small" appearance="outlined" variant="neutral" @click=${this._onQr} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:qrcode"></ha-icon>
|
||||
${t("qr_code", L) || "QR"}
|
||||
${t("qr_code", L)}
|
||||
</ha-button>
|
||||
<ha-button size="small" appearance="outlined" variant="neutral"
|
||||
@click=${task.archived ? this._onUnarchive : this._onArchive}
|
||||
.disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="${task.archived ? 'mdi:archive-arrow-up-outline' : 'mdi:archive-outline'}"></ha-icon>
|
||||
${task.archived ? (t("unarchive", L) || "Unarchive") : (t("archive", L) || "Archive")}
|
||||
${task.archived ? t("unarchive", L) : t("archive", L)}
|
||||
</ha-button>
|
||||
<ha-button size="small" appearance="outlined" variant="danger" class="danger" @click=${this._onDelete} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:delete"></ha-icon>
|
||||
${t("delete", L) || "Delete"}
|
||||
${t("delete", L)}
|
||||
</ha-button>
|
||||
</div>
|
||||
`
|
||||
@@ -689,8 +625,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<button class="link" @click=${() => { this._showDetails = !this._showDetails; }}>
|
||||
<ha-icon icon="${this._showDetails ? 'mdi:chevron-up' : 'mdi:chevron-down'}"></ha-icon>
|
||||
${this._showDetails
|
||||
? (t("hide_details", L) || "Hide details")
|
||||
: (t("show_details", L) || "Show history + stats")}
|
||||
? t("hide_details", L)
|
||||
: t("show_details", L)}
|
||||
</button>
|
||||
${this._features.adaptive
|
||||
|| this._features.seasonal
|
||||
@@ -698,8 +634,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
? html`<button class="link" @click=${() => { this._showAdaptive = !this._showAdaptive; }}>
|
||||
<ha-icon icon="${this._showAdaptive ? 'mdi:chart-line' : 'mdi:chart-line-variant'}"></ha-icon>
|
||||
${this._showAdaptive
|
||||
? (t("hide_stats", L) || "Hide stats")
|
||||
: (t("show_stats", L) || "Show stats + graphs")}
|
||||
? t("hide_stats", L)
|
||||
: t("show_stats", L)}
|
||||
</button>`
|
||||
: nothing}
|
||||
</div>
|
||||
@@ -708,12 +644,12 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<div class="footer">
|
||||
<button class="link" @click=${this._onOpenInPanel}>
|
||||
<ha-icon icon="mdi:open-in-new"></ha-icon>
|
||||
${t("open_in_panel", L) || "Open in Maintenance panel"}
|
||||
${t("open_in_panel", L)}
|
||||
</button>
|
||||
</div>
|
||||
`}
|
||||
`
|
||||
: html`<div class="loading">${t("loading", L) || "Loading…"}</div>`}
|
||||
: html`<div class="loading">${t("loading", L)}</div>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -843,41 +779,16 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px;
|
||||
}
|
||||
.history-empty { color: var(--secondary-text-color); font-style: italic; font-size: 13px; }
|
||||
.history-list { display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow: auto; }
|
||||
.history-entry {
|
||||
padding: 6px 8px; border-radius: 6px;
|
||||
/* The rows themselves are the shared renderer's (.history-entry.compact
|
||||
+ the history-* classes from sharedStyles); only the list chrome is
|
||||
local. */
|
||||
.history-list { display: flex; flex-direction: column; max-height: 280px; overflow: auto; }
|
||||
.history-list .history-entry {
|
||||
padding: 6px 8px; border-radius: 6px; border-bottom: none; margin-bottom: 6px;
|
||||
background: var(--secondary-background-color, rgba(255,255,255,0.03));
|
||||
font-size: 13px;
|
||||
}
|
||||
.history-line {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.history-type {
|
||||
font-weight: 600; font-size: 11px;
|
||||
padding: 2px 6px; border-radius: 4px;
|
||||
text-transform: uppercase; letter-spacing: 0.5px;
|
||||
}
|
||||
.type-completed { background: rgba(46,125,50,0.2); color: #66bb6a; }
|
||||
.type-skipped { background: rgba(158,158,158,0.2); color: var(--secondary-text-color); }
|
||||
.type-reset { background: rgba(33,150,243,0.2); color: #64b5f6; }
|
||||
.type-triggered { background: rgba(255,87,34,0.2); color: #ff8a65; }
|
||||
.history-date { font-size: 11px; color: var(--secondary-text-color); flex: 1; text-align: right; }
|
||||
.history-edit {
|
||||
background: transparent; border: none; cursor: pointer;
|
||||
padding: 4px; border-radius: 4px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.history-edit:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); color: var(--primary-color); }
|
||||
.history-edit ha-icon { --mdc-icon-size: 14px; }
|
||||
.history-notes { margin-top: 4px; color: var(--primary-text-color); }
|
||||
.history-meta { display: flex; gap: 12px; margin-top: 4px; color: var(--secondary-text-color); font-size: 11px; }
|
||||
/* #161: readings + photos on the entry (panel-timeline parity) */
|
||||
.history-readings { display: flex; flex-wrap: wrap; gap: 2px 16px; margin-top: 4px; font-size: 12px; }
|
||||
.history-reading { display: inline-flex; gap: 6px; }
|
||||
.history-reading-name { color: var(--secondary-text-color); }
|
||||
.history-reading-value { font-variant-numeric: tabular-nums; }
|
||||
.history-photos { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
|
||||
.history-list .history-date { font-size: 11px; }
|
||||
.history-list .history-details { font-size: 11px; }
|
||||
.history-more { padding: 8px; text-align: center; font-size: 12px; color: var(--secondary-text-color); font-style: italic; }
|
||||
|
||||
/* Adaptive section — wraps the panel renderers (which assume sharedStyles
|
||||
|
||||
Reference in New Issue
Block a user