v0.11.6 added event form rules.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jama-backend",
|
"name": "jama-backend",
|
||||||
"version": "0.11.5",
|
"version": "0.11.6",
|
||||||
"description": "TeamChat backend server",
|
"description": "TeamChat backend server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -13,7 +13,7 @@
|
|||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="${1:-0.11.5}"
|
VERSION="${1:-0.11.6}"
|
||||||
ACTION="${2:-}"
|
ACTION="${2:-}"
|
||||||
REGISTRY="${REGISTRY:-}"
|
REGISTRY="${REGISTRY:-}"
|
||||||
IMAGE_NAME="jama"
|
IMAGE_NAME="jama"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jama-frontend",
|
"name": "jama-frontend",
|
||||||
"version": "0.11.5",
|
"version": "0.11.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
|
|||||||
: null;
|
: null;
|
||||||
// Track previous typeId so we can detect a type change vs start time change
|
// Track previous typeId so we can detect a type change vs start time change
|
||||||
const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : '');
|
const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : '');
|
||||||
|
const mountedRef = useRef(false);
|
||||||
const [allDay, setAllDay] = useState(!!event?.all_day);
|
const [allDay, setAllDay] = useState(!!event?.all_day);
|
||||||
const [track, setTrack] = useState(!!event?.track_availability);
|
const [track, setTrack] = useState(!!event?.track_availability);
|
||||||
const [isPrivate, setIsPrivate] = useState(event ? !event.is_public : false);
|
const [isPrivate, setIsPrivate] = useState(event ? !event.is_public : false);
|
||||||
@@ -262,13 +263,13 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
|
|||||||
finally { setSavingType(false); }
|
finally { setSavingType(false); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auto-calculate end date/time when start date, start time, or event type changes.
|
// Mark mounted after first render
|
||||||
// Rules:
|
useEffect(() => { mountedRef.current = true; }, []);
|
||||||
// - New event: use eventType duration (default 1hr)
|
|
||||||
// - Editing + type changed: use new eventType duration
|
// Auto-calculate end date/time ONLY when start date, start time, or type actually changes.
|
||||||
// - Editing + type same: use saved event duration (preserve original length)
|
// Skips initial mount so edit mode fields are never overwritten on open.
|
||||||
// - Always: if end < start, advance end date by 1 day (overnight events)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if(!mountedRef.current) return; // skip initial mount — never auto-change on open
|
||||||
if(!sd||!st) return;
|
if(!sd||!st) return;
|
||||||
const start = buildISO(sd,st);
|
const start = buildISO(sd,st);
|
||||||
if(!start) return;
|
if(!start) return;
|
||||||
@@ -278,11 +279,11 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
|
|||||||
|
|
||||||
let durMins;
|
let durMins;
|
||||||
if(!event || typeChanged) {
|
if(!event || typeChanged) {
|
||||||
// New event or type change: use eventType duration
|
// New event or explicit type change: use eventType duration
|
||||||
const typ = localTypes.find(t=>t.id===Number(typeId));
|
const typ = localTypes.find(t=>t.id===Number(typeId));
|
||||||
durMins = (typ?.default_duration_hrs||1) * 60;
|
durMins = (typ?.default_duration_hrs||1) * 60;
|
||||||
} else {
|
} else {
|
||||||
// Editing with same type: preserve the saved event duration
|
// Editing start date/time with same type: preserve saved duration
|
||||||
durMins = savedDurMins || 60;
|
durMins = savedDurMins || 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,9 +179,14 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|||||||
? (new Date(event.end_at) - new Date(event.start_at)) / 60000
|
? (new Date(event.end_at) - new Date(event.start_at)) / 60000
|
||||||
: null;
|
: null;
|
||||||
const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : '');
|
const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : '');
|
||||||
|
const mountedRef = useRef(false);
|
||||||
|
|
||||||
// Auto-update end time when event type or start time changes
|
// Mark mounted after first render so effects skip initial fire
|
||||||
|
useEffect(() => { mountedRef.current = true; }, []);
|
||||||
|
|
||||||
|
// Auto-update end time only when type, start date, or start time actually changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!mountedRef.current) return; // skip initial mount
|
||||||
if (!startDate || !startTime) return;
|
if (!startDate || !startTime) return;
|
||||||
const et = localEventTypes.find(t => t.id === Number(eventTypeId));
|
const et = localEventTypes.find(t => t.id === Number(eventTypeId));
|
||||||
const start = buildISO(startDate, startTime);
|
const start = buildISO(startDate, startTime);
|
||||||
@@ -189,12 +194,12 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|||||||
const typeChanged = String(eventTypeId) !== prevTypeIdRef.current;
|
const typeChanged = String(eventTypeId) !== prevTypeIdRef.current;
|
||||||
prevTypeIdRef.current = String(eventTypeId);
|
prevTypeIdRef.current = String(eventTypeId);
|
||||||
if (!event || typeChanged) {
|
if (!event || typeChanged) {
|
||||||
// New event or type change: apply eventType duration
|
// New event or explicit type change: apply eventType duration
|
||||||
const dur = et?.default_duration_hrs || 1;
|
const dur = et?.default_duration_hrs || 1;
|
||||||
setEndDate(toLocalDateInput(addHours(start, dur)));
|
setEndDate(toLocalDateInput(addHours(start, dur)));
|
||||||
setEndTime(toLocalTimeInput(addHours(start, dur)));
|
setEndTime(toLocalTimeInput(addHours(start, dur)));
|
||||||
} else {
|
} else {
|
||||||
// Editing with same type: preserve saved duration
|
// Editing start date/time with same type: preserve saved duration
|
||||||
const durMins = savedDurMins || 60;
|
const durMins = savedDurMins || 60;
|
||||||
setEndDate(toLocalDateInput(addHours(start, durMins/60)));
|
setEndDate(toLocalDateInput(addHours(start, durMins/60)));
|
||||||
setEndTime(toLocalTimeInput(addHours(start, durMins/60)));
|
setEndTime(toLocalTimeInput(addHours(start, durMins/60)));
|
||||||
|
|||||||
@@ -370,12 +370,14 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|||||||
? (new Date(event.end_at) - new Date(event.start_at)) / 60000
|
? (new Date(event.end_at) - new Date(event.start_at)) / 60000
|
||||||
: null;
|
: null;
|
||||||
const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : '');
|
const prevTypeIdRef = useRef(event?.event_type_id ? String(event.event_type_id) : '');
|
||||||
|
const mountedRef = useRef(false); // skip all auto-calc effects on initial mount
|
||||||
|
|
||||||
// When event type changes:
|
// When event type changes:
|
||||||
// - Creating: always apply the type's duration to compute end time
|
// - Creating: always apply the type's duration to compute end time
|
||||||
// - Editing: only apply duration if the type HAS a defined duration
|
// - Editing: only apply duration if the type HAS a defined duration
|
||||||
// (if no duration on type, keep existing saved end time)
|
// (if no duration on type, keep existing saved end time)
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
|
if(!mountedRef.current) return; // skip on initial mount
|
||||||
if(!sd||!st) return;
|
if(!sd||!st) return;
|
||||||
const typ=localTypes.find(t=>t.id===Number(typeId));
|
const typ=localTypes.find(t=>t.id===Number(typeId));
|
||||||
const start=buildISO(sd,st);
|
const start=buildISO(sd,st);
|
||||||
@@ -383,37 +385,40 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
|
|||||||
const typeChanged = typeId !== prevTypeIdRef.current;
|
const typeChanged = typeId !== prevTypeIdRef.current;
|
||||||
prevTypeIdRef.current = String(typeId);
|
prevTypeIdRef.current = String(typeId);
|
||||||
if(!event || typeChanged) {
|
if(!event || typeChanged) {
|
||||||
// New event or type change: apply eventType duration
|
// New event or type change only: apply eventType duration
|
||||||
const dur=typ?.default_duration_hrs||1;
|
const dur=typ?.default_duration_hrs||1;
|
||||||
const endIso=addHours(start,dur);
|
const endIso=addHours(start,dur);
|
||||||
setEd(toDateIn(endIso)); setEt(toTimeIn(endIso));
|
setEd(toDateIn(endIso)); setEt(toTimeIn(endIso));
|
||||||
userSetEndTime.current = false;
|
userSetEndTime.current = false;
|
||||||
}
|
}
|
||||||
// Editing with same type: do not override end time here (start-time effect handles it)
|
|
||||||
if(typ?.default_user_group_id&&!event) setGrps(prev=>new Set([...prev,Number(typ.default_user_group_id)]));
|
if(typ?.default_user_group_id&&!event) setGrps(prev=>new Set([...prev,Number(typ.default_user_group_id)]));
|
||||||
},[typeId]);
|
},[typeId]);
|
||||||
|
|
||||||
// When start date changes: match end date (both modes) unless user set it manually
|
// When start date changes: recalculate end preserving duration
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
if(!userSetEndTime.current) setEd(sd);
|
if(!mountedRef.current) return;
|
||||||
|
if(!sd||!st) return;
|
||||||
|
const start=buildISO(sd,st);
|
||||||
|
if(!start) return;
|
||||||
|
const durMins = (event && savedDurMins) ? savedDurMins : (localTypes.find(t=>t.id===Number(typeId))?.default_duration_hrs||1)*60;
|
||||||
|
const endIso=addHours(start,durMins/60);
|
||||||
|
setEd(toDateIn(endIso)); setEt(toTimeIn(endIso));
|
||||||
},[sd]);
|
},[sd]);
|
||||||
|
|
||||||
// When start time changes: recompute end preserving duration
|
// When start time changes: recompute end preserving duration
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
|
if(!mountedRef.current) return;
|
||||||
if(!sd||!st) return;
|
if(!sd||!st) return;
|
||||||
if(userSetEndTime.current && event) {
|
|
||||||
// Editing with user-preserved end: maintain saved duration
|
|
||||||
const durMins = savedDurMins || 60;
|
|
||||||
const start=buildISO(sd,st);
|
|
||||||
if(start){ setEd(toDateIn(addHours(start,durMins/60))); setEt(toTimeIn(addHours(start,durMins/60))); }
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const typ=localTypes.find(t=>t.id===Number(typeId));
|
|
||||||
const dur=typ?.default_duration_hrs||1;
|
|
||||||
const start=buildISO(sd,st);
|
const start=buildISO(sd,st);
|
||||||
if(start){ setEd(toDateIn(addHours(start,dur))); setEt(toTimeIn(addHours(start,dur))); }
|
if(!start) return;
|
||||||
|
const durMins = (event && savedDurMins) ? savedDurMins : (localTypes.find(t=>t.id===Number(typeId))?.default_duration_hrs||1)*60;
|
||||||
|
setEd(toDateIn(addHours(start,durMins/60)));
|
||||||
|
setEt(toTimeIn(addHours(start,durMins/60)));
|
||||||
},[st]);
|
},[st]);
|
||||||
|
|
||||||
|
// Mark mounted after all effects have registered — effects skip on initial render
|
||||||
|
useEffect(()=>{ mountedRef.current = true; },[]);
|
||||||
|
|
||||||
const toggleGrp=id=>setGrps(prev=>{const n=new Set(prev);n.has(id)?n.delete(id):n.add(id);return n;});
|
const toggleGrp=id=>setGrps(prev=>{const n=new Set(prev);n.has(id)?n.delete(id):n.add(id);return n;});
|
||||||
const groupsRequired=track; // when tracking, groups are required
|
const groupsRequired=track; // when tracking, groups are required
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user