Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

All notable changes to PWA Template will be documented in this file. This project adheres to a manual release process; update both this file and `assets/changelog.json` when shipping new versions so the in-app update summary stays accurate.

## [0.0.1] - 2025-10-11
- Make the 4 day week leave summary combine zero-day allowances and totals into a concise statement.

## [0.0.0] - 2025-10-11
- Create base Progressive Web app.

7 changes: 7 additions & 0 deletions assets/changelog.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
[
{
"version": "0.0.1",
"date": "2025-10-11",
"changes": [
"Made the 4 day week leave summary combine zero-day allowances and totals into a concise statement."
]
},
{
"version": "0.0.0",
"date": "2025-10-11",
Expand Down
137 changes: 137 additions & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,59 @@
const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => Array.from(document.querySelectorAll(selector));

const numberFormatter = (() => {
try {
return new Intl.NumberFormat(undefined, {
maximumFractionDigits: 2,
minimumFractionDigits: 0,
});
} catch (_) {
return null;
}
})();

const formatNumber = (value) => {
const numeric = typeof value === 'number' ? value : Number(value);
if (!Number.isFinite(numeric)) return null;
if (numberFormatter) {
try {
return numberFormatter.format(numeric);
} catch (_) {
/* ignore */
}
}
return String(Math.round(numeric * 100) / 100);
};

const formatDays = (value) => {
const formatted = formatNumber(value);
if (!formatted) return null;
const numeric = typeof value === 'number' ? value : Number(value);
const unit = Math.abs(numeric) === 1 ? 'day' : 'days';
return `${formatted} ${unit}`;
};

const formatList = (items) => {
const filtered = items
.map((item) => (typeof item === 'string' ? item.trim() : ''))
.filter(Boolean);
if (!filtered.length) return '';
if (typeof Intl !== 'undefined' && typeof Intl.ListFormat === 'function') {
try {
const formatter = new Intl.ListFormat(undefined, {
style: 'long',
type: 'conjunction',
});
return formatter.format(filtered);
} catch (_) {
/* ignore */
}
}
if (filtered.length === 1) return filtered[0];
if (filtered.length === 2) return `${filtered[0]} and ${filtered[1]}`;
return `${filtered.slice(0, -1).join(', ')}, and ${filtered[filtered.length - 1]}`;
};

const safeSet = (key, value) => {
try {
localStorage.setItem(key, value);
Expand All @@ -34,6 +87,89 @@

let welcomeHiddenState = false;

function generateLeaveSummary(payload = {}) {
const breakdown = Array.isArray(payload.breakdown) ? payload.breakdown : [];
const normalized = breakdown
.map((entry) => {
const label = typeof entry?.label === 'string' ? entry.label.trim() : '';
if (!label) return null;
const rawDays = entry?.days ?? entry?.value ?? entry?.amount ?? 0;
const days = Number(rawDays);
return {
label,
days: Number.isFinite(days) ? days : 0,
};
})
.filter(Boolean);

const zeroCategories = normalized.filter((entry) => !(entry.days > 0));
const positiveCategories = normalized.filter((entry) => entry.days > 0);
const sentences = [];

if (zeroCategories.length) {
const zeroLabels = zeroCategories.map((entry) => entry.label.toLowerCase());
const zeroList = formatList(zeroLabels);
if (zeroList) sentences.push(`No ${zeroList} days.`);
}

if (positiveCategories.length) {
const positiveParts = positiveCategories
.map((entry) => {
const daysText = formatDays(entry.days);
if (!daysText) return '';
return `${daysText} of ${entry.label.toLowerCase()}`;
})
.filter(Boolean);
const positiveList = formatList(positiveParts);
if (positiveList) sentences.push(`${positiveList}.`);
}

const totalParts = [];
const totalDaysRaw = payload.totalDays ?? payload.total ?? payload.days;
const totalDaysNumeric = Number(totalDaysRaw);
const totalDays = Number.isFinite(totalDaysNumeric)
? totalDaysNumeric
: positiveCategories.reduce((sum, entry) => sum + entry.days, 0);
if (Number.isFinite(totalDays) && totalDays > 0) {
const daysText = formatDays(totalDays);
if (daysText) totalParts.push(daysText);
}

const hoursRaw = payload.totalHours ?? payload.hours ?? payload.totalHoursWorked;
const totalHours = Number(hoursRaw);
if (Number.isFinite(totalHours) && totalHours > 0) {
const hoursText = formatNumber(totalHours);
if (hoursText) totalParts.push(`${hoursText} hours`);
}

if (totalParts.length) {
const totalList = formatList(totalParts);
if (totalList) sentences.push(`Overall: ${totalList}.`);
}

return sentences.join(' ');
}

function initializeLeaveSummaries() {
const targets = document.querySelectorAll('[data-leave-summary]');
if (!targets.length) return;
targets.forEach((node) => {
const raw = node.getAttribute('data-leave-summary');
if (!raw) return;
let payload = null;
try {
payload = JSON.parse(raw);
} catch (error) {
console.error('Unable to parse leave summary payload', error);
return;
}
const text = generateLeaveSummary(payload);
if (text) {
node.textContent = text;
}
});
}

function applyDarkMode(enabled, { persist = true, withTransition = false } = {}) {
const shouldEnable = !!enabled;
if (withTransition) root.classList.add('theme-transition');
Expand Down Expand Up @@ -802,6 +938,7 @@
}
});

initializeLeaveSummaries();
initializeCollapsibles();
renderChangelog();
updateVersionDisplay();
Expand Down
2 changes: 1 addition & 1 deletion assets/styles.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/version.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "0.0.0"
"version": "1.1.35"
}
56 changes: 40 additions & 16 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,23 @@ <h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">
</div>

<nav class="w-full">
<button class="nav-btn active-nav-button" data-target="welcome" data-first-time>
<span class="flex items-center">
<i class="fa-solid fa-circle-info h-5 w-5 mr-3"></i>
Welcome
</span>
</button>
<button class="nav-btn" data-target="settings">
<span class="flex items-center">
<i class="fa-solid fa-sliders-h h-5 w-5 mr-3"></i>
Settings
</span>
<button class="nav-btn active-nav-button" data-target="welcome" data-first-time>
<span class="flex items-center">
<i class="fa-solid fa-circle-info h-5 w-5 mr-3"></i>
Welcome
</span>
</button>
<button class="nav-btn" data-target="fourDayWeek">
<span class="flex items-center">
<i class="fa-solid fa-calendar-check h-5 w-5 mr-3"></i>
4 Day Week
</span>
</button>
<button class="nav-btn" data-target="settings">
<span class="flex items-center">
<i class="fa-solid fa-sliders-h h-5 w-5 mr-3"></i>
Settings
</span>
</button>
</nav>
</div>
Expand Down Expand Up @@ -193,11 +199,29 @@ <h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">
This simplified build focuses on the core PWA experience so you can install PWA Template, manage its theme, and follow future progress through the built-in changelog.
</p>
</div>
</div>

<div id="settings" class="content-section">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-6">
Settings
</div>

<div id="fourDayWeek" class="content-section">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-6">
4 Day Week
</h2>
<div class="card">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">
Leave summary
</h3>
<p
class="text-gray-700 dark:text-gray-300"
data-leave-summary='{"breakdown":[{"label":"Core annual leave","days":0},{"label":"Long service leave","days":0},{"label":"Carry over leave","days":0},{"label":"Purchased leave","days":0},{"label":"Bank holidays","days":3}],"totalDays":3,"totalHours":22.2}'
></p>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-3">
Example output using the current policy values.
</p>
</div>
</div>

<div id="settings" class="content-section">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-6">
Settings
</h2>
<div
class="card is-collapsible"
Expand Down
2 changes: 1 addition & 1 deletion service-worker.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const APP_VERSION = "0.0.0";
const APP_VERSION = "1.1.35";
const CACHE_VERSION = APP_VERSION && APP_VERSION.endsWith("-dev")
? "dev"
: APP_VERSION;
Expand Down