Josh's Changes
This commit is contained in:
parent
df06dcc0d7
commit
03825c1048
@ -780,6 +780,86 @@ body.header-tone-light .app-header .tab.active {
|
||||
}
|
||||
}
|
||||
|
||||
/* TV dashboard: dark room mode (wall display) */
|
||||
body.calendar-tv-dark {
|
||||
color-scheme: dark;
|
||||
background-color: #0f172a;
|
||||
color: #e2e8f0;
|
||||
--text-color: #e2e8f0;
|
||||
--border-color: #334155;
|
||||
--secondary-color: #1e293b;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark main {
|
||||
background-color: #0f172a;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view h2,
|
||||
body.calendar-tv-dark .calendar-tv-view h3 {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .text-muted {
|
||||
color: #94a3b8 !important;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .calendar-embed {
|
||||
background-color: #1e293b;
|
||||
border-color: #334155 !important;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .calendar-embed.bg-white {
|
||||
background-color: #1e293b !important;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .list-group-item {
|
||||
background-color: rgba(30, 41, 59, 0.75);
|
||||
color: #e2e8f0;
|
||||
border-color: #334155 !important;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .list-group-item-action:hover,
|
||||
body.calendar-tv-dark .calendar-tv-view .list-group-item-action:focus {
|
||||
background-color: rgba(51, 65, 85, 0.65);
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .calendar-agenda-today {
|
||||
background-color: color-mix(in srgb, var(--fh-primary, #4a90e2) 24%, #1e293b);
|
||||
border-color: var(--fh-primary, #60a5fa) !important;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .calendar-agenda-today .text-primary {
|
||||
color: var(--fh-primary, #93c5fd) !important;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .text-primary {
|
||||
color: var(--fh-primary, #93c5fd) !important;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .btn-outline-secondary {
|
||||
color: #cbd5e1;
|
||||
border-color: #64748b;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .btn-outline-secondary:hover,
|
||||
body.calendar-tv-dark .calendar-tv-view .btn-outline-secondary:focus {
|
||||
background-color: #334155;
|
||||
border-color: #94a3b8;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view code {
|
||||
background-color: #334155;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.calendar-tv-dark .calendar-tv-view .badge.bg-secondary {
|
||||
background-color: #475569 !important;
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
|
||||
/* Meal detail (recipe) page: sections and flow */
|
||||
.meal-detail-header {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
@ -8,6 +8,30 @@
|
||||
|
||||
var apiBase = typeof window.familyHubApiBase === 'string' ? window.familyHubApiBase : '/api';
|
||||
|
||||
/**
|
||||
* Avoids JSON.parse on empty bodies (Firefox: "unexpected end of data at line 1 column 1")
|
||||
* and surfaces clearer errors when PHP returns HTML or nothing.
|
||||
*/
|
||||
function parseJsonResponseText(res, text) {
|
||||
var trimmed = (text || '').trim();
|
||||
if (trimmed === '') {
|
||||
var emptyErr = new Error(
|
||||
!res.ok ? (res.statusText || 'Request failed') : 'Empty response from server.'
|
||||
);
|
||||
emptyErr.payload = {};
|
||||
emptyErr.status = res.status;
|
||||
throw emptyErr;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(trimmed);
|
||||
} catch (e) {
|
||||
var parseErr = new Error('Server did not return JSON. Check server logs or network tab.');
|
||||
parseErr.payload = trimmed.slice(0, 500);
|
||||
parseErr.status = res.status;
|
||||
throw parseErr;
|
||||
}
|
||||
}
|
||||
|
||||
function postJson(path, body) {
|
||||
return fetch(apiBase + path, {
|
||||
method: 'POST',
|
||||
@ -15,7 +39,8 @@
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify(body || {})
|
||||
}).then(function (res) {
|
||||
return res.json().then(function (data) {
|
||||
return res.text().then(function (text) {
|
||||
var data = parseJsonResponseText(res, text);
|
||||
if (!res.ok) {
|
||||
var err = new Error(data.error || res.statusText || 'Request failed');
|
||||
err.payload = data;
|
||||
@ -775,11 +800,15 @@
|
||||
|
||||
function readMealsLibrary() {
|
||||
var el = document.getElementById('mealsLibraryJson');
|
||||
if (!el || !el.textContent) {
|
||||
if (!el) {
|
||||
return [];
|
||||
}
|
||||
var raw = (el.textContent || '').trim();
|
||||
if (raw === '') {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
return JSON.parse(el.textContent);
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
@ -1032,7 +1061,8 @@
|
||||
var safeId = String(mealId);
|
||||
fetch(apiBase + '/meal_export.php?mealId=' + encodeURIComponent(safeId), { credentials: 'same-origin' })
|
||||
.then(function (res) {
|
||||
return res.json().then(function (data) {
|
||||
return res.text().then(function (text) {
|
||||
var data = parseJsonResponseText(res, text);
|
||||
return { res: res, data: data };
|
||||
});
|
||||
})
|
||||
@ -1083,7 +1113,11 @@
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
try {
|
||||
mealImportParsed = JSON.parse(String(reader.result || ''));
|
||||
var raw = String(reader.result || '').trim();
|
||||
if (raw === '') {
|
||||
throw new Error('File is empty.');
|
||||
}
|
||||
mealImportParsed = JSON.parse(raw);
|
||||
$prev.text(JSON.stringify(mealImportParsed, null, 2));
|
||||
$('#btnMealImportSubmit').prop('disabled', false);
|
||||
if ($fb.length) {
|
||||
|
||||
@ -3,7 +3,9 @@ GOOGLE_CLIENT_ID=your_client_id_here
|
||||
GOOGLE_CLIENT_SECRET=your_client_secret_here
|
||||
GOOGLE_REDIRECT_URI=http://localhost/family-hub/auth/google/callback
|
||||
|
||||
# Google Calendar
|
||||
# Google Calendar (Calendar tab embed). Replace placeholders—leaving your_calendar_id_here hides the embed.
|
||||
# Easiest: set GOOGLE_CALENDAR_ID to the Calendar ID from Google Calendar → Settings → Integrate calendar.
|
||||
# Optional: paste the embed URL or full <iframe> HTML into GOOGLE_CALENDAR_EMBED_CODE; wrap the whole value in "double quotes" if you have parsing issues.
|
||||
GOOGLE_CALENDAR_ID=your_calendar_id_here
|
||||
GOOGLE_CALENDAR_EMBED_CODE=your_embed_code_here
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ function readJsonBody(): array {
|
||||
|
||||
function sendJson(array $payload, int $code = 200): void {
|
||||
http_response_code($code);
|
||||
echo json_encode($payload);
|
||||
echo json_encode($payload, familyHubJsonEncodeShellFlags());
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/persona.php';
|
||||
require_once __DIR__ . '/utils.php';
|
||||
|
||||
const CHORE_SCHEDULE_ONCE = 'once';
|
||||
const CHORE_SCHEDULE_RECURRING = 'recurring';
|
||||
@ -32,11 +33,11 @@ function legacyAssigneeNameToIds(string $name, array $people): array {
|
||||
if ($name === '') {
|
||||
return [];
|
||||
}
|
||||
$lower = mb_strtolower($name, 'UTF-8');
|
||||
$lower = familyHubStrLowerUtf8($name);
|
||||
$ids = [];
|
||||
foreach ($people as $p) {
|
||||
$pn = trim((string) ($p['name'] ?? ''));
|
||||
if ($pn !== '' && mb_strtolower($pn, 'UTF-8') === $lower) {
|
||||
if ($pn !== '' && familyHubStrLowerUtf8($pn) === $lower) {
|
||||
$ids[] = (string) $p['id'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/utils.php';
|
||||
|
||||
function getDataDirectory(): string {
|
||||
return __DIR__ . '/../data';
|
||||
}
|
||||
@ -49,7 +51,7 @@ function writeJsonFile(string $filename, $data): bool {
|
||||
fclose($fp);
|
||||
return false;
|
||||
}
|
||||
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
$json = json_encode($data, JSON_PRETTY_PRINT | familyHubJsonEncodeShellFlags());
|
||||
if ($json === false) {
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
</main>
|
||||
<?php if (empty($isCalendarTvView)): ?>
|
||||
<footer class="bg-light p-3 mt-4">
|
||||
<div class="container text-center">
|
||||
<p>Family Hub © <?= date('Y') ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Bootstrap JS from CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/utils.php';
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string, mixed>>
|
||||
@ -35,8 +37,8 @@ function normalizeCatalogList($raw): array {
|
||||
}
|
||||
|
||||
function groceryCatalogDedupeKey(string $storeId, string $name, string $size): string {
|
||||
$n = mb_strtolower(trim($name), 'UTF-8');
|
||||
$s = mb_strtolower(trim($size), 'UTF-8');
|
||||
$n = familyHubStrLowerUtf8(trim($name));
|
||||
$s = familyHubStrLowerUtf8(trim($size));
|
||||
return $storeId . '|' . $n . '|' . $s;
|
||||
}
|
||||
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
}
|
||||
$bodyStyle = implode('; ', $bodyStyleParts) . ';';
|
||||
?>
|
||||
<body class="family-hub-body <?= htmlspecialchars($headerThemeClass ?? 'header-tone-dark', ENT_QUOTES, 'UTF-8') ?>" style="<?= htmlspecialchars($bodyStyle, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<body class="family-hub-body <?= htmlspecialchars($headerThemeClass ?? 'header-tone-dark', ENT_QUOTES, 'UTF-8') ?><?= !empty($isCalendarTvView) ? ' calendar-tv-dark' : '' ?>" style="<?= htmlspecialchars($bodyStyle, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<?php $hideTopHeader = !empty($isCalendarTvView); ?>
|
||||
<?php if (!$hideTopHeader): ?>
|
||||
<header class="app-header app-header-with-tabs text-white pt-3 px-3 pb-2 mb-0">
|
||||
@ -220,4 +220,9 @@
|
||||
<script>
|
||||
window.familyHubApiBase = <?= json_encode($familyHubApiBase, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP) ?>;
|
||||
</script>
|
||||
<main class="container py-4">
|
||||
<?php
|
||||
$fhMainClasses = !empty($isCalendarTvView)
|
||||
? 'container-fluid px-0 py-0'
|
||||
: 'container py-4';
|
||||
?>
|
||||
<main class="<?= htmlspecialchars($fhMainClasses, ENT_QUOTES, 'UTF-8') ?>">
|
||||
|
||||
@ -33,3 +33,29 @@ function familyHubWebApiBase(): string {
|
||||
}
|
||||
return $sd . '/api';
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitmask for json_encode so pasted recipe text (or any user string) with malformed UTF-8
|
||||
* does not make json_encode return false. Without substitution, failed embeds emit an empty
|
||||
* <script type="application/json"> and the meals UI hits JSON.parse errors.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function familyHubJsonEncodeShellFlags(): int {
|
||||
$f = JSON_UNESCAPED_UNICODE;
|
||||
if (defined('JSON_INVALID_UTF8_SUBSTITUTE')) {
|
||||
$f |= JSON_INVALID_UTF8_SUBSTITUTE;
|
||||
}
|
||||
return $f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lowercase for comparisons and keys. Uses mbstring when available; otherwise ASCII-only strtolower
|
||||
* so grocery/chore code does not fatal on hosts without ext-mbstring.
|
||||
*/
|
||||
function familyHubStrLowerUtf8(string $s): string {
|
||||
if (function_exists('mb_strtolower')) {
|
||||
return mb_strtolower($s, 'UTF-8');
|
||||
}
|
||||
return strtolower($s);
|
||||
}
|
||||
|
||||
@ -167,7 +167,7 @@ if (fhRelativeLuminance($themePalette['primary']) >= 0.62) {
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="<?= $isCalendarTvView ? 'container-fluid px-3 py-3' : 'container' ?>">
|
||||
<div class="<?= $isCalendarTvView ? 'container-fluid px-2 py-0' : 'container' ?>">
|
||||
<div class="tab-content" id="dashboardContentArea">
|
||||
<?php include 'tabs/' . $activeTab . '.php'; ?>
|
||||
</div>
|
||||
|
||||
@ -51,7 +51,7 @@ The **Calendar** tab embeds your family calendar in the hub using these values (
|
||||
| Variable | What it is | Where to find it |
|
||||
| -------- | ----------- | ---------------- |
|
||||
| **`GOOGLE_CALENDAR_ID`** | ID of the calendar to target (APIs, sharing, embeds) | **[Google Calendar](https://calendar.google.com/)** → hover the calendar in the sidebar → **⋮** → **Settings and sharing** → scroll to **Integrate calendar** → **Calendar ID** (often an email or `...@group.calendar.google.com`). If **`GOOGLE_CALENDAR_EMBED_CODE`** is unset, the tab builds a standard embed URL from this ID. |
|
||||
| **`GOOGLE_CALENDAR_EMBED_CODE`** | Embed URL or full iframe HTML from Google | Same **Settings and sharing** page → **Integrate calendar** → copy the **embed code** (iframe) or the `https://calendar.google.com/...` URL from the `src` attribute. Prefer this when you want Google’s chosen view options. |
|
||||
| **`GOOGLE_CALENDAR_EMBED_CODE`** | Embed URL or full iframe HTML from Google | Same **Settings and sharing** page → **Integrate calendar** → copy the **embed code** (iframe) or the `https://calendar.google.com/...` URL from the `src` attribute. Prefer this when you want Google’s chosen view options. Leave the placeholder `your_embed_code_here` unchanged only if you use **`GOOGLE_CALENDAR_ID`** instead. If a long iframe line misbehaves in `.env`, wrap the whole value in **double quotes**. |
|
||||
|
||||
### Google Drive
|
||||
|
||||
@ -111,7 +111,7 @@ These are **not** in `.env`; they live in `data/family_settings.json` and are ed
|
||||
| Key / UI | Purpose | Notes |
|
||||
| -------- | -------- | ----- |
|
||||
| **Family timezone** | US IANA zone (e.g. `America/Denver`) | Dropdown of US zones only. Drives **today** and date ranges on the **Calendar** tab agenda. Options are the standard PHP US timezone identifiers (e.g. **America/***, **Pacific/Honolulu**). |
|
||||
| **Enable two-way Google Calendar sync** | Opt-in for future OAuth sync with Google | Default off. When off, the Calendar tab uses the Google **embed** from `.env` (if set) and the local agenda only—no merge API. When on, the app may show sync messaging; full OAuth is not required for the embed. |
|
||||
| **Notify when two-way Google Calendar sync is available** (`calendar_two_way_google`) | Preference for a future OAuth feature | Default off. **Does not enable sync today.** The Calendar tab still uses only the Google **iframe embed** from `.env` (if set). The Family Hub agenda is built from chores, meals, groceries, expenses, bills, and birthdays in this app—not from Google’s API. |
|
||||
| **Monthly bill reminders** | `calendar_bill_days` | JSON array of `{ "dayOfMonth": 1–31, "title": "Rent" }`. Shown on the agenda on that calendar day each month. |
|
||||
|
||||
**API:** `GET api/calendar_events.php?from=YYYY-MM-DD&to=YYYY-MM-DD` returns `{ success, events, rangeStart, rangeEnd, today }` for the signed-in profile (same session rules as other APIs). Omit `from`/`to` to use the default two-week window from **today** in the family timezone.
|
||||
|
||||
@ -11,7 +11,7 @@ $calId = defined('GOOGLE_CALENDAR_ID') ? trim((string) GOOGLE_CALENDAR_ID) : '';
|
||||
*/
|
||||
function familyHub_google_calendar_embed_url(string $raw): ?string {
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') {
|
||||
if ($raw === '' || strcasecmp($raw, 'your_embed_code_here') === 0) {
|
||||
return null;
|
||||
}
|
||||
$allowed = static function (string $u): bool {
|
||||
@ -34,7 +34,8 @@ function familyHub_google_calendar_embed_url(string $raw): ?string {
|
||||
if ($allowed($raw)) {
|
||||
return $raw;
|
||||
}
|
||||
if (preg_match('#<iframe\s[^>]*\ssrc\s*=\s*["\']([^"\']+)["\']#i', $raw, $m)) {
|
||||
// Match iframe with src first or after other attributes (previous pattern required \s before src and failed on <iframe src="...">).
|
||||
if (preg_match('#<iframe\s[^>]*\s*src\s*=\s*["\']([^"\']+)["\']#i', $raw, $m)) {
|
||||
$u = html_entity_decode($m[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
return $allowed($u) ? $u : null;
|
||||
}
|
||||
@ -56,7 +57,6 @@ $twoWayMerge = !empty($familySettings['calendar_two_way_google']);
|
||||
$isTvView = isset($_GET['view']) && (string) $_GET['view'] === 'tv';
|
||||
$calendarBaseUrl = '?tab=calendar';
|
||||
$calendarTvUrl = '?tab=calendar&view=tv';
|
||||
$lastRefreshedStamp = gmdate('Y-m-d H:i') . ' UTC';
|
||||
|
||||
$eventsByDate = [];
|
||||
foreach ($events as $ev) {
|
||||
@ -82,43 +82,40 @@ $iconByType = [
|
||||
?>
|
||||
|
||||
<div id="calendar" class="tab-content<?= $isTvView ? ' calendar-tv-view' : '' ?>">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||||
<h2 class="mb-0">Calendar</h2>
|
||||
<?php if ($isTvView): ?>
|
||||
<div class="d-flex flex-wrap justify-content-end align-items-center gap-2 mb-2 calendar-tv-toolbar">
|
||||
<a href="<?= htmlspecialchars($calendarBaseUrl, ENT_QUOTES, 'UTF-8') ?>" class="btn btn-outline-secondary btn-sm" title="Exit TV view">
|
||||
<i class="fa fa-xmark me-1" aria-hidden="true"></i>
|
||||
Exit TV view
|
||||
</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||||
<h2 class="mb-0">Calendar</h2>
|
||||
<a href="<?= htmlspecialchars($calendarTvUrl, ENT_QUOTES, 'UTF-8') ?>" class="btn btn-outline-secondary btn-sm" title="Open TV view dashboard">
|
||||
<i class="fa fa-tv me-1" aria-hidden="true"></i>
|
||||
Open TV view
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p class="text-muted small mb-3">
|
||||
<?php if ($isTvView): ?>
|
||||
TV view auto-refreshes every 10 minutes.
|
||||
<?php else: ?>
|
||||
Open TV view for a dedicated dashboard URL that refreshes every 10 minutes.
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<?php if ($isTvView): ?>
|
||||
<p class="small text-muted mb-3">Last refreshed: <strong><?= htmlspecialchars($lastRefreshedStamp, ENT_QUOTES, 'UTF-8') ?></strong></p>
|
||||
<?php endif; ?>
|
||||
<p class="text-muted small mb-3">Open TV view for a full-width dashboard without site chrome.</p>
|
||||
|
||||
<div class="alert alert-secondary mb-3" role="status">
|
||||
<strong>Google Calendar is not synced into Family Hub data.</strong>
|
||||
The Google panel below is an embedded view from Google (live in your browser). The <strong>Family Hub agenda</strong> lists chores, meals, groceries, expenses, bill reminders, and birthdays from this app only—Google events never appear there until a future API connection exists.
|
||||
<?php if ($twoWayMerge): ?>
|
||||
<div class="alert alert-info mb-3" role="status">
|
||||
<strong>Two-way Google Calendar sync</strong> is enabled in Family settings. OAuth connection is not available in this version yet. Your Google view below (if configured) and the Family Hub agenda still work; full sync will require signing in with Google when supported.
|
||||
<span class="d-block mt-1 small">You turned on “two-way sync” in settings; OAuth with Google is <strong>not implemented yet</strong>, so that option does not change behavior today.</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row g-4 calendar-tab-layout<?= $embedUrl !== null ? ' calendar-tab-layout-split' : '' ?>">
|
||||
<div class="row <?= $isTvView ? 'g-3' : 'g-4' ?> calendar-tab-layout<?= $embedUrl !== null ? ' calendar-tab-layout-split' : '' ?>">
|
||||
<div class="col-12 calendar-tab-google">
|
||||
<h3 class="h5 mb-3">Google Calendar</h3>
|
||||
<h3 class="h5 mb-3"><?= $isTvView ? 'Calendar' : 'Google Calendar' ?></h3>
|
||||
<?php if ($embedUrl === null): ?>
|
||||
<?php if (!$isTvView): ?>
|
||||
<p class="text-muted">Connect a shared family calendar by setting <strong>GOOGLE_CALENDAR_EMBED_CODE</strong> (embed URL or full <code><iframe></code> from Google Calendar) or <strong>GOOGLE_CALENDAR_ID</strong> in <code>.env</code>. See <a href="https://calendar.google.com/">Google Calendar</a> → calendar menu → <strong>Settings and sharing</strong> → <strong>Integrate calendar</strong>.</p>
|
||||
<?php else: ?>
|
||||
<p class="text-muted small mb-0">Google Calendar not configured (set <code class="small">.env</code>).</p>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<?php if ($fromIdOnly && !$isTvView): ?>
|
||||
@ -137,8 +134,11 @@ $iconByType = [
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="col-12 calendar-tab-agenda">
|
||||
<h3 class="h5 mb-3">Family Hub agenda</h3>
|
||||
<p class="text-muted small mb-3">
|
||||
<h3 class="h5 mb-3"><?= $isTvView ? 'Weekly hub' : 'Family Hub agenda' ?></h3>
|
||||
<?php if (!$isTvView): ?>
|
||||
<p class="text-muted small mb-2">Hub data only (not Google Calendar).</p>
|
||||
<?php endif; ?>
|
||||
<p class="text-muted small mb-3 hide hidden" style="display: none;">
|
||||
<?= htmlspecialchars($rangeStart, ENT_QUOTES, 'UTF-8') ?> to <?= htmlspecialchars($rangeEnd, ENT_QUOTES, 'UTF-8') ?>
|
||||
· Times use <strong><?= htmlspecialchars(str_replace('_', ' ', (string) ($familySettings['timezone'] ?? '')), ENT_QUOTES, 'UTF-8') ?></strong>
|
||||
</p>
|
||||
@ -188,3 +188,10 @@ $iconByType = [
|
||||
}, 10 * 60 * 1000);
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
<?php if ($isTvView): ?>
|
||||
<script>
|
||||
window.setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 10 * 60 * 1000);
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
@ -213,7 +213,7 @@ $activeCharityDonated = is_numeric($activeBankPerson['charity_donated_total'] ??
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<script type="application/json" id="currencyLeaderboardPeriodData"><?= json_encode($leaderboardRowsByPeriod, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?></script>
|
||||
<script type="application/json" id="currencyLeaderboardPeriodData"><?= json_encode($leaderboardRowsByPeriod, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | familyHubJsonEncodeShellFlags()) ?></script>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped mb-0 leaderboard-table">
|
||||
<thead>
|
||||
|
||||
@ -492,6 +492,6 @@ $mealEditorForbidden = $editMealId !== '' && !$showMealEditorPage;
|
||||
'title' => (string) ($m['title'] ?? ''),
|
||||
'tags' => array_values($m['tags'] ?? []),
|
||||
];
|
||||
}, $meals), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE) ?></script>
|
||||
}, $meals), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | familyHubJsonEncodeShellFlags()) ?></script>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
@ -102,13 +102,14 @@ $permanenceOptions = [
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-end">
|
||||
<div class="form-check mb-2">
|
||||
<div class="col-12">
|
||||
<div class="form-check mb-1">
|
||||
<input class="form-check-input" type="checkbox" id="calendar_two_way_google" name="calendar_two_way_google" value="1"
|
||||
<?= !empty($familySettings['calendar_two_way_google']) ? 'checked' : '' ?>
|
||||
<?= !$canManage ? 'disabled' : '' ?>>
|
||||
<label class="form-check-label" for="calendar_two_way_google">Enable two-way Google Calendar sync</label>
|
||||
<label class="form-check-label" for="calendar_two_way_google">Notify me when two-way Google Calendar sync is available</label>
|
||||
</div>
|
||||
<p class="small text-muted mb-0">Family Hub does not call Google’s API yet. Your calendar appears on the Calendar tab only via <code class="small">.env</code> (<code>GOOGLE_CALENDAR_ID</code> or <code>GOOGLE_CALENDAR_EMBED_CODE</code>). This checkbox only stores a preference; it does not enable sync today.</p>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Monthly bill reminders</label>
|
||||
@ -368,7 +369,7 @@ $permanenceOptions = [
|
||||
];
|
||||
}
|
||||
?>
|
||||
<script type="application/json" id="settingsPeopleEditPayload"><?= json_encode($peopleEditPayload, JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE) ?></script>
|
||||
<script type="application/json" id="settingsPeopleEditPayload"><?= json_encode($peopleEditPayload, JSON_HEX_TAG | JSON_HEX_AMP | familyHubJsonEncodeShellFlags()) ?></script>
|
||||
|
||||
<div class="modal fade" id="editPersonModal" tabindex="-1" aria-labelledby="editPersonModalLabel" aria-hidden="true" data-bs-backdrop="false">
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user