More changes than expected. Mobile version improvements & PWA
Build & Push Docker Images / test-backend (push) Successful in 44s
Build & Push Docker Images / test-frontend (push) Successful in 52s
Build & Push Docker Images / build-backend (push) Failing after 34s
Build & Push Docker Images / build-frontend (push) Failing after 30s
Build & Push Docker Images / test-backend (push) Successful in 44s
Build & Push Docker Images / test-frontend (push) Successful in 52s
Build & Push Docker Images / build-backend (push) Failing after 34s
Build & Push Docker Images / build-frontend (push) Failing after 30s
This commit is contained in:
@@ -1,5 +1,47 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import './AdminComponents.css';
|
||||
import { DAY_KEYS, DAY_LABEL } from '../../utils/hours';
|
||||
|
||||
function emptyHoursDraft() {
|
||||
const draft = {};
|
||||
for (const day of DAY_KEYS) {
|
||||
draft[day] = { open: '09:00', close: '21:00', closed: true };
|
||||
}
|
||||
return draft;
|
||||
}
|
||||
|
||||
function hoursToDraft(raw) {
|
||||
let parsed = null;
|
||||
if (raw) {
|
||||
try { parsed = typeof raw === 'string' ? JSON.parse(raw) : raw; }
|
||||
catch { parsed = null; }
|
||||
}
|
||||
const draft = {};
|
||||
for (const day of DAY_KEYS) {
|
||||
const v = parsed && parsed[day];
|
||||
if (Array.isArray(v) && v.length === 2) {
|
||||
draft[day] = { open: v[0], close: v[1], closed: false };
|
||||
} else {
|
||||
draft[day] = { open: '09:00', close: '21:00', closed: true };
|
||||
}
|
||||
}
|
||||
return draft;
|
||||
}
|
||||
|
||||
function draftToHours(draft) {
|
||||
const out = {};
|
||||
let hasAny = false;
|
||||
for (const day of DAY_KEYS) {
|
||||
const d = draft[day];
|
||||
if (d && !d.closed && d.open && d.close) {
|
||||
out[day] = [d.open, d.close];
|
||||
hasAny = true;
|
||||
} else {
|
||||
out[day] = null;
|
||||
}
|
||||
}
|
||||
return hasAny ? out : null;
|
||||
}
|
||||
|
||||
/** Distance in metres between two WGS84 points */
|
||||
function haversineMeters(lat1, lon1, lat2, lon2) {
|
||||
@@ -59,6 +101,7 @@ function PharmacyManagement() {
|
||||
latitude: '',
|
||||
longitude: '',
|
||||
});
|
||||
const [hoursDraft, setHoursDraft] = useState(() => emptyHoursDraft());
|
||||
|
||||
const [cityQuery, setCityQuery] = useState('');
|
||||
const [cityLookupLoading, setCityLookupLoading] = useState(false);
|
||||
@@ -251,6 +294,7 @@ function PharmacyManagement() {
|
||||
...formData,
|
||||
latitude: formData.latitude ? parseFloat(formData.latitude) : null,
|
||||
longitude: formData.longitude ? parseFloat(formData.longitude) : null,
|
||||
opening_hours: draftToHours(hoursDraft),
|
||||
};
|
||||
|
||||
if (editingPharmacy) {
|
||||
@@ -299,6 +343,7 @@ function PharmacyManagement() {
|
||||
latitude: pharmacy.latitude ?? '',
|
||||
longitude: pharmacy.longitude ?? '',
|
||||
});
|
||||
setHoursDraft(hoursToDraft(pharmacy.opening_hours));
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
@@ -329,10 +374,15 @@ function PharmacyManagement() {
|
||||
latitude: '',
|
||||
longitude: '',
|
||||
});
|
||||
setHoursDraft(emptyHoursDraft());
|
||||
setEditingPharmacy(null);
|
||||
setShowForm(false);
|
||||
};
|
||||
|
||||
const updateDay = (day, patch) => {
|
||||
setHoursDraft((prev) => ({ ...prev, [day]: { ...prev[day], ...patch } }));
|
||||
};
|
||||
|
||||
const onRegionFieldChange = (setter) => (e) => {
|
||||
setRegionPreset('custom');
|
||||
setter(e.target.value);
|
||||
@@ -571,6 +621,42 @@ function PharmacyManagement() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset className="hours-editor">
|
||||
<legend>Opening hours</legend>
|
||||
<p className="hours-editor-hint">Leave a day checked as <em>Closed</em> if the pharmacy doesn't open that day.</p>
|
||||
{DAY_KEYS.map((day) => {
|
||||
const d = hoursDraft[day];
|
||||
return (
|
||||
<div key={day} className={`hours-row ${d.closed ? 'is-closed' : ''}`}>
|
||||
<span className="hours-day">{DAY_LABEL[day]}</span>
|
||||
<label className="hours-closed-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={d.closed}
|
||||
onChange={(e) => updateDay(day, { closed: e.target.checked })}
|
||||
/>
|
||||
Closed
|
||||
</label>
|
||||
<input
|
||||
type="time"
|
||||
value={d.open}
|
||||
disabled={d.closed}
|
||||
onChange={(e) => updateDay(day, { open: e.target.value })}
|
||||
aria-label={`${DAY_LABEL[day]} opens at`}
|
||||
/>
|
||||
<span className="hours-sep">–</span>
|
||||
<input
|
||||
type="time"
|
||||
value={d.close}
|
||||
disabled={d.closed}
|
||||
onChange={(e) => updateDay(day, { close: e.target.value })}
|
||||
aria-label={`${DAY_LABEL[day]} closes at`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</fieldset>
|
||||
|
||||
<div className="form-actions">
|
||||
<button type="submit" className="btn-primary" disabled={saving}>
|
||||
{saving ? 'Saving...' : editingPharmacy ? 'Update' : 'Add'} Pharmacy
|
||||
|
||||
Reference in New Issue
Block a user