This commit is contained in:
gsinghpal
2026-03-09 15:21:22 -04:00
parent a3e85a23ef
commit acd3fc455e
243 changed files with 20459 additions and 4197 deletions

View File

@@ -66,12 +66,23 @@ html.o_dark .fclk-app,
box-sizing: border-box;
}
/* Hide portal navigation */
/* Hide portal navigation and footer */
.fclk-app ~ .o_portal_navbar,
.fclk-app .o_portal_navbar {
display: none !important;
}
.fclk-app ~ footer,
.fclk-app ~ .o_footer,
.fclk-app ~ .o_footer_copyright {
display: none !important;
}
body:has(.fclk-app) footer,
body:has(.fclk-app) .o_footer {
display: none !important;
}
.fclk-container {
max-width: 480px;
margin: 0 auto;
@@ -224,11 +235,12 @@ html.o_dark .fclk-app,
.fclk-timer {
color: var(--fclk-text);
font-size: 52px;
font-size: clamp(32px, 10vw, 52px);
font-weight: 300;
letter-spacing: 4px;
letter-spacing: 2px;
font-variant-numeric: tabular-nums;
font-family: 'SF Mono', 'Fira Code', 'Courier New', monospace;
white-space: nowrap;
}
/* ---- Clock Button ---- */
@@ -282,6 +294,13 @@ html.o_dark .fclk-app,
.fclk-btn-icon {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
.fclk-btn-icon svg[id="fclk-btn-icon-play"] {
transform: translateX(3px);
}
.fclk-btn-ripple {
@@ -1000,14 +1019,15 @@ html.o_dark .fclk-wizard-overlay {
}
/* ---- Responsive ---- */
@media (max-width: 380px) {
.fclk-timer {
font-size: 40px;
}
@media (max-width: 420px) {
.fclk-clock-btn {
width: 100px;
height: 100px;
}
.fclk-clock-btn svg {
width: 36px;
height: 36px;
}
.fclk-stat-value {
font-size: 26px;
}

View File

@@ -24,6 +24,13 @@ export class FusionClockPortal extends Interaction {
this.checkInTime = new Date(this.el.dataset.checkInTime + "Z");
}
// If server didn't provide check-in data, try localStorage
if (!this.isCheckedIn || !this.checkInTime) {
this._restoreState();
} else {
this._saveState();
}
// Load locations
const locDataEl = document.getElementById("fclk-locations-data");
this.locations = [];
@@ -34,9 +41,6 @@ export class FusionClockPortal extends Interaction {
this.selectedLocationId = this.locations[0].id;
}
// Restore localStorage state
this._restoreState();
// Start live clock
this._updateCurrentTime();
this.clockInterval = setInterval(() => this._updateCurrentTime(), 1000);
@@ -44,6 +48,9 @@ export class FusionClockPortal extends Interaction {
// Start timer if checked in
if (this.isCheckedIn && this.checkInTime) {
this._startTimer();
this._updateUIForClockIn({
location_name: this.el.dataset.locationName || "",
});
}
this._updateDateDisplay();
@@ -51,14 +58,16 @@ export class FusionClockPortal extends Interaction {
// Event listeners
this._setupEventListeners();
// Visibility sync
// Visibility sync -- also sync on load to verify with server
this._onVisibilityChange = () => this._syncOnVisibilityChange();
document.addEventListener("visibilitychange", this._onVisibilityChange);
this._syncTimeout = setTimeout(() => this._syncOnVisibilityChange(), 3000);
}
destroy() {
this._stopTimer();
if (this.clockInterval) clearInterval(this.clockInterval);
if (this._syncTimeout) clearTimeout(this._syncTimeout);
if (this._onVisibilityChange) {
document.removeEventListener("visibilitychange", this._onVisibilityChange);
}
@@ -247,6 +256,7 @@ export class FusionClockPortal extends Interaction {
if (result.action === "clock_in") {
this.isCheckedIn = true;
this.checkInTime = new Date(result.check_in + "Z");
this.el.dataset.locationName = result.location_name || "";
this._updateUIForClockIn(result);
this._startTimer();
this._playSound("in");
@@ -483,6 +493,9 @@ export class FusionClockPortal extends Interaction {
if (this.checkInTime) {
localStorage.setItem("fclk_check_in_time", this.checkInTime.toISOString());
}
if (this.el.dataset.locationName) {
localStorage.setItem("fclk_location_name", this.el.dataset.locationName);
}
} catch (e) {}
}
@@ -490,13 +503,21 @@ export class FusionClockPortal extends Interaction {
try {
localStorage.removeItem("fclk_checked_in");
localStorage.removeItem("fclk_check_in_time");
localStorage.removeItem("fclk_location_name");
} catch (e) {}
}
_restoreState() {
try {
if (!this.isCheckedIn && localStorage.getItem("fclk_checked_in") === "true") {
this._clearState();
const wasCheckedIn = localStorage.getItem("fclk_checked_in") === "true";
const savedTime = localStorage.getItem("fclk_check_in_time");
if (wasCheckedIn && savedTime) {
const t = new Date(savedTime);
if (!isNaN(t.getTime()) && (Date.now() - t.getTime()) < 24 * 60 * 60 * 1000) {
this.isCheckedIn = true;
this.checkInTime = t;
this.el.dataset.locationName = localStorage.getItem("fclk_location_name") || "";
}
}
} catch (e) {}
}
@@ -587,6 +608,12 @@ export class FusionClockPortal extends Interaction {
this._updateUIForClockIn({ location_name: result.location_name });
this._startTimer();
this._saveState();
} else if (result.is_checked_in && this.isCheckedIn) {
const serverTime = new Date(result.check_in + "Z");
if (Math.abs(serverTime - this.checkInTime) > 5000) {
this.checkInTime = serverTime;
this._saveState();
}
} else if (!result.is_checked_in && this.isCheckedIn) {
this.isCheckedIn = false;
this._updateUIForClockOut({});
@@ -602,7 +629,9 @@ export class FusionClockPortal extends Interaction {
if (weekEl && result.week_hours !== undefined) {
weekEl.textContent = result.week_hours.toFixed(1) + "h";
}
} catch (e) {}
} catch {
// Server unavailable (restarting) -- keep timer running from localStorage
}
}
}

View File

@@ -76,26 +76,44 @@ export class FusionClockFAB extends Component {
async _fetchStatus() {
try {
const result = await rpc("/fusion_clock/get_status", {});
if (result.error) {
this.state.isDisplayed = false;
return;
}
if (result.error) return;
this.state.isDisplayed = result.enable_clock !== false;
this.state.isCheckedIn = result.is_checked_in;
this.state.locationName = result.location_name || "";
this.state.todayHours = (result.today_hours || 0).toFixed(1);
this.state.weekHours = (result.week_hours || 0).toFixed(1);
if (result.is_checked_in && result.check_in) {
this.state.checkInTime = new Date(result.check_in + "Z");
this._startTimer();
} else {
const serverTime = new Date(result.check_in + "Z");
const wasRunning = this.state.isCheckedIn;
this.state.isCheckedIn = true;
if (!wasRunning || Math.abs(serverTime - this.state.checkInTime) > 5000) {
this.state.checkInTime = serverTime;
this._startTimer();
}
try { localStorage.setItem("fclk_fab_check_in", serverTime.toISOString()); } catch {}
} else if (!result.is_checked_in) {
this.state.isCheckedIn = false;
this.state.checkInTime = null;
this._stopTimer();
this.state.timerDisplay = "00:00:00";
try { localStorage.removeItem("fclk_fab_check_in"); } catch {}
}
} catch {
if (!this.state.isCheckedIn) {
try {
const saved = localStorage.getItem("fclk_fab_check_in");
if (saved) {
const t = new Date(saved);
if (!isNaN(t.getTime()) && (Date.now() - t.getTime()) < 24 * 60 * 60 * 1000) {
this.state.isDisplayed = true;
this.state.isCheckedIn = true;
this.state.checkInTime = t;
this._startTimer();
}
}
} catch {}
}
} catch (e) {
this.state.isDisplayed = false;
}
}