mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-11-03 11:44:17 -05:00
145 lines
6.6 KiB
React
145 lines
6.6 KiB
React
import React, {useEffect, useState} from 'react';
|
|
|
|
import {getJson, patchJson, postJson} from '../api';
|
|
import {errorMessages} from '../errors';
|
|
import Modal from './Modal';
|
|
|
|
// The same fields as the garden form page, in a dialog over the page. Given a
|
|
// garden it edits it: loads the current values from /gardens/:slug/edit.json
|
|
// and saves with PATCH. Given none it adds one: loads /gardens/new.json and
|
|
// saves with POST. Either way it hands the garden's card to onSaved, so a list
|
|
// updates in place and the page is never left.
|
|
export default function GardenFormModal({garden = null, onClose, onSaved}) {
|
|
const isNew = !garden;
|
|
const [form, setForm] = useState(null); // {garden, area_units, garden_types, location_help, ...}
|
|
const [values, setValues] = useState(null);
|
|
const [loadError, setLoadError] = useState(false);
|
|
const [saving, setSaving] = useState(false);
|
|
const [errors, setErrors] = useState([]);
|
|
|
|
useEffect(() => {
|
|
const controller = new AbortController();
|
|
(async () => {
|
|
try {
|
|
const {ok, data} = await getJson(isNew ? '/gardens/new.json' : `${garden.url}/edit.json`, {signal: controller.signal});
|
|
if (!ok) throw new Error('load failed');
|
|
setForm(data);
|
|
setValues(data.garden);
|
|
} catch (error) {
|
|
if (error.name !== 'AbortError') setLoadError(true);
|
|
}
|
|
})();
|
|
return () => controller.abort();
|
|
}, [isNew, garden && garden.url]);
|
|
|
|
const set = (field) => (event) => {
|
|
const {type, checked, value} = event.target;
|
|
setValues((current) => ({...current, [field]: type === 'checkbox' ? checked : value}));
|
|
};
|
|
|
|
async function save(event) {
|
|
event.preventDefault();
|
|
setSaving(true);
|
|
setErrors([]);
|
|
const {name, description, area, area_unit: areaUnit, garden_type_id: gardenTypeId, active} = values;
|
|
try {
|
|
const body = {garden: {name, description, area, area_unit: areaUnit, garden_type_id: gardenTypeId, active}};
|
|
const {ok, status, data} = isNew ? await postJson('/gardens.json', body) : await patchJson(garden.url, body);
|
|
if (ok) {
|
|
onSaved(data.garden); // closes this dialog
|
|
return;
|
|
}
|
|
setErrors(errorMessages(status, data, {signedOut: 'Please sign in again to save this garden.', forbidden: 'You can\'t change this garden.'}));
|
|
} catch (error) {
|
|
setErrors(['Couldn\'t reach the server. Please try again.']);
|
|
}
|
|
setSaving(false);
|
|
}
|
|
|
|
return (
|
|
<Modal title={isNew ? 'New garden' : `Edit ${garden.name}`} titleId={`garden-form-title-${isNew ? 'new' : garden.id}`} onClose={onClose}>
|
|
{loadError && (
|
|
<div className="modal-body">
|
|
<div className="alert alert-danger mb-0" role="alert">Couldn’t load this garden. Please close this and try again.</div>
|
|
</div>
|
|
)}
|
|
{!loadError && !values && (
|
|
<div className="modal-body" role="status"><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
|
|
)}
|
|
{values && (
|
|
<form onSubmit={save}>
|
|
<div className="modal-body edit-garden-body">
|
|
{errors.length > 0 && (
|
|
<div className="alert alert-danger" role="alert">
|
|
<strong>That didn’t save.</strong>
|
|
<ul className="mb-0">{errors.map((message) => <li key={message}>{message}</li>)}</ul>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mb-3">
|
|
<label className="form-label" htmlFor="edit-garden-name">Name</label>
|
|
<input id="edit-garden-name" type="text" className="form-control" maxLength="255" required value={values.name || ''} onChange={set('name')} />
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<label className="form-label" htmlFor="edit-garden-description">Description</label>
|
|
<textarea
|
|
id="edit-garden-description"
|
|
className="form-control"
|
|
rows="4"
|
|
placeholder="Where is it? What does it look like? Do you have irrigation? What are your plans?"
|
|
value={values.description || ''}
|
|
onChange={set('description')}
|
|
/>
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<span className="form-label d-block">Location</span>
|
|
<p className="mb-0">
|
|
{values.location ? <strong>{values.location}</strong> : 'Not set'}{' '}
|
|
{values.url && <a href={`${values.url}/edit`}>Change or remove</a>}
|
|
</p>
|
|
<div className="form-text">{form.location_help}</div>
|
|
</div>
|
|
|
|
<div className="row g-3 mb-3">
|
|
<div className="col-md-5">
|
|
<label className="form-label" htmlFor="edit-garden-area">Area</label>
|
|
<input id="edit-garden-area" type="number" step="any" min="0" className="form-control" value={values.area ?? ''} onChange={set('area')} />
|
|
</div>
|
|
<div className="col-md-7">
|
|
<label className="form-label" htmlFor="edit-garden-area-unit">Area unit</label>
|
|
<select id="edit-garden-area-unit" className="form-select" value={values.area_unit || ''} onChange={set('area_unit')}>
|
|
{form.area_units.map((unit) => <option key={unit.value} value={unit.value}>{unit.label}</option>)}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<label className="form-label" htmlFor="edit-garden-type">Garden type</label>
|
|
<select id="edit-garden-type" className="form-select" value={values.garden_type_id ?? ''} onChange={set('garden_type_id')}>
|
|
<option value="" />
|
|
{form.garden_types.map((type) => <option key={type.id} value={type.id}>{type.name}</option>)}
|
|
</select>
|
|
</div>
|
|
|
|
<div className="form-check">
|
|
<input id="edit-garden-active" type="checkbox" className="form-check-input" checked={!!values.active} onChange={set('active')} />
|
|
<label className="form-check-label" htmlFor="edit-garden-active">Active?</label>
|
|
<div className="form-text">
|
|
You can mark a garden as inactive if you no longer use it.
|
|
Note: this will mark all plantings in the garden as “finished”.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="modal-footer">
|
|
<button type="submit" className="btn btn-success btn-plant" disabled={saving} aria-busy={saving}>
|
|
{saving ? <><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Save garden'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
)}
|
|
</Modal>
|
|
);
|
|
}
|