8 Commits
Author SHA1 Message Date
brian 120c7ec309 Merge pull request 'Make recent activity cards dynamic' (#45) from make-recent-activity-last-sleep-dynamic into main
Python Code Quality / python-code-quality (push) Successful in 12s
Python Test / python-test (push) Successful in 21s
Build and Push Docker Image / build-and-push (push) Successful in 39s
Reviewed-on: https://gitea.lille-vemmelund.dk/brian/baby-monitor/pulls/45
2026-05-21 15:29:16 +02:00
Brian Bjarke Jensen 6c74358d8b changed formatting of recent activity cards
Build and Push Docker Image / build-and-push (pull_request) Successful in 3m23s
Python Code Quality / python-code-quality (pull_request) Successful in 13s
Python Test / python-test (pull_request) Successful in 20s
2026-05-21 15:20:42 +02:00
brian 993612a90f Merge pull request 'added support for setting start time of sleep entry' (#44) from fix(#43)--enable-editing-sleep-entries into main
Python Code Quality / python-code-quality (push) Successful in 12s
Python Test / python-test (push) Successful in 20s
Build and Push Docker Image / build-and-push (push) Successful in 41s
Refresh Showcase Data / refresh-data (push) Successful in 22s
Reviewed-on: #44
2026-03-29 20:29:28 +02:00
Brian Bjarke Jensen 0b06aefa03 added support for setting start time of sleep entry
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m25s
Python Code Quality / python-code-quality (pull_request) Successful in 12s
Python Test / python-test (pull_request) Successful in 21s
2026-03-29 20:22:26 +02:00
brian ff88d0fcb6 Merge pull request 'implement-ready-endpoint-logic' (#42) from implement-ready-endpoint-logic into main
Build and Push Docker Image / build-and-push (push) Successful in 27s
Python Code Quality / python-code-quality (push) Successful in 10s
Python Test / python-test (push) Successful in 19s
Refresh Showcase Data / refresh-data (push) Successful in 47s
Reviewed-on: #42
2026-01-27 21:26:06 +01:00
Brian Bjarke Jensen ec0e1f6140 code quality changes
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m3s
Python Code Quality / python-code-quality (pull_request) Successful in 10s
Python Test / python-test (pull_request) Successful in 17s
2026-01-27 21:21:55 +01:00
Brian Bjarke Jensen b2aa71b95b updated ready endpoints logic
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m6s
Python Code Quality / python-code-quality (pull_request) Failing after 10s
Python Test / python-test (pull_request) Successful in 18s
2026-01-27 21:17:47 +01:00
Brian Bjarke Jensen 826cfde2ab recreated lock file 2026-01-27 21:17:34 +01:00
16 changed files with 1186 additions and 983 deletions
+1
View File
@@ -15,6 +15,7 @@ class CreateSleepRequest(BaseModel):
class UpdateSleepRequest(BaseModel): class UpdateSleepRequest(BaseModel):
"""Request model for updating a sleep log.""" """Request model for updating a sleep log."""
start_time: datetime | None = None
end_time: datetime | None = None end_time: datetime | None = None
@@ -32,6 +32,7 @@ class SleepRepositoryInterface(ABC):
def update( def update(
self, self,
sleep_id: int, sleep_id: int,
start_time: datetime | None = None,
end_time: datetime | None = None, end_time: datetime | None = None,
) -> dict | None: ) -> dict | None:
"""Update a sleep log entry.""" """Update a sleep log entry."""
@@ -73,6 +73,7 @@ class DatabaseSleepRepository(SleepRepositoryInterface):
def update( def update(
self, self,
sleep_id: int, sleep_id: int,
start_time: datetime | None = None,
end_time: datetime | None = None, end_time: datetime | None = None,
) -> dict | None: ) -> dict | None:
"""Update a sleep log entry.""" """Update a sleep log entry."""
@@ -80,6 +81,8 @@ class DatabaseSleepRepository(SleepRepositoryInterface):
if not sleep: if not sleep:
return None return None
if start_time is not None:
sleep.start_time = start_time # type: ignore[assignment]
if end_time is not None: if end_time is not None:
sleep.end_time = end_time # type: ignore[assignment] sleep.end_time = end_time # type: ignore[assignment]
+50 -7
View File
@@ -1,6 +1,7 @@
"""Health check router for monitoring endpoints.""" """Health check router for monitoring endpoints."""
from fastapi import APIRouter import os
from fastapi import APIRouter, HTTPException
router = APIRouter(tags=["health"]) router = APIRouter(tags=["health"])
@@ -20,11 +21,53 @@ def readiness_check() -> dict[str, str]:
""" """
Readiness check endpoint for container orchestration. Readiness check endpoint for container orchestration.
Returns 200 if the service is ready to accept traffic. Returns 200 if the service is ready to accept traffic, else 503.
This can include checks for database connectivity, external services, etc.
""" """
# Add dependency checks here if needed (database, cache, etc.)
# For now, simple readiness check # Determine if configured to use Redis
redis_uri = os.getenv("REDIS_URI")
# If Redis is used, check connectivity
if redis_uri:
try:
import redis
redis_client = redis.from_url(redis_uri)
redis_client.ping()
except Exception as exc:
raise HTTPException(
status_code=503,
detail={
"status": "not ready",
"reason": "No connection to Redis",
"exception": str(exc),
},
) from exc
# Determine if configured to use PostgreSQL
postgres_uri = os.getenv("POSTGRES_URI")
# If PostgreSQL is used, check connectivity
if postgres_uri:
try:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy import text
engine = create_engine(postgres_uri)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
with SessionLocal() as db:
db.execute(text("SELECT 1"))
except Exception as exc:
raise HTTPException(
status_code=503,
detail={
"status": "not ready",
"reason": "No connection to PostgreSQL",
"exception": str(exc),
},
) from exc
# Unable to prove not ready, so assume ready
return {"status": "ready"} return {"status": "ready"}
@@ -60,9 +103,9 @@ def service_info() -> dict:
# Check current configuration # Check current configuration
config = { config = {
"environment": os.getenv("ENVIRONMENT", "production"), "environment": os.getenv("ENVIRONMENT", "production"),
"data_dir": os.getenv("DATA_DIR", "/data"),
"redis_configured": bool(os.getenv("REDIS_URI")),
} }
if not features["postgresql"]:
config["data_dir"] = os.getenv("DATA_DIR", "/data")
return { return {
"service": "baby-monitor", "service": "baby-monitor",
+1
View File
@@ -112,6 +112,7 @@ def update_sleep(
updated = sleep_repo.update( updated = sleep_repo.update(
sleep_id=sleep_id, sleep_id=sleep_id,
start_time=request.start_time,
end_time=request.end_time, end_time=request.end_time,
) )
+193 -189
View File
@@ -3,201 +3,205 @@
* Usage: Include this before your chart initialization code * Usage: Include this before your chart initialization code
*/ */
(function() { (function () {
'use strict'; "use strict";
// Helper to get effective theme // Helper to get effective theme
function getEffectiveTheme() { function getEffectiveTheme() {
return window.getEffectiveTheme ? window.getEffectiveTheme() : 'light'; return window.getEffectiveTheme ? window.getEffectiveTheme() : "light";
} }
// Get theme-appropriate colors for Chart.js // Get theme-appropriate colors for Chart.js
window.getChartColors = function() { window.getChartColors = function () {
const isDark = getEffectiveTheme() === 'dark'; const isDark = getEffectiveTheme() === "dark";
return { return {
// Grid lines // Grid lines
grid: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)', grid: isDark ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.05)",
gridMajor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)', gridMajor: isDark ? "rgba(255, 255, 255, 0.15)" : "rgba(0, 0, 0, 0.1)",
// Text colors // Text colors
text: isDark ? '#b0b0b0' : '#666666', text: isDark ? "#b0b0b0" : "#666666",
textPrimary: isDark ? '#e0e0e0' : '#333333', textPrimary: isDark ? "#e0e0e0" : "#333333",
// Data series colors (optimized for background) // Data series colors (optimized for background)
series: isDark ? [ series: isDark
'rgba(139, 158, 255, 0.8)', // Brighter purple ? [
'rgba(110, 197, 255, 0.8)', // Brighter blue "rgba(139, 158, 255, 0.8)", // Brighter purple
'rgba(255, 179, 255, 0.8)', // Brighter pink "rgba(110, 197, 255, 0.8)", // Brighter blue
'rgba(129, 199, 132, 0.8)', // Brighter green "rgba(255, 179, 255, 0.8)", // Brighter pink
'rgba(255, 183, 77, 0.8)', // Brighter orange "rgba(129, 199, 132, 0.8)", // Brighter green
'rgba(156, 107, 184, 0.8)', // Brighter deep purple "rgba(255, 183, 77, 0.8)", // Brighter orange
'rgba(77, 255, 255, 0.8)', // Brighter cyan "rgba(156, 107, 184, 0.8)", // Brighter deep purple
'rgba(255, 138, 155, 0.8)', // Brighter red "rgba(77, 255, 255, 0.8)", // Brighter cyan
] : [ "rgba(255, 138, 155, 0.8)", // Brighter red
'rgba(102, 126, 234, 0.8)', // Purple ]
'rgba(118, 75, 162, 0.8)', // Deep purple : [
'rgba(255, 159, 64, 0.8)', // Orange "rgba(102, 126, 234, 0.8)", // Purple
'rgba(76, 175, 80, 0.8)', // Green "rgba(118, 75, 162, 0.8)", // Deep purple
'rgba(244, 67, 54, 0.8)', // Red "rgba(255, 159, 64, 0.8)", // Orange
'rgba(156, 39, 176, 0.8)', // Violet "rgba(76, 175, 80, 0.8)", // Green
'rgba(102, 126, 234, 0.6)', // Light purple "rgba(244, 67, 54, 0.8)", // Red
'rgba(118, 75, 162, 0.6)', // Light deep purple "rgba(156, 39, 176, 0.8)", // Violet
], "rgba(102, 126, 234, 0.6)", // Light purple
"rgba(118, 75, 162, 0.6)", // Light deep purple
// Solid data colors (for pie charts, etc.) ],
solidSeries: isDark ? [
'#8b9eff', // Solid data colors (for pie charts, etc.)
'#6ec5ff', solidSeries: isDark
'#ffb3ff', ? [
'#81c784', "#8b9eff",
'#ffb74d', "#6ec5ff",
'#9c6bb8', "#ffb3ff",
'#4dffff', "#81c784",
'#ff8a9b', "#ffb74d",
] : [ "#9c6bb8",
'#667eea', "#4dffff",
'#764ba2', "#ff8a9b",
'#ff9f40', ]
'#4caf50', : [
'#f44336', "#667eea",
'#9c27b0', "#764ba2",
'#667eea', "#ff9f40",
'#764ba2', "#4caf50",
], "#f44336",
"#9c27b0",
// Background colors "#667eea",
background: isDark ? '#2d2d2d' : '#f8f9fa', "#764ba2",
}; ],
// Background colors
background: isDark ? "#2d2d2d" : "#f8f9fa",
}; };
};
// Get default Chart.js options with theme support
window.getChartOptions = function(customOptions = {}) { // Get default Chart.js options with theme support
const colors = window.getChartColors(); window.getChartOptions = function (customOptions = {}) {
const colors = window.getChartColors();
const defaults = {
responsive: true, const defaults = {
maintainAspectRatio: false, responsive: true,
plugins: { maintainAspectRatio: false,
legend: { plugins: {
labels: { legend: {
color: colors.text, labels: {
font: { color: colors.text,
size: 12 font: {
} size: 12,
}
},
tooltip: {
backgroundColor: getEffectiveTheme() === 'dark' ? '#3a3a3a' : '#ffffff',
titleColor: colors.textPrimary,
bodyColor: colors.text,
borderColor: colors.grid,
borderWidth: 1,
}
}, },
scales: {} },
}; },
tooltip: {
// Deep merge customOptions with defaults backgroundColor:
return mergeDeep(defaults, customOptions); getEffectiveTheme() === "dark" ? "#3a3a3a" : "#ffffff",
titleColor: colors.textPrimary,
bodyColor: colors.text,
borderColor: colors.grid,
borderWidth: 1,
},
},
scales: {},
}; };
// Get theme-appropriate scale options // Deep merge customOptions with defaults
window.getChartScaleOptions = function(axis = 'x', customOptions = {}) { return mergeDeep(defaults, customOptions);
const colors = window.getChartColors(); };
const defaults = { // Get theme-appropriate scale options
grid: { window.getChartScaleOptions = function (axis = "x", customOptions = {}) {
color: colors.grid, const colors = window.getChartColors();
borderColor: colors.gridMajor,
}, const defaults = {
ticks: { grid: {
color: colors.text, color: colors.grid,
font: { borderColor: colors.gridMajor,
size: 12 },
} ticks: {
}, color: colors.text,
title: { font: {
color: colors.textPrimary, size: 12,
font: { },
size: 13, },
weight: '600' title: {
} color: colors.textPrimary,
} font: {
}; size: 13,
weight: "600",
return mergeDeep(defaults, customOptions); },
},
}; };
// Helper function to deep merge objects return mergeDeep(defaults, customOptions);
function mergeDeep(target, source) { };
const output = Object.assign({}, target);
if (isObject(target) && isObject(source)) { // Helper function to deep merge objects
Object.keys(source).forEach(key => { function mergeDeep(target, source) {
if (isObject(source[key])) { const output = Object.assign({}, target);
if (!(key in target)) { if (isObject(target) && isObject(source)) {
Object.assign(output, { [key]: source[key] }); Object.keys(source).forEach((key) => {
} else { if (isObject(source[key])) {
output[key] = mergeDeep(target[key], source[key]); if (!(key in target)) {
} Object.assign(output, { [key]: source[key] });
} else { } else {
Object.assign(output, { [key]: source[key] }); output[key] = mergeDeep(target[key], source[key]);
} }
}); } else {
Object.assign(output, { [key]: source[key] });
} }
return output; });
} }
return output;
function isObject(item) { }
return item && typeof item === 'object' && !Array.isArray(item);
function isObject(item) {
return item && typeof item === "object" && !Array.isArray(item);
}
// Function to update all charts on theme change
window.updateChartsForTheme = function (chartInstances) {
if (!Array.isArray(chartInstances)) {
chartInstances = [chartInstances];
} }
// Function to update all charts on theme change const colors = window.getChartColors();
window.updateChartsForTheme = function(chartInstances) {
if (!Array.isArray(chartInstances)) { chartInstances.forEach((chart) => {
chartInstances = [chartInstances]; if (!chart) return;
}
// Update grid colors
const colors = window.getChartColors(); if (chart.options.scales) {
Object.keys(chart.options.scales).forEach((axis) => {
chartInstances.forEach(chart => { if (chart.options.scales[axis].grid) {
if (!chart) return; chart.options.scales[axis].grid.color = colors.grid;
chart.options.scales[axis].grid.borderColor = colors.gridMajor;
// Update grid colors }
if (chart.options.scales) { if (chart.options.scales[axis].ticks) {
Object.keys(chart.options.scales).forEach(axis => { chart.options.scales[axis].ticks.color = colors.text;
if (chart.options.scales[axis].grid) { }
chart.options.scales[axis].grid.color = colors.grid; if (chart.options.scales[axis].title) {
chart.options.scales[axis].grid.borderColor = colors.gridMajor; chart.options.scales[axis].title.color = colors.textPrimary;
} }
if (chart.options.scales[axis].ticks) {
chart.options.scales[axis].ticks.color = colors.text;
}
if (chart.options.scales[axis].title) {
chart.options.scales[axis].title.color = colors.textPrimary;
}
});
}
// Update legend colors
if (chart.options.plugins && chart.options.plugins.legend) {
if (chart.options.plugins.legend.labels) {
chart.options.plugins.legend.labels.color = colors.text;
}
}
// Update tooltip colors
if (chart.options.plugins && chart.options.plugins.tooltip) {
chart.options.plugins.tooltip.backgroundColor =
getEffectiveTheme() === 'dark' ? '#3a3a3a' : '#ffffff';
chart.options.plugins.tooltip.titleColor = colors.textPrimary;
chart.options.plugins.tooltip.bodyColor = colors.text;
chart.options.plugins.tooltip.borderColor = colors.grid;
}
// Update the chart
chart.update();
}); });
}; }
// Update legend colors
if (chart.options.plugins && chart.options.plugins.legend) {
if (chart.options.plugins.legend.labels) {
chart.options.plugins.legend.labels.color = colors.text;
}
}
// Update tooltip colors
if (chart.options.plugins && chart.options.plugins.tooltip) {
chart.options.plugins.tooltip.backgroundColor =
getEffectiveTheme() === "dark" ? "#3a3a3a" : "#ffffff";
chart.options.plugins.tooltip.titleColor = colors.textPrimary;
chart.options.plugins.tooltip.bodyColor = colors.text;
chart.options.plugins.tooltip.borderColor = colors.grid;
}
// Update the chart
chart.update();
});
};
})(); })();
+188 -188
View File
@@ -3,198 +3,130 @@
*/ */
:root { :root {
/* ===== LIGHT MODE (Default) ===== */ /* ===== LIGHT MODE (Default) ===== */
/* Background colors */ /* Background colors */
--bg-primary: #f0f0f0; --bg-primary: #f0f0f0;
--bg-secondary: #ffffff; --bg-secondary: #ffffff;
--bg-tertiary: #f8f9fa; --bg-tertiary: #f8f9fa;
--bg-card: #ffffff; --bg-card: #ffffff;
/* Text colors */ /* Text colors */
--text-primary: #333333; --text-primary: #333333;
--text-secondary: #666666; --text-secondary: #666666;
--text-muted: #999999; --text-muted: #999999;
--text-inverse: #ffffff; --text-inverse: #ffffff;
/* Border colors */ /* Border colors */
--border-color: #dddddd; --border-color: #dddddd;
--border-light: #e0e0e0; --border-light: #e0e0e0;
--border-lighter: #eeeeee; --border-lighter: #eeeeee;
/* Gradients - Brand */ /* Gradients - Brand */
--gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--gradient-blue: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); --gradient-blue: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--gradient-pink: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); --gradient-pink: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--gradient-green: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%); --gradient-green: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-orange: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%); --gradient-orange: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%);
/* Action colors */ /* Action colors */
--color-success: #4caf50; --color-success: #4caf50;
--color-error: #dc3545; --color-error: #dc3545;
--color-warning: #ff9800; --color-warning: #ff9800;
--color-info: #2196f3; --color-info: #2196f3;
--color-danger: #f44336; --color-danger: #f44336;
/* Action gradients */ /* Action gradients */
--gradient-success: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%); --gradient-success: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-error: linear-gradient(135deg, #dc3545 0%, #c62828 100%); --gradient-error: linear-gradient(135deg, #dc3545 0%, #c62828 100%);
--gradient-danger: linear-gradient(135deg, #f44336 0%, #d32f2f 100%); --gradient-danger: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
/* Interactive elements */ /* Interactive elements */
--input-bg: #ffffff; --input-bg: #ffffff;
--input-border: #dddddd; --input-border: #dddddd;
--input-focus-border: #667eea; --input-focus-border: #667eea;
--input-text: #333333; --input-text: #333333;
--input-placeholder: #999999; --input-placeholder: #999999;
/* Shadows */ /* Shadows */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1); --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.1); --shadow-md: 0 2px 10px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.15); --shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.15);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.2); --shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.2);
/* Modal/Overlay */ /* Modal/Overlay */
--overlay-bg: rgba(0, 0, 0, 0.5); --overlay-bg: rgba(0, 0, 0, 0.5);
/* Chart colors (vibrant for light backgrounds) */ /* Chart colors (vibrant for light backgrounds) */
--chart-grid: rgba(0, 0, 0, 0.1); --chart-grid: rgba(0, 0, 0, 0.1);
--chart-text: #666666; --chart-text: #666666;
--chart-color-1: #667eea; --chart-color-1: #667eea;
--chart-color-2: #4facfe; --chart-color-2: #4facfe;
--chart-color-3: #f093fb; --chart-color-3: #f093fb;
--chart-color-4: #56ab2f; --chart-color-4: #56ab2f;
--chart-color-5: #f2994a; --chart-color-5: #f2994a;
--chart-color-6: #764ba2; --chart-color-6: #764ba2;
--chart-color-7: #00f2fe; --chart-color-7: #00f2fe;
--chart-color-8: #f5576c; --chart-color-8: #f5576c;
} }
/* ===== DARK MODE ===== */ /* ===== DARK MODE ===== */
/* Applied via @media query (auto-detect) OR .dark-mode class (manual override) */ /* Applied via @media query (auto-detect) OR .dark-mode class (manual override) */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root:not(.light-mode) { :root:not(.light-mode) {
/* Background colors - Dark theme */
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a;
--bg-card: #2d2d2d;
/* Text colors - Light on dark */
--text-primary: #e0e0e0;
--text-secondary: #b0b0b0;
--text-muted: #808080;
--text-inverse: #1a1a1a;
/* Border colors - Lighter for visibility */
--border-color: #444444;
--border-light: #4a4a4a;
--border-lighter: #505050;
/* Gradients - Dimmed (~50% brightness reduction) */
--gradient-purple: linear-gradient(135deg, #3d4d8c 0%, #452d61 100%);
--gradient-blue: linear-gradient(135deg, #2f667f 0%, #00919a 100%);
--gradient-pink: linear-gradient(135deg, #905997 0%, #924344 100%);
--gradient-green: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-orange: linear-gradient(135deg, #915b2d 0%, #917a2e 100%);
/* Action colors - Slightly brighter for visibility */
--color-success: #66bb6a;
--color-error: #ef5350;
--color-warning: #ffa726;
--color-info: #42a5f5;
--color-danger: #ff5252;
/* Action gradients - Dimmed */
--gradient-success: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-error: linear-gradient(135deg, #a32a2a 0%, #8b2020 100%);
--gradient-danger: linear-gradient(135deg, #a32929 0%, #8b1e1e 100%);
/* Interactive elements */
--input-bg: #2d2d2d;
--input-border: #444444;
--input-focus-border: #667eea;
--input-text: #e0e0e0;
--input-placeholder: #808080;
/* Shadows - Lighter/softer for dark mode */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.6);
/* Modal/Overlay - Same opacity */
--overlay-bg: rgba(0, 0, 0, 0.5);
/* Chart colors - Brighter and more saturated for dark backgrounds */
--chart-grid: rgba(255, 255, 255, 0.1);
--chart-text: #b0b0b0;
--chart-color-1: #8b9eff;
--chart-color-2: #6ec5ff;
--chart-color-3: #ffb3ff;
--chart-color-4: #81c784;
--chart-color-5: #ffb74d;
--chart-color-6: #9c6bb8;
--chart-color-7: #4dffff;
--chart-color-8: #ff8a9b;
}
}
/* Manual override with .dark-mode class */
body.dark-mode {
/* Background colors - Dark theme */ /* Background colors - Dark theme */
--bg-primary: #1a1a1a; --bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d; --bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a; --bg-tertiary: #3a3a3a;
--bg-card: #2d2d2d; --bg-card: #2d2d2d;
/* Text colors - Light on dark */ /* Text colors - Light on dark */
--text-primary: #e0e0e0; --text-primary: #e0e0e0;
--text-secondary: #b0b0b0; --text-secondary: #b0b0b0;
--text-muted: #808080; --text-muted: #808080;
--text-inverse: #1a1a1a; --text-inverse: #1a1a1a;
/* Border colors - Lighter for visibility */ /* Border colors - Lighter for visibility */
--border-color: #444444; --border-color: #444444;
--border-light: #4a4a4a; --border-light: #4a4a4a;
--border-lighter: #505050; --border-lighter: #505050;
/* Gradients - Dimmed (~50% brightness reduction) */ /* Gradients - Dimmed (~50% brightness reduction) */
--gradient-purple: linear-gradient(135deg, #3d4d8c 0%, #452d61 100%); --gradient-purple: linear-gradient(135deg, #3d4d8c 0%, #452d61 100%);
--gradient-blue: linear-gradient(135deg, #2f667f 0%, #00919a 100%); --gradient-blue: linear-gradient(135deg, #2f667f 0%, #00919a 100%);
--gradient-pink: linear-gradient(135deg, #905997 0%, #924344 100%); --gradient-pink: linear-gradient(135deg, #905997 0%, #924344 100%);
--gradient-green: linear-gradient(135deg, #33661c 0%, #64873c 100%); --gradient-green: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-orange: linear-gradient(135deg, #915b2d 0%, #917a2e 100%); --gradient-orange: linear-gradient(135deg, #915b2d 0%, #917a2e 100%);
/* Action colors - Slightly brighter for visibility */ /* Action colors - Slightly brighter for visibility */
--color-success: #66bb6a; --color-success: #66bb6a;
--color-error: #ef5350; --color-error: #ef5350;
--color-warning: #ffa726; --color-warning: #ffa726;
--color-info: #42a5f5; --color-info: #42a5f5;
--color-danger: #ff5252; --color-danger: #ff5252;
/* Action gradients - Dimmed */ /* Action gradients - Dimmed */
--gradient-success: linear-gradient(135deg, #33661c 0%, #64873c 100%); --gradient-success: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-error: linear-gradient(135deg, #a32a2a 0%, #8b2020 100%); --gradient-error: linear-gradient(135deg, #a32a2a 0%, #8b2020 100%);
--gradient-danger: linear-gradient(135deg, #a32929 0%, #8b1e1e 100%); --gradient-danger: linear-gradient(135deg, #a32929 0%, #8b1e1e 100%);
/* Interactive elements */ /* Interactive elements */
--input-bg: #2d2d2d; --input-bg: #2d2d2d;
--input-border: #444444; --input-border: #444444;
--input-focus-border: #667eea; --input-focus-border: #667eea;
--input-text: #e0e0e0; --input-text: #e0e0e0;
--input-placeholder: #808080; --input-placeholder: #808080;
/* Shadows - Lighter/softer for dark mode */ /* Shadows - Lighter/softer for dark mode */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3); --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.4); --shadow-md: 0 2px 10px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5); --shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.6); --shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.6);
/* Modal/Overlay - Same opacity */ /* Modal/Overlay - Same opacity */
--overlay-bg: rgba(0, 0, 0, 0.5); --overlay-bg: rgba(0, 0, 0, 0.5);
/* Chart colors - Brighter and more saturated for dark backgrounds */ /* Chart colors - Brighter and more saturated for dark backgrounds */
--chart-grid: rgba(255, 255, 255, 0.1); --chart-grid: rgba(255, 255, 255, 0.1);
--chart-text: #b0b0b0; --chart-text: #b0b0b0;
@@ -206,53 +138,121 @@ body.dark-mode {
--chart-color-6: #9c6bb8; --chart-color-6: #9c6bb8;
--chart-color-7: #4dffff; --chart-color-7: #4dffff;
--chart-color-8: #ff8a9b; --chart-color-8: #ff8a9b;
}
}
/* Manual override with .dark-mode class */
body.dark-mode {
/* Background colors - Dark theme */
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a;
--bg-card: #2d2d2d;
/* Text colors - Light on dark */
--text-primary: #e0e0e0;
--text-secondary: #b0b0b0;
--text-muted: #808080;
--text-inverse: #1a1a1a;
/* Border colors - Lighter for visibility */
--border-color: #444444;
--border-light: #4a4a4a;
--border-lighter: #505050;
/* Gradients - Dimmed (~50% brightness reduction) */
--gradient-purple: linear-gradient(135deg, #3d4d8c 0%, #452d61 100%);
--gradient-blue: linear-gradient(135deg, #2f667f 0%, #00919a 100%);
--gradient-pink: linear-gradient(135deg, #905997 0%, #924344 100%);
--gradient-green: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-orange: linear-gradient(135deg, #915b2d 0%, #917a2e 100%);
/* Action colors - Slightly brighter for visibility */
--color-success: #66bb6a;
--color-error: #ef5350;
--color-warning: #ffa726;
--color-info: #42a5f5;
--color-danger: #ff5252;
/* Action gradients - Dimmed */
--gradient-success: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-error: linear-gradient(135deg, #a32a2a 0%, #8b2020 100%);
--gradient-danger: linear-gradient(135deg, #a32929 0%, #8b1e1e 100%);
/* Interactive elements */
--input-bg: #2d2d2d;
--input-border: #444444;
--input-focus-border: #667eea;
--input-text: #e0e0e0;
--input-placeholder: #808080;
/* Shadows - Lighter/softer for dark mode */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.6);
/* Modal/Overlay - Same opacity */
--overlay-bg: rgba(0, 0, 0, 0.5);
/* Chart colors - Brighter and more saturated for dark backgrounds */
--chart-grid: rgba(255, 255, 255, 0.1);
--chart-text: #b0b0b0;
--chart-color-1: #8b9eff;
--chart-color-2: #6ec5ff;
--chart-color-3: #ffb3ff;
--chart-color-4: #81c784;
--chart-color-5: #ffb74d;
--chart-color-6: #9c6bb8;
--chart-color-7: #4dffff;
--chart-color-8: #ff8a9b;
} }
/* Manual override with .light-mode class (forces light even when browser prefers dark) */ /* Manual override with .light-mode class (forces light even when browser prefers dark) */
body.light-mode { body.light-mode {
/* Force light mode values - same as root defaults */ /* Force light mode values - same as root defaults */
--bg-primary: #f0f0f0; --bg-primary: #f0f0f0;
--bg-secondary: #ffffff; --bg-secondary: #ffffff;
--bg-tertiary: #f8f9fa; --bg-tertiary: #f8f9fa;
--bg-card: #ffffff; --bg-card: #ffffff;
--text-primary: #333333; --text-primary: #333333;
--text-secondary: #666666; --text-secondary: #666666;
--text-muted: #999999; --text-muted: #999999;
--text-inverse: #ffffff; --text-inverse: #ffffff;
--border-color: #dddddd; --border-color: #dddddd;
--border-light: #e0e0e0; --border-light: #e0e0e0;
--border-lighter: #eeeeee; --border-lighter: #eeeeee;
--gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--gradient-blue: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); --gradient-blue: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--gradient-pink: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); --gradient-pink: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--gradient-green: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%); --gradient-green: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-orange: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%); --gradient-orange: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%);
--color-success: #4caf50; --color-success: #4caf50;
--color-error: #dc3545; --color-error: #dc3545;
--color-warning: #ff9800; --color-warning: #ff9800;
--color-info: #2196f3; --color-info: #2196f3;
--color-danger: #f44336; --color-danger: #f44336;
--gradient-success: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%); --gradient-success: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-error: linear-gradient(135deg, #dc3545 0%, #c62828 100%); --gradient-error: linear-gradient(135deg, #dc3545 0%, #c62828 100%);
--gradient-danger: linear-gradient(135deg, #f44336 0%, #d32f2f 100%); --gradient-danger: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
--input-bg: #ffffff; --input-bg: #ffffff;
--input-border: #dddddd; --input-border: #dddddd;
--input-focus-border: #667eea; --input-focus-border: #667eea;
--input-text: #333333; --input-text: #333333;
--input-placeholder: #999999; --input-placeholder: #999999;
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1); --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.1); --shadow-md: 0 2px 10px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.15); --shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.15);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.2); --shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.2);
--overlay-bg: rgba(0, 0, 0, 0.5); --overlay-bg: rgba(0, 0, 0, 0.5);
--chart-grid: rgba(0, 0, 0, 0.1); --chart-grid: rgba(0, 0, 0, 0.1);
--chart-text: #666666; --chart-text: #666666;
--chart-color-1: #667eea; --chart-color-1: #667eea;
--chart-color-2: #4facfe; --chart-color-2: #4facfe;
--chart-color-3: #f093fb; --chart-color-3: #f093fb;
--chart-color-4: #56ab2f; --chart-color-4: #56ab2f;
--chart-color-5: #f2994a; --chart-color-5: #f2994a;
--chart-color-6: #764ba2; --chart-color-6: #764ba2;
--chart-color-7: #00f2fe; --chart-color-7: #00f2fe;
--chart-color-8: #f5576c; --chart-color-8: #f5576c;
} }
+7 -3
View File
@@ -857,7 +857,7 @@
}, },
}, },
scales: { scales: {
y: getChartScaleOptions('y', { y: getChartScaleOptions("y", {
beginAtZero: true, beginAtZero: true,
title: { title: {
display: true, display: true,
@@ -1098,8 +1098,12 @@
loadDiaperChanges(); loadDiaperChanges();
// Listen for theme changes and update charts // Listen for theme changes and update charts
window.addEventListener('themechange', () => { window.addEventListener("themechange", () => {
const charts = [barChartInstance, poopPieChartInstance, peePieChartInstance].filter(c => c); const charts = [
barChartInstance,
poopPieChartInstance,
peePieChartInstance,
].filter((c) => c);
updateChartsForTheme(charts); updateChartsForTheme(charts);
}); });
</script> </script>
+21 -17
View File
@@ -424,13 +424,13 @@
// Build child dropdown options // Build child dropdown options
const childOptions = allChildren const childOptions = allChildren
.map( .map(
(child) => (child) =>
`<option value="${child.id}" ${selectedChildId === child.id ? "selected" : ""}>${child.name}</option>`, `<option value="${child.id}" ${selectedChildId === child.id ? "selected" : ""}>${child.name}</option>`,
) )
.join(""); .join("");
content.innerHTML = ` content.innerHTML = `
<div class="controls"> <div class="controls">
${ ${
allChildren.length === 1 allChildren.length === 1
@@ -486,13 +486,13 @@
</div> </div>
`; `;
// Render charts // Render charts
renderBarChart(barChartData); renderBarChart(barChartData);
renderPieChart(pieChartData); renderPieChart(pieChartData);
renderDurationChart(durationChartData); renderDurationChart(durationChartData);
// Render feeding list // Render feeding list
renderFeedingList(feedings); renderFeedingList(feedings);
} }
function renderFeedingList(feedings) { function renderFeedingList(feedings) {
@@ -940,7 +940,7 @@
display: false, display: false,
}, },
}, },
y: getChartScaleOptions('y', { y: getChartScaleOptions("y", {
stacked: true, stacked: true,
beginAtZero: true, beginAtZero: true,
title: { title: {
@@ -1127,7 +1127,7 @@
}, },
}, },
scales: { scales: {
y: getChartScaleOptions('y', { y: getChartScaleOptions("y", {
beginAtZero: true, beginAtZero: true,
title: { title: {
display: true, display: true,
@@ -1144,7 +1144,7 @@
}, },
}, },
}), }),
x: getChartScaleOptions('x', { x: getChartScaleOptions("x", {
title: { title: {
display: true, display: true,
text: "Duration (minutes)", text: "Duration (minutes)",
@@ -1202,8 +1202,12 @@
loadFeedings(); loadFeedings();
// Listen for theme changes and update charts // Listen for theme changes and update charts
window.addEventListener('themechange', () => { window.addEventListener("themechange", () => {
const charts = [barChartInstance, pieChartInstance, durationChartInstance].filter(c => c); const charts = [
barChartInstance,
pieChartInstance,
durationChartInstance,
].filter((c) => c);
updateChartsForTheme(charts); updateChartsForTheme(charts);
}); });
</script> </script>
+7 -5
View File
@@ -3,9 +3,11 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Baby Monitor</title> <link rel="stylesheet" href="/dark-mode.css" /> <title>Baby Monitor</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script> <script src="/theme-init.js"></script>
<script src="/chart-theme.js"></script> <link rel="stylesheet" href="/menu.css" /> <script src="/chart-theme.js"></script>
<link rel="stylesheet" href="/menu.css" />
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -412,7 +414,7 @@
if (feedings.length > 0) { if (feedings.length > 0) {
// Calculate the most recent feeding session // Calculate the most recent feeding session
let sessionStart = feedings[0].start_time; let sessionStart = feedings[0].start_time;
let sessionEnd = feedings[0].end_time || feedings[0].start_time; let sessionEnd = feedings[0].end_time;
let sessionChildId = feedings[0].child_id; let sessionChildId = feedings[0].child_id;
let sessionEntries = [feedings[0]]; let sessionEntries = [feedings[0]];
@@ -647,7 +649,7 @@
<a href="/feedings.html" class="activity-card" style="text-decoration:none; color:inherit;"> <a href="/feedings.html" class="activity-card" style="text-decoration:none; color:inherit;">
<div class="icon">🍼</div> <div class="icon">🍼</div>
<div class="type">Last Feeding Session</div> <div class="type">Last Feeding Session</div>
<div class="time">${formatTime(lastFeeding.start_time)}</div> <div class="time">${lastFeeding.end_time ? `stopped ${formatTime(lastFeeding.end_time)}` : `started ${formatTime(lastFeeding.start_time)}`}</div>
<div class="details"> <div class="details">
${lastFeeding.feeding_types_string ? `${lastFeeding.feeding_types_string}` : ""} ${lastFeeding.feeding_types_string ? `${lastFeeding.feeding_types_string}` : ""}
${lastFeeding.feeding_types_string && lastFeeding.accumulated_minutes ? " • " : ""} ${lastFeeding.feeding_types_string && lastFeeding.accumulated_minutes ? " • " : ""}
@@ -669,7 +671,7 @@
<a href="/sleep.html" class="activity-card" style="text-decoration:none; color:inherit;"> <a href="/sleep.html" class="activity-card" style="text-decoration:none; color:inherit;">
<div class="icon">😴</div> <div class="icon">😴</div>
<div class="type">Last Sleep</div> <div class="type">Last Sleep</div>
<div class="time">${formatTime(lastSleep.start_time)}</div> <div class="time">${lastSleep.end_time ? `stopped ${formatTime(lastSleep.end_time)}` : `started ${formatTime(lastSleep.start_time)}`}</div>
<div class="details"> <div class="details">
Duration: ${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"} Duration: ${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"}
</div> </div>
+1 -1
View File
@@ -245,7 +245,7 @@
<button <button
type="button" type="button"
class="button secondary" class="button secondary"
onclick="window.location.href=getReturnUrl()" onclick="window.location.href = getReturnUrl()"
> >
Cancel Cancel
</button> </button>
+10 -3
View File
@@ -9,7 +9,9 @@
padding: 0.5rem; padding: 0.5rem;
border-radius: 4px; border-radius: 4px;
box-shadow: var(--shadow-md, 0 2px 10px rgba(0, 0, 0, 0.1)); box-shadow: var(--shadow-md, 0 2px 10px rgba(0, 0, 0, 0.1));
transition: background 0.3s, box-shadow 0.3s; transition:
background 0.3s,
box-shadow 0.3s;
} }
.burger-menu div { .burger-menu div {
@@ -43,7 +45,9 @@
height: 100vh; height: 100vh;
background: var(--bg-secondary, #ffffff); background: var(--bg-secondary, #ffffff);
box-shadow: var(--shadow-md, 2px 0 10px rgba(0, 0, 0, 0.1)); box-shadow: var(--shadow-md, 2px 0 10px rgba(0, 0, 0, 0.1));
transition: left 0.3s, background 0.3s; transition:
left 0.3s,
background 0.3s;
z-index: 999; z-index: 999;
padding: 4rem 1rem 1rem 1rem; padding: 4rem 1rem 1rem 1rem;
overflow: hidden; overflow: hidden;
@@ -57,7 +61,10 @@
padding: 1rem; padding: 1rem;
border-bottom: 1px solid var(--border-color, #eeeeee); border-bottom: 1px solid var(--border-color, #eeeeee);
cursor: pointer; cursor: pointer;
transition: background 0.2s, color 0.3s, border-color 0.3s; transition:
background 0.2s,
color 0.3s,
border-color 0.3s;
color: var(--text-primary, #333333); color: var(--text-primary, #333333);
} }
+152 -31
View File
@@ -240,30 +240,132 @@
<div class="section"> <div class="section">
<h2>🌓 Theme Preference</h2> <h2>🌓 Theme Preference</h2>
<p style="color: var(--text-secondary); margin-bottom: 20px; font-size: 14px"> <p
style="
color: var(--text-secondary);
margin-bottom: 20px;
font-size: 14px;
"
>
Choose how Baby Monitor looks to you. Choose how Baby Monitor looks to you.
</p> </p>
<div class="form-group"> <div class="form-group">
<div style="display: flex; flex-direction: column; gap: 15px"> <div style="display: flex; flex-direction: column; gap: 15px">
<label style="display: flex; align-items: center; cursor: pointer; padding: 12px; border: 2px solid var(--border-light); border-radius: 8px; transition: all 0.2s;" class="theme-option" data-theme="light"> <label
<input type="radio" name="theme" value="light" style="margin-right: 12px; width: 20px; height: 20px; cursor: pointer;" /> style="
display: flex;
align-items: center;
cursor: pointer;
padding: 12px;
border: 2px solid var(--border-light);
border-radius: 8px;
transition: all 0.2s;
"
class="theme-option"
data-theme="light"
>
<input
type="radio"
name="theme"
value="light"
style="
margin-right: 12px;
width: 20px;
height: 20px;
cursor: pointer;
"
/>
<div> <div>
<div style="font-weight: 600; color: var(--text-primary); margin-bottom: 4px;">☀️ Light Mode</div> <div
<div style="font-size: 12px; color: var(--text-secondary);">Always use light theme</div> style="
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
"
>
☀️ Light Mode
</div>
<div style="font-size: 12px; color: var(--text-secondary)">
Always use light theme
</div>
</div> </div>
</label> </label>
<label style="display: flex; align-items: center; cursor: pointer; padding: 12px; border: 2px solid var(--border-light); border-radius: 8px; transition: all 0.2s;" class="theme-option" data-theme="dark"> <label
<input type="radio" name="theme" value="dark" style="margin-right: 12px; width: 20px; height: 20px; cursor: pointer;" /> style="
display: flex;
align-items: center;
cursor: pointer;
padding: 12px;
border: 2px solid var(--border-light);
border-radius: 8px;
transition: all 0.2s;
"
class="theme-option"
data-theme="dark"
>
<input
type="radio"
name="theme"
value="dark"
style="
margin-right: 12px;
width: 20px;
height: 20px;
cursor: pointer;
"
/>
<div> <div>
<div style="font-weight: 600; color: var(--text-primary); margin-bottom: 4px;">🌙 Dark Mode</div> <div
<div style="font-size: 12px; color: var(--text-secondary);">Always use dark theme</div> style="
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
"
>
🌙 Dark Mode
</div>
<div style="font-size: 12px; color: var(--text-secondary)">
Always use dark theme
</div>
</div> </div>
</label> </label>
<label style="display: flex; align-items: center; cursor: pointer; padding: 12px; border: 2px solid var(--border-light); border-radius: 8px; transition: all 0.2s;" class="theme-option" data-theme="auto"> <label
<input type="radio" name="theme" value="auto" style="margin-right: 12px; width: 20px; height: 20px; cursor: pointer;" /> style="
display: flex;
align-items: center;
cursor: pointer;
padding: 12px;
border: 2px solid var(--border-light);
border-radius: 8px;
transition: all 0.2s;
"
class="theme-option"
data-theme="auto"
>
<input
type="radio"
name="theme"
value="auto"
style="
margin-right: 12px;
width: 20px;
height: 20px;
cursor: pointer;
"
/>
<div> <div>
<div style="font-weight: 600; color: var(--text-primary); margin-bottom: 4px;">🔄 Auto</div> <div
<div style="font-size: 12px; color: var(--text-secondary);">Match your browser settings</div> style="
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
"
>
🔄 Auto
</div>
<div style="font-size: 12px; color: var(--text-secondary)">
Match your browser settings
</div>
</div> </div>
</label> </label>
</div> </div>
@@ -298,7 +400,13 @@
" "
> >
<h3 style="color: #dc3545; margin-bottom: 15px">⚠️ Danger Zone</h3> <h3 style="color: #dc3545; margin-bottom: 15px">⚠️ Danger Zone</h3>
<p style="color: var(--text-secondary); margin-bottom: 15px; font-size: 14px"> <p
style="
color: var(--text-secondary);
margin-bottom: 15px;
font-size: 14px;
"
>
Deleting your account is permanent and cannot be undone. This will: Deleting your account is permanent and cannot be undone. This will:
</p> </p>
<ul <ul
@@ -322,9 +430,7 @@
</ul> </ul>
<button <button
class="button" class="button"
style=" style="background: var(--gradient-error)"
background: var(--gradient-error);
"
onclick="confirmDeleteAccount()" onclick="confirmDeleteAccount()"
> >
🗑️ Delete Account 🗑️ Delete Account
@@ -337,7 +443,7 @@
<button <button
class="quick-action" class="quick-action"
onclick="window.location.href='/add-child.html'" onclick="window.location.href = '/add-child.html'"
> >
Add New Child Add New Child
</button> </button>
@@ -382,7 +488,9 @@
width: 90%; width: 90%;
" "
> >
<h2 style="margin-top: 0; color: var(--text-primary);">🔗 Redeem Share Code</h2> <h2 style="margin-top: 0; color: var(--text-primary)">
🔗 Redeem Share Code
</h2>
<p style="color: var(--text-secondary); margin-bottom: 20px"> <p style="color: var(--text-secondary); margin-bottom: 20px">
Enter the share code to get access to a child. Enter the share code to get access to a child.
</p> </p>
@@ -448,8 +556,11 @@
width: 90%; width: 90%;
" "
> >
<h2 style="margin-top: 0; color: var(--text-primary);">🔗 Share Code</h2> <h2 style="margin-top: 0; color: var(--text-primary)">🔗 Share Code</h2>
<p style="color: var(--text-secondary); margin-bottom: 20px" id="shareCodeChildName"></p> <p
style="color: var(--text-secondary); margin-bottom: 20px"
id="shareCodeChildName"
></p>
<div <div
style=" style="
background: var(--bg-tertiary); background: var(--bg-tertiary);
@@ -490,7 +601,13 @@
</button> </button>
</div> </div>
</div> </div>
<p style="color: var(--text-secondary); font-size: 14px; margin-bottom: 20px"> <p
style="
color: var(--text-secondary);
font-size: 14px;
margin-bottom: 20px;
"
>
Share this code with another user. They can use it in Settings > Share this code with another user. They can use it in Settings >
Redeem Share Code to get access to this child. Redeem Share Code to get access to this child.
<br /><br /> <br /><br />
@@ -520,7 +637,9 @@
// Initialize theme selector // Initialize theme selector
function initThemeSelector() { function initThemeSelector() {
const currentTheme = window.getThemePreference(); const currentTheme = window.getThemePreference();
const themeRadio = document.querySelector(`input[name="theme"][value="${currentTheme}"]`); const themeRadio = document.querySelector(
`input[name="theme"][value="${currentTheme}"]`,
);
if (themeRadio) { if (themeRadio) {
themeRadio.checked = true; themeRadio.checked = true;
} }
@@ -529,8 +648,8 @@
updateThemeOptionStyles(); updateThemeOptionStyles();
// Listen for theme changes // Listen for theme changes
document.querySelectorAll('input[name="theme"]').forEach(radio => { document.querySelectorAll('input[name="theme"]').forEach((radio) => {
radio.addEventListener('change', (e) => { radio.addEventListener("change", (e) => {
window.setTheme(e.target.value); window.setTheme(e.target.value);
updateThemeOptionStyles(); updateThemeOptionStyles();
}); });
@@ -538,15 +657,17 @@
} }
function updateThemeOptionStyles() { function updateThemeOptionStyles() {
const selectedValue = document.querySelector('input[name="theme"]:checked')?.value; const selectedValue = document.querySelector(
document.querySelectorAll('.theme-option').forEach(option => { 'input[name="theme"]:checked',
)?.value;
document.querySelectorAll(".theme-option").forEach((option) => {
const isSelected = option.dataset.theme === selectedValue; const isSelected = option.dataset.theme === selectedValue;
if (isSelected) { if (isSelected) {
option.style.borderColor = '#667eea'; option.style.borderColor = "#667eea";
option.style.background = 'rgba(102, 126, 234, 0.05)'; option.style.background = "rgba(102, 126, 234, 0.05)";
} else { } else {
option.style.borderColor = '#e0e0e0'; option.style.borderColor = "#e0e0e0";
option.style.background = 'transparent'; option.style.background = "transparent";
} }
}); });
} }
+8 -4
View File
@@ -895,7 +895,7 @@
x: { x: {
stacked: true, stacked: true,
}, },
y: getChartScaleOptions('y', { y: getChartScaleOptions("y", {
stacked: true, stacked: true,
beginAtZero: true, beginAtZero: true,
title: { title: {
@@ -1056,7 +1056,7 @@
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
scales: { scales: {
y: getChartScaleOptions('y', { y: getChartScaleOptions("y", {
beginAtZero: true, beginAtZero: true,
ticks: { ticks: {
callback: function (value) { callback: function (value) {
@@ -1135,8 +1135,12 @@
loadData(); loadData();
// Listen for theme changes and update charts // Listen for theme changes and update charts
window.addEventListener('themechange', () => { window.addEventListener("themechange", () => {
const charts = [barChartInstance, durationChartInstance, avgDurationChartInstance].filter(c => c); const charts = [
barChartInstance,
durationChartInstance,
avgDurationChartInstance,
].filter((c) => c);
updateChartsForTheme(charts); updateChartsForTheme(charts);
}); });
</script> </script>
+78 -67
View File
@@ -3,74 +3,85 @@
* Must be loaded in <head> before page content to prevent flash of unstyled content * Must be loaded in <head> before page content to prevent flash of unstyled content
*/ */
(function() { (function () {
'use strict'; "use strict";
// Get saved theme preference from localStorage // Get saved theme preference from localStorage
const savedTheme = localStorage.getItem('theme-preference'); const savedTheme = localStorage.getItem("theme-preference");
// Apply theme immediately to prevent FOUC (Flash of Unstyled Content) // Apply theme immediately to prevent FOUC (Flash of Unstyled Content)
function applyTheme(theme) { function applyTheme(theme) {
const body = document.body || document.documentElement; const body = document.body || document.documentElement;
if (theme === 'dark') { if (theme === "dark") {
body.classList.remove('light-mode'); body.classList.remove("light-mode");
body.classList.add('dark-mode'); body.classList.add("dark-mode");
} else if (theme === 'light') { } else if (theme === "light") {
body.classList.remove('dark-mode'); body.classList.remove("dark-mode");
body.classList.add('light-mode'); body.classList.add("light-mode");
} else { } else {
// 'auto' mode - remove manual override classes // 'auto' mode - remove manual override classes
body.classList.remove('dark-mode', 'light-mode'); body.classList.remove("dark-mode", "light-mode");
}
} }
}
// Apply theme based on preference
if (savedTheme === 'dark' || savedTheme === 'light' || savedTheme === 'auto') { // Apply theme based on preference
applyTheme(savedTheme); if (
savedTheme === "dark" ||
savedTheme === "light" ||
savedTheme === "auto"
) {
applyTheme(savedTheme);
}
// If no preference saved, default to 'auto' (browser preference)
// Make theme switching function available globally
window.setTheme = function (theme) {
if (theme === "light" || theme === "dark" || theme === "auto") {
localStorage.setItem("theme-preference", theme);
applyTheme(theme);
// Trigger custom event for components that need to update (e.g., charts)
window.dispatchEvent(
new CustomEvent("themechange", { detail: { theme } }),
);
} }
// If no preference saved, default to 'auto' (browser preference) };
// Make theme switching function available globally // Get current effective theme (resolves 'auto' to actual theme)
window.setTheme = function(theme) { window.getEffectiveTheme = function () {
if (theme === 'light' || theme === 'dark' || theme === 'auto') { const savedTheme = localStorage.getItem("theme-preference");
localStorage.setItem('theme-preference', theme);
applyTheme(theme); if (savedTheme === "dark") return "dark";
if (savedTheme === "light") return "light";
// Trigger custom event for components that need to update (e.g., charts)
window.dispatchEvent(new CustomEvent('themechange', { detail: { theme } })); // 'auto' or no preference - check system preference
} if (
}; window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
// Get current effective theme (resolves 'auto' to actual theme) ) {
window.getEffectiveTheme = function() { return "dark";
const savedTheme = localStorage.getItem('theme-preference');
if (savedTheme === 'dark') return 'dark';
if (savedTheme === 'light') return 'light';
// 'auto' or no preference - check system preference
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
};
// Get saved theme preference (returns 'light', 'dark', 'auto', or null)
window.getThemePreference = function() {
return localStorage.getItem('theme-preference') || 'auto';
};
// Listen for system theme changes when in auto mode
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', function(e) {
const savedTheme = localStorage.getItem('theme-preference');
// Only react to system changes if in auto mode
if (!savedTheme || savedTheme === 'auto') {
// Trigger theme change event for components that need to update
window.dispatchEvent(new CustomEvent('themechange', { detail: { theme: 'auto' } }));
}
});
} }
return "light";
};
// Get saved theme preference (returns 'light', 'dark', 'auto', or null)
window.getThemePreference = function () {
return localStorage.getItem("theme-preference") || "auto";
};
// Listen for system theme changes when in auto mode
if (window.matchMedia) {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", function (e) {
const savedTheme = localStorage.getItem("theme-preference");
// Only react to system changes if in auto mode
if (!savedTheme || savedTheme === "auto") {
// Trigger theme change event for components that need to update
window.dispatchEvent(
new CustomEvent("themechange", { detail: { theme: "auto" } }),
);
}
});
}
})(); })();
Generated
+465 -468
View File
File diff suppressed because it is too large Load Diff