mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-10-30 02:38:47 -04:00
The planting page's layout was planting-specific CSS. It is now a shared .record-page / .record-panel in _record_page.scss, and crops#show uses it: the crop's own data inside one border, the sidebar outside, sections as bordered blocks with a heading bar as the lid. Along the way a duplicate section rule and two copies of the kebab button styling collapsed into one each. Crop page: - Scientific and alternate names sit under the crop's name, where they belong, instead of having a sidebar card of their own. Wranglers add and edit them from the actions menu, in a dialog; the loose "Add" buttons in the page body are gone. - The actions menu is the same three-dot kebab as plantings, in the top right of the hero. The "you are a CROP WRANGLER" banner is gone: seeing the menu already tells you that. Menu items have no icons now, except delete, which keeps its icon and its red. - Growing facts were split three ways — prose in the hero, prediction cards in the main column, an OpenFarm list in the sidebar — and the hero prose was the same fields as the OpenFarm ones. One "Growing X" section of fact cards now holds all of it. - The sidebar listed planting, harvest and seed counts directly above three sections with those same names. The counts are headline numbers in the hero instead, which also fills the space the hero was wasting. - Main column reads top to bottom: how to grow it, how members grow it, varieties, companions, photos, video, where it grows, posts, history. Headings say something rather than naming a partial. - Crop actions are just "Add to my garden". Recording a harvest, saving seeds, posting and marking a planting failed all belong to a planting you already have. - The charts section rendered as an empty white strip whenever a crop had any plantings or harvests but no chart to draw. - The hero photo moved to the sidebar, where it has room. Also: the whimsical icons perch on the heading bar below them rather than sitting inside the box, the garden picker dialog gives every row the same shape so the names line up, and crops/_grown_for.html.haml is deleted — nothing rendered it once its content moved into the growing cards. Specs are not updated yet: crop_detail_page_spec clicks "Record harvest" on this page, which no longer exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
125 lines
4.6 KiB
React
125 lines
4.6 KiB
React
import React, {useEffect, useRef, useState} from 'react';
|
|
|
|
import ActionsMenu from './ActionsMenu';
|
|
import AddPhotoModal from './AddPhotoModal';
|
|
import EditPlantingModal from './EditPlantingModal';
|
|
import MarkFinishedModal from './MarkFinishedModal';
|
|
import RecordHarvestModal from './RecordHarvestModal';
|
|
import SaveSeedsModal from './SaveSeedsModal';
|
|
|
|
// The menu items we open a dialog for. Anything else — delete, and whatever is
|
|
// added later — keeps its link, its data-method and its data-confirm.
|
|
const DIALOGS = ['edit', 'harvest', 'seeds', 'finish', 'photo'];
|
|
|
|
// Transplanting is a server-rendered Bootstrap dialog, already on the page but
|
|
// hidden. Its form posts as it always has, so it needs no JSON endpoint.
|
|
function showTransplantDialog() {
|
|
const dialog = document.getElementById('transplant-modal');
|
|
if (dialog && window.bootstrap) window.bootstrap.Modal.getOrCreateInstance(dialog).show();
|
|
}
|
|
|
|
// The quiet "add" on each section heading opens the same dialog as the matching
|
|
// menu item. Those links are server-rendered, outside this island, so we listen
|
|
// for them on the document; their href stays as the fallback without
|
|
// JavaScript. The handler is held in a ref so the listener is attached once
|
|
// rather than on every render.
|
|
function useSectionTriggers(onTrigger) {
|
|
const latest = useRef(onTrigger);
|
|
latest.current = onTrigger;
|
|
|
|
useEffect(() => {
|
|
function onClick(event) {
|
|
const trigger = event.target.closest('[data-planting-dialog]');
|
|
if (!trigger) return;
|
|
event.preventDefault();
|
|
latest.current(trigger.dataset.plantingDialog, trigger.getAttribute('href'));
|
|
}
|
|
|
|
document.addEventListener('click', onClick);
|
|
return () => document.removeEventListener('click', onClick);
|
|
}, []);
|
|
}
|
|
|
|
// The one actions menu for a planting's own page: the three-dot button in the
|
|
// top right of the hero, and the dialogs its items open.
|
|
//
|
|
// The items come from the server (label, href, and for non-GET links `method`
|
|
// and `confirm`), so permissions and wording stay in one place.
|
|
//
|
|
// Saving reloads the page, because the sections below (facts, harvests, seeds,
|
|
// photos) are server-rendered and there is no JSON for them yet. The member
|
|
// stays on the planting either way, which is the point; re-rendering only the
|
|
// affected section is tracked separately.
|
|
export default function PlantingActions({
|
|
planting, actions,
|
|
harvest_icon_url: harvestIconUrl, seed_icon_url: seedIconUrl,
|
|
photo_icon_url: photoIconUrl, finish_icon_url: finishIconUrl
|
|
}) {
|
|
// {kind, href}: which dialog is open, and the link it was opened from, which
|
|
// the add-photo dialog needs.
|
|
const [dialog, setDialog] = useState(null);
|
|
|
|
const open = (kind, href) => setDialog({kind, href});
|
|
const close = () => setDialog(null);
|
|
|
|
// Close the dialog, then reload on the next tick. Reloading straight from the
|
|
// save callback tears the page down while the dialog is still finishing its
|
|
// own update, which leaves the browser wedged mid-navigation.
|
|
const saved = () => {
|
|
setDialog(null);
|
|
setTimeout(() => window.location.reload(), 0);
|
|
};
|
|
|
|
useSectionTriggers(open);
|
|
|
|
// After the hooks: React counts them, and an early return above would change
|
|
// the count between renders.
|
|
if (!actions || actions.length === 0) return null;
|
|
|
|
function choose(action, event) {
|
|
if (action.key === 'transplant') {
|
|
event.preventDefault();
|
|
showTransplantDialog();
|
|
} else if (DIALOGS.includes(action.key)) {
|
|
event.preventDefault();
|
|
open(action.key, action.href);
|
|
}
|
|
}
|
|
|
|
const kind = dialog && dialog.kind;
|
|
|
|
return (
|
|
<>
|
|
<ActionsMenu
|
|
id={planting.id}
|
|
actions={actions}
|
|
className="btn record-actions-toggle"
|
|
ariaLabel="Actions"
|
|
label={<i className="fas fa-ellipsis-v" aria-hidden="true" title="Actions" />}
|
|
onSelect={choose}
|
|
/>
|
|
{kind === 'edit' && (
|
|
<EditPlantingModal planting={planting} onClose={close} onSaved={saved} />
|
|
)}
|
|
{kind === 'harvest' && (
|
|
<RecordHarvestModal planting={planting} iconUrl={harvestIconUrl} onClose={close} onSaved={saved} />
|
|
)}
|
|
{kind === 'seeds' && (
|
|
<SaveSeedsModal planting={planting} iconUrl={seedIconUrl} onClose={close} onSaved={saved} />
|
|
)}
|
|
{kind === 'finish' && (
|
|
<MarkFinishedModal planting={planting} iconUrl={finishIconUrl} onClose={close} onSaved={saved} />
|
|
)}
|
|
{kind === 'photo' && (
|
|
<AddPhotoModal
|
|
label={`${planting.crop.name} planting`}
|
|
newUrl={dialog.href}
|
|
iconUrl={photoIconUrl}
|
|
onClose={close}
|
|
onAdded={saved}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|