v0.11.3 fixed timezone issue

This commit is contained in:
2026-03-21 00:23:29 -04:00
parent 01a9b97f0d
commit 596fd0f969
6 changed files with 83 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "jama-backend",
"version": "0.11.2",
"version": "0.11.3",
"description": "TeamChat backend server",
"main": "src/index.js",
"scripts": {

View File

@@ -13,7 +13,7 @@
# ─────────────────────────────────────────────────────────────
set -euo pipefail
VERSION="${1:-0.11.2}"
VERSION="${1:-0.11.3}"
ACTION="${2:-}"
REGISTRY="${REGISTRY:-}"
IMAGE_NAME="jama"

View File

@@ -1,6 +1,6 @@
{
"name": "jama-frontend",
"version": "0.11.2",
"version": "0.11.3",
"private": true,
"scripts": {
"dev": "vite",

View File

@@ -20,15 +20,32 @@ const TIME_SLOTS = (() => {
return s;
})();
function toDateIn(iso) { return iso ? iso.slice(0,10) : ''; }
function toDateIn(iso) {
if (!iso) return '';
const d = new Date(iso);
const pad = n => String(n).padStart(2,'0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
}
function toTimeIn(iso) {
if(!iso) return '';
const d=new Date(iso);
const h=String(d.getHours()).padStart(2,'0'), m=d.getMinutes()<30?'00':'30';
if (!iso) return '';
const d = new Date(iso);
const h = String(d.getHours()).padStart(2,'0'), m = d.getMinutes() < 30 ? '00' : '30';
return `${h}:${m}`;
}
function buildISO(d,t) { return d&&t?`${d}T${t}:00`:''; }
function addHours(iso,h){ const d=new Date(iso); d.setMinutes(d.getMinutes()+h*60); const pad=n=>String(n).padStart(2,'0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:00`; }
function buildISO(date, time) {
if (!date || !time) return '';
const d = new Date(`${date}T${time}:00`);
const pad = n => String(n).padStart(2,'0');
const off = -d.getTimezoneOffset();
const sign = off >= 0 ? '+' : '-';
const abs = Math.abs(off);
return `${date}T${time}:00${sign}${pad(Math.floor(abs/60))}:${pad(abs%60)}`;
}
function addHours(iso, h) {
const d = new Date(iso); d.setMinutes(d.getMinutes() + h * 60);
const pad = n => String(n).padStart(2,'0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:00`;
}
function fmtDateDisplay(iso) { if(!iso) return ''; const d=new Date(iso); return `${DAYS[d.getDay()]}, ${SHORT_MONTHS[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`; }
function fmtTimeDisplay(slot) { const f=TIME_SLOTS.find(s=>s.value===slot); return f?f.label:slot; }
@@ -252,7 +269,7 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
if(!title.trim()) return toast('Title required','error');
setSaving(true);
try {
const body = { title:title.trim(), eventTypeId:typeId||null, startAt:allDay?`${sd}T00:00:00`:buildISO(sd,st), endAt:allDay?`${ed}T23:59:59`:buildISO(ed,et), allDay, location, description, isPublic:!isPrivate, trackAvailability:track, userGroupIds:[...groups], recurrenceRule:recRule||null };
const body = { title:title.trim(), eventTypeId:typeId||null, startAt:allDay?buildISO(sd,'00:00'):buildISO(sd,st), endAt:allDay?buildISO(ed,'23:59'):buildISO(ed,et), allDay, location, description, isPublic:!isPrivate, trackAvailability:track, userGroupIds:[...groups], recurrenceRule:recRule||null };
let scope = 'this';
if(event && event.recurrence_rule?.freq) {
const choice = window.confirm('This is a recurring event.\n\nOK = Update this and all future occurrences\nCancel = Update this event only');

View File

@@ -16,13 +16,33 @@ function fmtTime(isoStr) {
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
function fmtTimeRange(start, end) { return `${fmtTime(start)} ${fmtTime(end)}`; }
function toLocalDateInput(isoStr) { return isoStr ? isoStr.slice(0,10) : ''; }
function toLocalTimeInput(isoStr) { return isoStr ? isoStr.slice(11,16) : ''; }
function buildISO(date, time) { return date && time ? `${date}T${time}:00` : ''; }
function toLocalDateInput(isoStr) {
if (!isoStr) return '';
const d = new Date(isoStr);
const pad = n => String(n).padStart(2,'0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
}
function toLocalTimeInput(isoStr) {
if (!isoStr) return '';
const d = new Date(isoStr);
const pad = n => String(n).padStart(2,'0');
return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
function buildISO(date, time) {
if (!date || !time) return '';
const d = new Date(`${date}T${time}:00`);
const pad = n => String(n).padStart(2,'0');
const off = -d.getTimezoneOffset();
const sign = off >= 0 ? '+' : '-';
const abs = Math.abs(off);
return `${date}T${time}:00${sign}${pad(Math.floor(abs/60))}:${pad(abs%60)}`;
}
function addHours(isoStr, hrs) {
const d = new Date(isoStr);
d.setMinutes(d.getMinutes() + hrs * 60);
return d.toISOString().slice(0,19);
const pad = n => String(n).padStart(2,'0');
// Return local datetime string — do NOT use toISOString() which shifts to UTC
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:00`;
}
function sameDay(a, b) {
return a.getFullYear()===b.getFullYear() && a.getMonth()===b.getMonth() && a.getDate()===b.getDate();
@@ -174,8 +194,8 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
try {
const body = {
title: title.trim(), eventTypeId: eventTypeId || null,
startAt: allDay ? `${startDate}T00:00:00` : buildISO(startDate, startTime),
endAt: allDay ? `${endDate}T23:59:59` : buildISO(endDate, endTime),
startAt: allDay ? buildISO(startDate, '00:00') : buildISO(startDate, startTime),
endAt: allDay ? buildISO(endDate, '23:59') : buildISO(endDate, endTime),
allDay, location, description, isPublic, trackAvailability: trackAvail,
userGroupIds: [...selectedGroups],
};

View File

@@ -16,13 +16,35 @@ const SHORT_MONTHS= ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct'
function fmtDate(d) { return `${d.getDate()} ${SHORT_MONTHS[d.getMonth()]} ${d.getFullYear()}`; }
function fmtTime(iso) { if(!iso) return ''; const d=new Date(iso); return d.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}); }
function fmtRange(s,e) { return `${fmtTime(s)} ${fmtTime(e)}`; }
function toDateIn(iso) { return iso?iso.slice(0,10):''; }
function toTimeIn(iso) { if(!iso) return ''; const d=new Date(iso); const h=String(d.getHours()).padStart(2,'0'), m=d.getMinutes()<30?'00':'30'; return `${h}:${m}`; }
function buildISO(d,t) { return d&&t?`${d}T${t}:00`:''; }
function addHours(iso,h){
const d=new Date(iso); d.setMinutes(d.getMinutes()+h*60);
// Return local datetime string (YYYY-MM-DDTHH:MM:SS) NOT toISOString() which shifts to UTC
const pad=n=>String(n).padStart(2,'0');
// Convert a UTC ISO string (from Postgres TIMESTAMPTZ) to local YYYY-MM-DD for <input type="date">
function toDateIn(iso) {
if (!iso) return '';
const d = new Date(iso);
const pad = n => String(n).padStart(2,'0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
}
// Convert a UTC ISO string to local HH:MM for <input type="time">, snapped to :00 or :30
function toTimeIn(iso) {
if (!iso) return '';
const d = new Date(iso);
const h = String(d.getHours()).padStart(2,'0');
const m = d.getMinutes() < 30 ? '00' : '30';
return `${h}:${m}`;
}
// Build an ISO string with local timezone offset so Postgres stores the right UTC value
function buildISO(date, time) {
if (!date || !time) return '';
// Parse as local datetime then get offset-aware ISO string
const d = new Date(`${date}T${time}:00`);
const pad = n => String(n).padStart(2,'0');
const off = -d.getTimezoneOffset();
const sign = off >= 0 ? '+' : '-';
const abs = Math.abs(off);
return `${date}T${time}:00${sign}${pad(Math.floor(abs/60))}:${pad(abs%60)}`;
}
function addHours(iso, h) {
const d = new Date(iso); d.setMinutes(d.getMinutes() + h * 60);
const pad = n => String(n).padStart(2,'0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:00`;
}
function sameDay(a,b) { return a.getFullYear()===b.getFullYear()&&a.getMonth()===b.getMonth()&&a.getDate()===b.getDate(); }
@@ -396,7 +418,7 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
if(groupsRequired&&grps.size===0) return toast('Select at least one group for availability tracking','error');
setSaving(true);
try{
const body={title:title.trim(),eventTypeId:typeId||null,startAt:allDay?`${sd}T00:00:00`:buildISO(sd,st),endAt:allDay?`${ed}T23:59:59`:buildISO(ed,et),allDay,location:loc,description:desc,isPublic:pub,trackAvailability:track,userGroupIds:[...grps],recurrenceRule:recRule||null};
const body={title:title.trim(),eventTypeId:typeId||null,startAt:allDay?buildISO(sd,'00:00'):buildISO(sd,st),endAt:allDay?buildISO(ed,'23:59'):buildISO(ed,et),allDay,location:loc,description:desc,isPublic:pub,trackAvailability:track,userGroupIds:[...grps],recurrenceRule:recRule||null};
let scope='this';
if(event && event.recurrence_rule?.freq) {
const choice = window.confirm('This is a recurring event.\n\nOK = Update this and all future occurrences\nCancel = Update this event only');