remove enabled state

This commit is contained in:
2026-05-24 13:18:57 +00:00
parent 193940ac17
commit 5edd75a8e9
32 changed files with 201 additions and 447 deletions
-7
View File
@@ -36,7 +36,6 @@ Reference data for where plants live.
| `id` | `int` | Yes | Primary key | | `id` | `int` | Yes | Primary key |
| `name` | `string` | Yes | User-facing location name | | `name` | `string` | Yes | User-facing location name |
| `notes` | `string` | No | Optional details | | `notes` | `string` | No | Optional details |
| `is_enabled` | `bool` | Yes | Whether the location is available for new assignments |
## `CareAction` ## `CareAction`
@@ -47,7 +46,6 @@ A reusable care verb, such as water, prune, fertilize, inspect, or repot.
| `id` | `int` | Yes | Primary key | | `id` | `int` | Yes | Primary key |
| `name` | `string` | Yes | User-facing action name | | `name` | `string` | Yes | User-facing action name |
| `description` | `string` | No | Optional explanation | | `description` | `string` | No | Optional explanation |
| `is_enabled` | `bool` | Yes | Whether the action is available for use |
## `ActionResource` ## `ActionResource`
@@ -57,9 +55,7 @@ A resource used while performing care.
| - | - | - | - | | - | - | - | - |
| `id` | `int` | Yes | Primary key | | `id` | `int` | Yes | Primary key |
| `name` | `string` | Yes | Resource name | | `name` | `string` | Yes | Resource name |
| `category` | `string` | No | Optional grouping, such as `Fertilizer`, `Medium`, `Treatment`, `Equipment`, or `Container` |
| `notes` | `string` | No | Optional details | | `notes` | `string` | No | Optional details |
| `is_enabled` | `bool` | Yes | Whether the resource is available for use |
## `CareActivity` ## `CareActivity`
@@ -70,7 +66,6 @@ A configurable care activity made from one or more care actions.
| `id` | `int` | Yes | Primary key | | `id` | `int` | Yes | Primary key |
| `name` | `string` | Yes | User-facing activity name | | `name` | `string` | Yes | User-facing activity name |
| `notes` | `string` | No | Optional details | | `notes` | `string` | No | Optional details |
| `is_enabled` | `bool` | Yes | Whether the activity is available for schedules and logs |
## `CareActivityAction` ## `CareActivityAction`
@@ -114,7 +109,6 @@ A per-plant recurring schedule for one care activity. Scheduler is the UI owner
| `ends_mode` | `string` | Yes | `on` or `after` | | `ends_mode` | `string` | Yes | `on` or `after` |
| `ends_on` | `date` | No | End date when `ends_mode` is `on` | | `ends_on` | `date` | No | End date when `ends_mode` is `on` |
| `ends_after_occurrences` | `int` | No | Occurrence limit when `ends_mode` is `after` | | `ends_after_occurrences` | `int` | No | Occurrence limit when `ends_mode` is `after` |
| `is_enabled` | `bool` | Yes | Whether this schedule contributes to care tasks |
## `ActionLog` ## `ActionLog`
@@ -150,7 +144,6 @@ A reusable plant flag.
| `id` | `int` | Yes | Primary key | | `id` | `int` | Yes | Primary key |
| `name` | `string` | Yes | Flag name | | `name` | `string` | Yes | Flag name |
| `color` | `string` | Yes | Display color | | `color` | `string` | Yes | Display color |
| `is_enabled` | `bool` | Yes | Whether the flag can be assigned |
## `PlantFlag` ## `PlantFlag`
+114 -44
View File
@@ -167,6 +167,76 @@ export function App() {
} }
} }
async function loadLocations() {
const locationsResponse = await getPlantLocations();
setError(null);
setPlantLocations(locationsResponse);
}
async function loadPlants() {
const plantsResponse = await getPlants();
setError(null);
setPlants(plantsResponse);
}
async function loadPlantsAndCareTasks() {
const [plantsResponse, tasksResponse] = await Promise.all([
getPlants(),
getCareTasks(),
]);
setError(null);
setPlants(plantsResponse);
setCareTasks(tasksResponse);
}
async function loadTaxaAndPlants() {
const [plantsResponse, taxaResponse] = await Promise.all([
getPlants(),
getPlantTaxa(),
]);
setError(null);
setPlants(plantsResponse);
setPlantTaxa(taxaResponse);
}
async function loadFlagsAndPlants() {
const [plantsResponse, flagsResponse] = await Promise.all([
getPlants(),
getPlantFlags(),
]);
setError(null);
setPlants(plantsResponse);
setPlantFlagDefinitions(flagsResponse);
}
async function loadCareModel() {
const [
plantsResponse,
tasksResponse,
actionsResponse,
resourcesResponse,
activitiesResponse,
] = await Promise.all([
getPlants(),
getCareTasks(),
getCareActions(),
getActionResources(),
getCareActivities(),
]);
setError(null);
setPlants(plantsResponse);
setCareTasks(tasksResponse);
setCareActions(actionsResponse);
setActionResources(resourcesResponse);
setCareActivities(activitiesResponse);
}
useEffect(() => { useEffect(() => {
queueMicrotask(() => { queueMicrotask(() => {
void loadDashboard(); void loadDashboard();
@@ -201,15 +271,15 @@ export function App() {
setTaxonForm((current) => ({ ...current, [field]: value })); setTaxonForm((current) => ({ ...current, [field]: value }));
} }
function updateLocationForm(field: keyof LocationFormState, value: string | boolean) { function updateLocationForm(field: keyof LocationFormState, value: string) {
setLocationForm((current) => ({ ...current, [field]: value })); setLocationForm((current) => ({ ...current, [field]: value }));
} }
function updateActionForm(field: keyof ActionFormState, value: string | boolean) { function updateActionForm(field: keyof ActionFormState, value: string) {
setActionForm((current) => ({ ...current, [field]: value })); setActionForm((current) => ({ ...current, [field]: value }));
} }
function updateResourceForm(field: keyof ResourceFormState, value: string | boolean) { function updateResourceForm(field: keyof ResourceFormState, value: string) {
setResourceForm((current) => ({ ...current, [field]: value })); setResourceForm((current) => ({ ...current, [field]: value }));
} }
@@ -217,7 +287,7 @@ export function App() {
setActivityForm((current) => ({ ...current, [field]: value })); setActivityForm((current) => ({ ...current, [field]: value }));
} }
function updateFlagDefinitionForm(field: keyof FlagDefinitionFormState, value: string | boolean) { function updateFlagDefinitionForm(field: keyof FlagDefinitionFormState, value: string) {
setFlagDefinitionForm((current) => ({ ...current, [field]: value })); setFlagDefinitionForm((current) => ({ ...current, [field]: value }));
} }
@@ -227,7 +297,7 @@ export function App() {
function updateBulkScheduleForm( function updateBulkScheduleForm(
field: keyof BulkScheduleFormState, field: keyof BulkScheduleFormState,
value: string | boolean | string[], value: string | string[],
) { ) {
setBulkScheduleForm((current) => ({ ...current, [field]: value })); setBulkScheduleForm((current) => ({ ...current, [field]: value }));
} }
@@ -414,7 +484,7 @@ export function App() {
await updatePlant(editingPlantId, payload); await updatePlant(editingPlantId, payload);
} }
cancelEditing(); cancelEditing();
await loadDashboard(); await loadPlantsAndCareTasks();
} catch { } catch {
setError('Could not save the plant.'); setError('Could not save the plant.');
} finally { } finally {
@@ -438,7 +508,7 @@ export function App() {
locationId: nextValues.locationId === undefined ? selectedPlant.locationId : nextValues.locationId, locationId: nextValues.locationId === undefined ? selectedPlant.locationId : nextValues.locationId,
careSchedules: null, careSchedules: null,
}); });
await loadDashboard(); await loadPlants();
} catch { } catch {
setError('Could not update the plant assignment.'); setError('Could not update the plant assignment.');
} finally { } finally {
@@ -474,7 +544,7 @@ export function App() {
if (editingPlantId === plant.id) { if (editingPlantId === plant.id) {
cancelEditing(); cancelEditing();
} }
await loadDashboard(); await loadPlantsAndCareTasks();
} catch { } catch {
setError('Could not delete the plant.'); setError('Could not delete the plant.');
} finally { } finally {
@@ -497,7 +567,7 @@ export function App() {
await updatePlantTaxon(editingTaxonId, payload); await updatePlantTaxon(editingTaxonId, payload);
} }
cancelEditingTaxon(); cancelEditingTaxon();
await loadDashboard(); await loadTaxaAndPlants();
} catch { } catch {
setError('Could not save the taxon.'); setError('Could not save the taxon.');
} finally { } finally {
@@ -517,7 +587,7 @@ export function App() {
if (editingTaxonId === taxon.id) { if (editingTaxonId === taxon.id) {
cancelEditingTaxon(); cancelEditingTaxon();
} }
await loadDashboard(); await loadTaxaAndPlants();
} catch { } catch {
setError('Could not delete the taxon. It may still be used by a plant.'); setError('Could not delete the taxon. It may still be used by a plant.');
} finally { } finally {
@@ -540,7 +610,7 @@ export function App() {
await updatePlantLocation(editingLocationId, payload); await updatePlantLocation(editingLocationId, payload);
} }
cancelEditingLocation(); cancelEditingLocation();
await loadDashboard(); await loadLocations();
} catch { } catch {
setError('Could not save the location.'); setError('Could not save the location.');
} finally { } finally {
@@ -549,7 +619,7 @@ export function App() {
} }
async function removeLocation(location: PlantLocation) { async function removeLocation(location: PlantLocation) {
const confirmed = window.confirm(`Delete ${location.name}? Locations in use will be disabled instead.`); const confirmed = window.confirm(`Delete ${location.name}? Locations assigned to plants cannot be deleted.`);
if (!confirmed) { if (!confirmed) {
return; return;
} }
@@ -560,10 +630,10 @@ export function App() {
if (editingLocationId === location.id) { if (editingLocationId === location.id) {
cancelEditingLocation(); cancelEditingLocation();
} }
await loadDashboard(); await loadLocations();
} catch { } catch {
await loadDashboard(); await loadLocations();
setError('Could not delete the location. If it is in use, it was disabled instead.'); setError('Could not delete the location. It may still be assigned to a plant.');
} finally { } finally {
setIsSaving(false); setIsSaving(false);
} }
@@ -584,7 +654,7 @@ export function App() {
await updateCareAction(editingActionId, payload); await updateCareAction(editingActionId, payload);
} }
cancelEditingAction(); cancelEditingAction();
await loadDashboard(); await loadCareModel();
} catch { } catch {
setError('Could not save the action.'); setError('Could not save the action.');
} finally { } finally {
@@ -593,7 +663,7 @@ export function App() {
} }
async function removeAction(action: CareAction) { async function removeAction(action: CareAction) {
const confirmed = window.confirm(`Delete ${action.name}? Actions with care history will be disabled instead.`); const confirmed = window.confirm(`Delete ${action.name}? Actions with care history cannot be deleted.`);
if (!confirmed) { if (!confirmed) {
return; return;
} }
@@ -604,10 +674,10 @@ export function App() {
if (editingActionId === action.id) { if (editingActionId === action.id) {
cancelEditingAction(); cancelEditingAction();
} }
await loadDashboard(); await loadCareModel();
} catch { } catch {
await loadDashboard(); await loadCareModel();
setError('Could not delete the action. If it has care history, it was disabled instead.'); setError('Could not delete the action. It may still have care history.');
} finally { } finally {
setIsSaving(false); setIsSaving(false);
} }
@@ -628,7 +698,7 @@ export function App() {
await updateActionResource(editingResourceId, payload); await updateActionResource(editingResourceId, payload);
} }
cancelEditingResource(); cancelEditingResource();
await loadDashboard(); await loadCareModel();
} catch { } catch {
setError('Could not save the resource.'); setError('Could not save the resource.');
} finally { } finally {
@@ -648,7 +718,7 @@ export function App() {
if (editingResourceId === resource.id) { if (editingResourceId === resource.id) {
cancelEditingResource(); cancelEditingResource();
} }
await loadDashboard(); await loadCareModel();
} catch { } catch {
setError('Could not delete the resource.'); setError('Could not delete the resource.');
} finally { } finally {
@@ -671,7 +741,7 @@ export function App() {
await updateCareActivity(editingActivityId, payload); await updateCareActivity(editingActivityId, payload);
} }
cancelEditingActivity(); cancelEditingActivity();
await loadDashboard(); await loadCareModel();
} catch { } catch {
setError('Could not save the activity.'); setError('Could not save the activity.');
} finally { } finally {
@@ -680,7 +750,7 @@ export function App() {
} }
async function removeActivity(activity: CareActivity) { async function removeActivity(activity: CareActivity) {
const confirmed = window.confirm(`Delete ${activity.name}? Activities in use will be disabled instead.`); const confirmed = window.confirm(`Delete ${activity.name}? Activities in use cannot be deleted.`);
if (!confirmed) { if (!confirmed) {
return; return;
} }
@@ -691,10 +761,10 @@ export function App() {
if (editingActivityId === activity.id) { if (editingActivityId === activity.id) {
cancelEditingActivity(); cancelEditingActivity();
} }
await loadDashboard(); await loadCareModel();
} catch { } catch {
await loadDashboard(); await loadCareModel();
setError('Could not delete the activity. If it is in use, it was disabled instead.'); setError('Could not delete the activity. It may still be in use.');
} finally { } finally {
setIsSaving(false); setIsSaving(false);
} }
@@ -715,7 +785,7 @@ export function App() {
await updatePlantFlag(editingFlagDefinitionId, payload); await updatePlantFlag(editingFlagDefinitionId, payload);
} }
cancelEditingFlagDefinition(); cancelEditingFlagDefinition();
await loadDashboard(); await loadFlagsAndPlants();
} catch { } catch {
setError('Could not save the plant flag.'); setError('Could not save the plant flag.');
} finally { } finally {
@@ -733,7 +803,7 @@ export function App() {
try { try {
await savePlantCareSchedulesBulk(toBulkSchedulePayload(bulkScheduleForm)); await savePlantCareSchedulesBulk(toBulkSchedulePayload(bulkScheduleForm));
setBulkScheduleForm(emptyBulkScheduleForm); setBulkScheduleForm(emptyBulkScheduleForm);
await loadDashboard(); await loadPlantsAndCareTasks();
} catch { } catch {
setError('Could not apply the care schedule.'); setError('Could not apply the care schedule.');
} finally { } finally {
@@ -751,7 +821,7 @@ export function App() {
try { try {
await removePlantCareSchedulesBulk(toBulkSchedulePayload(bulkScheduleForm)); await removePlantCareSchedulesBulk(toBulkSchedulePayload(bulkScheduleForm));
setBulkScheduleForm(emptyBulkScheduleForm); setBulkScheduleForm(emptyBulkScheduleForm);
await loadDashboard(); await loadPlantsAndCareTasks();
} catch { } catch {
setError('Could not remove the care schedule.'); setError('Could not remove the care schedule.');
} finally { } finally {
@@ -760,7 +830,7 @@ export function App() {
} }
async function removeFlagDefinition(flag: PlantFlagDefinition) { async function removeFlagDefinition(flag: PlantFlagDefinition) {
const confirmed = window.confirm(`Delete ${flag.name}? Flags assigned to plants will be disabled instead.`); const confirmed = window.confirm(`Delete ${flag.name}? Flags assigned to plants cannot be deleted.`);
if (!confirmed) { if (!confirmed) {
return; return;
} }
@@ -771,10 +841,10 @@ export function App() {
if (editingFlagDefinitionId === flag.id) { if (editingFlagDefinitionId === flag.id) {
cancelEditingFlagDefinition(); cancelEditingFlagDefinition();
} }
await loadDashboard(); await loadFlagsAndPlants();
} catch { } catch {
await loadDashboard(); await loadFlagsAndPlants();
setError('Could not delete the flag. If it is assigned to plants, it was disabled instead.'); setError('Could not delete the flag. It may still be assigned to a plant.');
} finally { } finally {
setIsSaving(false); setIsSaving(false);
} }
@@ -790,7 +860,7 @@ export function App() {
try { try {
await assignPlantFlag(selectedPlantId, toPlantFlagPayload(plantFlagForm)); await assignPlantFlag(selectedPlantId, toPlantFlagPayload(plantFlagForm));
setPlantFlagForm(emptyPlantFlagForm); setPlantFlagForm(emptyPlantFlagForm);
await loadDashboard(); await loadPlants();
} catch { } catch {
setError('Could not attach the plant flag.'); setError('Could not attach the plant flag.');
} finally { } finally {
@@ -806,7 +876,7 @@ export function App() {
setIsSaving(true); setIsSaving(true);
try { try {
await resolvePlantFlag(selectedPlantId, flag.id); await resolvePlantFlag(selectedPlantId, flag.id);
await loadDashboard(); await loadPlants();
} catch { } catch {
setError('Could not resolve the plant flag.'); setError('Could not resolve the plant flag.');
} finally { } finally {
@@ -827,7 +897,7 @@ export function App() {
setIsSaving(true); setIsSaving(true);
try { try {
await removePlantFlagAssignment(selectedPlantId, flag.id); await removePlantFlagAssignment(selectedPlantId, flag.id);
await loadDashboard(); await loadPlants();
} catch { } catch {
setError('Could not remove the plant flag.'); setError('Could not remove the plant flag.');
} finally { } finally {
@@ -845,7 +915,7 @@ export function App() {
notes: '', notes: '',
resources: [], resources: [],
}); });
await loadDashboard(); await loadPlantsAndCareTasks();
} catch { } catch {
setError('Could not log the care task.'); setError('Could not log the care task.');
} finally { } finally {
@@ -874,7 +944,7 @@ export function App() {
notes: '', notes: '',
resources: [], resources: [],
}); });
await loadDashboard(); await loadPlantsAndCareTasks();
} catch { } catch {
setError('Could not log the due tasks.'); setError('Could not log the due tasks.');
} finally { } finally {
@@ -985,11 +1055,11 @@ export function App() {
<section className="catalog-help" aria-label="Catalog status"> <section className="catalog-help" aria-label="Catalog status">
<h3>Catalog Status</h3> <h3>Catalog Status</h3>
<p>{dueCount} due</p> <p>{dueCount} due</p>
<p>{plantLocations.filter((location) => location.isEnabled).length} active locations</p> <p>{plantLocations.length} locations</p>
<p>{careActivities.filter((activity) => activity.isEnabled).length} active activities</p> <p>{careActivities.length} activities</p>
<p>{careActions.filter((action) => action.isEnabled).length} active actions</p> <p>{careActions.length} actions</p>
<p>{actionResources.filter((resource) => resource.isEnabled).length} active resources</p> <p>{actionResources.length} resources</p>
<p>{plantFlagDefinitions.filter((flag) => flag.isEnabled).length} active flags</p> <p>{plantFlagDefinitions.length} flags</p>
</section> </section>
</aside> </aside>
@@ -15,7 +15,7 @@ type ActionsViewProps = {
onCloseDetail: () => void; onCloseDetail: () => void;
onDelete: (action: CareAction) => void; onDelete: (action: CareAction) => void;
onEdit: (action: CareAction) => void; onEdit: (action: CareAction) => void;
onFieldChange: (field: keyof ActionFormState, value: string | boolean) => void; onFieldChange: (field: keyof ActionFormState, value: string) => void;
onNew: () => void; onNew: () => void;
onOpenDetail: (action: CareAction) => void; onOpenDetail: (action: CareAction) => void;
onSave: () => void; onSave: () => void;
@@ -39,15 +39,13 @@ export function ActionsView({
onOpenDetail, onOpenDetail,
onSave, onSave,
}: ActionsViewProps) { }: ActionsViewProps) {
const enabledCount = actions.filter((action) => action.isEnabled).length;
return ( return (
<> <>
<section className="summary-panel" aria-labelledby="actions-summary-heading"> <section className="summary-panel" aria-labelledby="actions-summary-heading">
<div> <div>
<p className="eyebrow">Care menu</p> <p className="eyebrow">Care menu</p>
<h2 id="actions-summary-heading"> <h2 id="actions-summary-heading">
{isLoading ? 'Loading actions' : `${enabledCount} actions enabled`} {isLoading ? 'Loading actions' : `${actions.length} actions`}
</h2> </h2>
<p>{error ?? 'Configure the care actions available when logging plant work.'}</p> <p>{error ?? 'Configure the care actions available when logging plant work.'}</p>
</div> </div>
@@ -78,10 +76,6 @@ export function ActionsView({
<span>Description</span> <span>Description</span>
<strong>{selectedAction.description ?? 'No description'}</strong> <strong>{selectedAction.description ?? 'No description'}</strong>
</div> </div>
<div>
<span>Status</span>
<strong>{selectedAction.isEnabled ? 'Enabled' : 'Disabled'}</strong>
</div>
</div> </div>
</section> </section>
) : null} ) : null}
@@ -113,14 +107,6 @@ export function ActionsView({
onChange={(event) => onFieldChange('description', event.target.value)} onChange={(event) => onFieldChange('description', event.target.value)}
/> />
</label> </label>
<label className="toggle-field">
<input
checked={form.isEnabled}
type="checkbox"
onChange={(event) => onFieldChange('isEnabled', event.target.checked)}
/>
Enabled
</label>
</div> </div>
<div className="form-actions"> <div className="form-actions">
@@ -151,8 +137,6 @@ export function ActionsView({
<h3>{action.name}</h3> <h3>{action.name}</h3>
<p> <p>
{action.description ?? 'No description'} {action.description ?? 'No description'}
{' - '}
{action.isEnabled ? 'Enabled' : 'Disabled'}
</p> </p>
</div> </div>
<div className="row-actions"> <div className="row-actions">
@@ -46,10 +46,6 @@ export function ActivitiesView({
onSave, onSave,
resources, resources,
}: ActivitiesViewProps) { }: ActivitiesViewProps) {
const enabledCount = activities.filter((activity) => activity.isEnabled).length;
const enabledActions = actions.filter((action) => action.isEnabled);
const enabledResources = resources.filter((resource) => resource.isEnabled);
function updateAction(index: number, nextAction: ActivityFormState['actions'][number]) { function updateAction(index: number, nextAction: ActivityFormState['actions'][number]) {
onFieldChange( onFieldChange(
'actions', 'actions',
@@ -77,7 +73,7 @@ export function ActivitiesView({
<div> <div>
<p className="eyebrow">Care activities</p> <p className="eyebrow">Care activities</p>
<h2 id="activities-summary-heading"> <h2 id="activities-summary-heading">
{isLoading ? 'Loading activities' : `${enabledCount} activities enabled`} {isLoading ? 'Loading activities' : `${activities.length} activities`}
</h2> </h2>
<p>{error ?? 'Configure reusable care bundles with actions and per-action resources.'}</p> <p>{error ?? 'Configure reusable care bundles with actions and per-action resources.'}</p>
</div> </div>
@@ -108,10 +104,6 @@ export function ActivitiesView({
<span>Notes</span> <span>Notes</span>
<strong>{selectedActivity.notes ?? 'No notes'}</strong> <strong>{selectedActivity.notes ?? 'No notes'}</strong>
</div> </div>
<div>
<span>Status</span>
<strong>{selectedActivity.isEnabled ? 'Enabled' : 'Disabled'}</strong>
</div>
</div> </div>
<div className="detail-list"> <div className="detail-list">
{selectedActivity.actions.length === 0 ? ( {selectedActivity.actions.length === 0 ? (
@@ -192,7 +184,7 @@ export function ActivitiesView({
)} )}
> >
<option value="">Select an action</option> <option value="">Select an action</option>
{enabledActions {actions
.filter((action) => !selectedActionIds.has(String(action.id))) .filter((action) => !selectedActionIds.has(String(action.id)))
.map((action) => ( .map((action) => (
<option key={action.id} value={action.id}> <option key={action.id} value={action.id}>
@@ -238,7 +230,7 @@ export function ActivitiesView({
)} )}
> >
<option value="">Select a resource</option> <option value="">Select a resource</option>
{enabledResources {resources
.filter((resource) => !selectedResourceIds.has(String(resource.id))) .filter((resource) => !selectedResourceIds.has(String(resource.id)))
.map((resource) => ( .map((resource) => (
<option key={resource.id} value={resource.id}> <option key={resource.id} value={resource.id}>
@@ -309,7 +301,7 @@ export function ActivitiesView({
<button <button
className="small-action" className="small-action"
type="button" type="button"
disabled={enabledResources.length === 0} disabled={resources.length === 0}
onClick={() => updateAction( onClick={() => updateAction(
actionIndex, actionIndex,
{ {
@@ -341,7 +333,7 @@ export function ActivitiesView({
<button <button
className="small-action" className="small-action"
type="button" type="button"
disabled={enabledActions.length === 0} disabled={actions.length === 0}
onClick={addAction} onClick={addAction}
> >
<Plus size={16} /> <Plus size={16} />
@@ -355,14 +347,6 @@ export function ActivitiesView({
onChange={(event) => onFieldChange('notes', event.target.value)} onChange={(event) => onFieldChange('notes', event.target.value)}
/> />
</label> </label>
<label className="toggle-field">
<input
checked={form.isEnabled}
type="checkbox"
onChange={(event) => onFieldChange('isEnabled', event.target.checked)}
/>
Enabled
</label>
</div> </div>
<div className="form-actions"> <div className="form-actions">
@@ -391,7 +375,7 @@ export function ActivitiesView({
<article className="plant-row" key={activity.id}> <article className="plant-row" key={activity.id}>
<div> <div>
<h3>{activity.name}</h3> <h3>{activity.name}</h3>
<p>{formatActivitySummary(activity)} - {activity.isEnabled ? 'Enabled' : 'Disabled'}</p> <p>{formatActivitySummary(activity)}</p>
</div> </div>
<div className="row-actions"> <div className="row-actions">
<button className="icon-button compact" type="button" aria-label={`View ${activity.name}`} onClick={() => onOpenDetail(activity)}> <button className="icon-button compact" type="button" aria-label={`View ${activity.name}`} onClick={() => onOpenDetail(activity)}>
+2 -17
View File
@@ -15,7 +15,7 @@ type FlagsViewProps = {
onCloseDetail: () => void; onCloseDetail: () => void;
onDelete: (flag: PlantFlagDefinition) => void; onDelete: (flag: PlantFlagDefinition) => void;
onEdit: (flag: PlantFlagDefinition) => void; onEdit: (flag: PlantFlagDefinition) => void;
onFieldChange: (field: keyof FlagDefinitionFormState, value: string | boolean) => void; onFieldChange: (field: keyof FlagDefinitionFormState, value: string) => void;
onNew: () => void; onNew: () => void;
onOpenDetail: (flag: PlantFlagDefinition) => void; onOpenDetail: (flag: PlantFlagDefinition) => void;
onSave: () => void; onSave: () => void;
@@ -39,15 +39,13 @@ export function FlagsView({
onOpenDetail, onOpenDetail,
onSave, onSave,
}: FlagsViewProps) { }: FlagsViewProps) {
const enabledCount = flags.filter((flag) => flag.isEnabled).length;
return ( return (
<> <>
<section className="summary-panel" aria-labelledby="flags-summary-heading"> <section className="summary-panel" aria-labelledby="flags-summary-heading">
<div> <div>
<p className="eyebrow">Plant flags</p> <p className="eyebrow">Plant flags</p>
<h2 id="flags-summary-heading"> <h2 id="flags-summary-heading">
{isLoading ? 'Loading flags' : `${enabledCount} flags enabled`} {isLoading ? 'Loading flags' : `${flags.length} flags`}
</h2> </h2>
<p>{error ?? 'Configure reusable flags for plants.'}</p> <p>{error ?? 'Configure reusable flags for plants.'}</p>
</div> </div>
@@ -82,10 +80,6 @@ export function FlagsView({
</span> </span>
</strong> </strong>
</div> </div>
<div>
<span>Status</span>
<strong>{selectedFlag.isEnabled ? 'Enabled' : 'Disabled'}</strong>
</div>
</div> </div>
</section> </section>
) : null} ) : null}
@@ -118,14 +112,6 @@ export function FlagsView({
onChange={(event) => onFieldChange('color', event.target.value)} onChange={(event) => onFieldChange('color', event.target.value)}
/> />
</label> </label>
<label className="toggle-field">
<input
checked={form.isEnabled}
type="checkbox"
onChange={(event) => onFieldChange('isEnabled', event.target.checked)}
/>
Enabled
</label>
</div> </div>
<div className="form-actions"> <div className="form-actions">
@@ -154,7 +140,6 @@ export function FlagsView({
<article className="plant-row" key={flag.id}> <article className="plant-row" key={flag.id}>
<div> <div>
<h3>{flag.name}</h3> <h3>{flag.name}</h3>
<p>{flag.isEnabled ? 'Enabled' : 'Disabled'}</p>
</div> </div>
<div className="row-actions"> <div className="row-actions">
<span className="flag-chip" style={{ backgroundColor: flag.color }}> <span className="flag-chip" style={{ backgroundColor: flag.color }}>
@@ -15,7 +15,7 @@ type LocationsViewProps = {
onCloseDetail: () => void; onCloseDetail: () => void;
onDelete: (location: PlantLocation) => void; onDelete: (location: PlantLocation) => void;
onEdit: (location: PlantLocation) => void; onEdit: (location: PlantLocation) => void;
onFieldChange: (field: keyof LocationFormState, value: string | boolean) => void; onFieldChange: (field: keyof LocationFormState, value: string) => void;
onNew: () => void; onNew: () => void;
onOpenDetail: (location: PlantLocation) => void; onOpenDetail: (location: PlantLocation) => void;
onSave: () => void; onSave: () => void;
@@ -39,15 +39,13 @@ export function LocationsView({
onOpenDetail, onOpenDetail,
onSave, onSave,
}: LocationsViewProps) { }: LocationsViewProps) {
const enabledCount = locations.filter((location) => location.isEnabled).length;
return ( return (
<> <>
<section className="summary-panel" aria-labelledby="locations-summary-heading"> <section className="summary-panel" aria-labelledby="locations-summary-heading">
<div> <div>
<p className="eyebrow">Location library</p> <p className="eyebrow">Location library</p>
<h2 id="locations-summary-heading"> <h2 id="locations-summary-heading">
{isLoading ? 'Loading locations' : `${enabledCount} locations enabled`} {isLoading ? 'Loading locations' : `${locations.length} locations`}
</h2> </h2>
<p>{error ?? 'Create and maintain the places where plants live.'}</p> <p>{error ?? 'Create and maintain the places where plants live.'}</p>
</div> </div>
@@ -78,10 +76,6 @@ export function LocationsView({
<span>Notes</span> <span>Notes</span>
<strong>{selectedLocation.notes ?? 'No notes'}</strong> <strong>{selectedLocation.notes ?? 'No notes'}</strong>
</div> </div>
<div>
<span>Status</span>
<strong>{selectedLocation.isEnabled ? 'Enabled' : 'Disabled'}</strong>
</div>
</div> </div>
</section> </section>
) : null} ) : null}
@@ -113,14 +107,6 @@ export function LocationsView({
onChange={(event) => onFieldChange('notes', event.target.value)} onChange={(event) => onFieldChange('notes', event.target.value)}
/> />
</label> </label>
<label className="toggle-field">
<input
checked={form.isEnabled}
type="checkbox"
onChange={(event) => onFieldChange('isEnabled', event.target.checked)}
/>
Enabled
</label>
</div> </div>
<div className="form-actions"> <div className="form-actions">
@@ -151,8 +137,6 @@ export function LocationsView({
<h3>{location.name}</h3> <h3>{location.name}</h3>
<p> <p>
{location.notes ?? 'No notes'} {location.notes ?? 'No notes'}
{' - '}
{location.isEnabled ? 'Enabled' : 'Disabled'}
</p> </p>
</div> </div>
<div className="row-actions"> <div className="row-actions">
@@ -48,9 +48,6 @@ export function PlantManagementView({
onSetLocation, onSetLocation,
onSetTaxon, onSetTaxon,
}: PlantManagementViewProps) { }: PlantManagementViewProps) {
const enabledFlags = plantFlagDefinitions.filter((flag) => flag.isEnabled);
const enabledLocations = plantLocations.filter((location) =>
location.isEnabled || location.id === selectedPlant?.locationId);
const selectedTaxon = plantTaxa.find((taxon) => taxon.id === selectedPlant?.taxonId); const selectedTaxon = plantTaxa.find((taxon) => taxon.id === selectedPlant?.taxonId);
const selectedLocation = plantLocations.find((location) => location.id === selectedPlant?.locationId); const selectedLocation = plantLocations.find((location) => location.id === selectedPlant?.locationId);
const activeFlags = selectedPlant?.flags.filter((flag) => flag.resolvedOn === null) ?? []; const activeFlags = selectedPlant?.flags.filter((flag) => flag.resolvedOn === null) ?? [];
@@ -148,9 +145,9 @@ export function PlantManagementView({
onChange={(event) => onSetLocation(event.target.value)} onChange={(event) => onSetLocation(event.target.value)}
> >
<option value="">No location</option> <option value="">No location</option>
{enabledLocations.map((location) => ( {plantLocations.map((location) => (
<option key={location.id} value={location.id}> <option key={location.id} value={location.id}>
{location.isEnabled ? location.name : `${location.name} (disabled)`} {location.name}
</option> </option>
))} ))}
</select> </select>
@@ -162,10 +159,6 @@ export function PlantManagementView({
<span>Location</span> <span>Location</span>
<strong>{selectedLocation.name}</strong> <strong>{selectedLocation.name}</strong>
</div> </div>
<div>
<span>Status</span>
<strong>{selectedLocation.isEnabled ? 'Enabled' : 'Disabled'}</strong>
</div>
{selectedLocation.notes ? ( {selectedLocation.notes ? (
<div className="meta-wide"> <div className="meta-wide">
<span>Notes</span> <span>Notes</span>
@@ -202,7 +195,7 @@ export function PlantManagementView({
onChange={(event) => onFieldChange('plantFlagDefinitionId', event.target.value)} onChange={(event) => onFieldChange('plantFlagDefinitionId', event.target.value)}
> >
<option value="">Select a flag</option> <option value="">Select a flag</option>
{enabledFlags.map((flag) => ( {plantFlagDefinitions.map((flag) => (
<option key={flag.id} value={flag.id}> <option key={flag.id} value={flag.id}>
{flag.name} {flag.name}
</option> </option>
@@ -14,7 +14,7 @@ type ResourcesViewProps = {
onCloseDetail: () => void; onCloseDetail: () => void;
onDelete: (resource: ActionResource) => void; onDelete: (resource: ActionResource) => void;
onEdit: (resource: ActionResource) => void; onEdit: (resource: ActionResource) => void;
onFieldChange: (field: keyof ResourceFormState, value: string | boolean) => void; onFieldChange: (field: keyof ResourceFormState, value: string) => void;
onNew: () => void; onNew: () => void;
onOpenDetail: (resource: ActionResource) => void; onOpenDetail: (resource: ActionResource) => void;
onSave: () => void; onSave: () => void;
@@ -39,15 +39,13 @@ export function ResourcesView({
onSave, onSave,
resources, resources,
}: ResourcesViewProps) { }: ResourcesViewProps) {
const enabledCount = resources.filter((resource) => resource.isEnabled).length;
return ( return (
<> <>
<section className="summary-panel" aria-labelledby="resources-summary-heading"> <section className="summary-panel" aria-labelledby="resources-summary-heading">
<div> <div>
<p className="eyebrow">Resource library</p> <p className="eyebrow">Resource library</p>
<h2 id="resources-summary-heading"> <h2 id="resources-summary-heading">
{isLoading ? 'Loading resources' : `${enabledCount} resources enabled`} {isLoading ? 'Loading resources' : `${resources.length} resources`}
</h2> </h2>
<p>{error ?? 'Configure materials, products, tools, and containers used during care.'}</p> <p>{error ?? 'Configure materials, products, tools, and containers used during care.'}</p>
</div> </div>
@@ -74,18 +72,10 @@ export function ResourcesView({
</div> </div>
</div> </div>
<div className="plant-detail-meta"> <div className="plant-detail-meta">
<div>
<span>Category</span>
<strong>{selectedResource.category ?? 'Uncategorized'}</strong>
</div>
<div> <div>
<span>Notes</span> <span>Notes</span>
<strong>{selectedResource.notes ?? 'No notes'}</strong> <strong>{selectedResource.notes ?? 'No notes'}</strong>
</div> </div>
<div>
<span>Status</span>
<strong>{selectedResource.isEnabled ? 'Enabled' : 'Disabled'}</strong>
</div>
</div> </div>
</section> </section>
) : null} ) : null}
@@ -110,13 +100,6 @@ export function ResourcesView({
onChange={(event) => onFieldChange('name', event.target.value)} onChange={(event) => onFieldChange('name', event.target.value)}
/> />
</label> </label>
<label>
Category
<input
value={form.category}
onChange={(event) => onFieldChange('category', event.target.value)}
/>
</label>
<label> <label>
Notes Notes
<input <input
@@ -124,14 +107,6 @@ export function ResourcesView({
onChange={(event) => onFieldChange('notes', event.target.value)} onChange={(event) => onFieldChange('notes', event.target.value)}
/> />
</label> </label>
<label className="toggle-field">
<input
checked={form.isEnabled}
type="checkbox"
onChange={(event) => onFieldChange('isEnabled', event.target.checked)}
/>
Enabled
</label>
</div> </div>
<div className="form-actions"> <div className="form-actions">
@@ -161,11 +136,7 @@ export function ResourcesView({
<div> <div>
<h3>{resource.name}</h3> <h3>{resource.name}</h3>
<p> <p>
{resource.category ?? 'Uncategorized'}
{' - '}
{resource.notes ?? 'No notes'} {resource.notes ?? 'No notes'}
{' - '}
{resource.isEnabled ? 'Enabled' : 'Disabled'}
</p> </p>
</div> </div>
<div className="row-actions"> <div className="row-actions">
@@ -10,7 +10,7 @@ type SchedulesViewProps = {
isLoading: boolean; isLoading: boolean;
isSaving: boolean; isSaving: boolean;
plants: Plant[]; plants: Plant[];
onFieldChange: (field: keyof BulkScheduleFormState, value: string | boolean | string[]) => void; onFieldChange: (field: keyof BulkScheduleFormState, value: string | string[]) => void;
onRemove: () => void; onRemove: () => void;
onSave: () => void; onSave: () => void;
}; };
@@ -46,7 +46,6 @@ export function SchedulesView({
onSave, onSave,
}: SchedulesViewProps) { }: SchedulesViewProps) {
const [plantQuery, setPlantQuery] = useState(''); const [plantQuery, setPlantQuery] = useState('');
const enabledActivities = activities.filter((activity) => activity.isEnabled);
const visiblePlants = useMemo( const visiblePlants = useMemo(
() => filterPlants(plants, plantQuery), () => filterPlants(plants, plantQuery),
[plants, plantQuery], [plants, plantQuery],
@@ -93,7 +92,7 @@ export function SchedulesView({
onChange={(event) => onFieldChange('careActivityId', event.target.value)} onChange={(event) => onFieldChange('careActivityId', event.target.value)}
> >
<option value="">Select an activity</option> <option value="">Select an activity</option>
{enabledActivities.map((activity) => ( {activities.map((activity) => (
<option key={activity.id} value={activity.id}> <option key={activity.id} value={activity.id}>
{activity.name} {activity.name}
</option> </option>
@@ -109,15 +108,6 @@ export function SchedulesView({
onChange={(event) => onFieldChange('scheduledFor', event.target.value)} onChange={(event) => onFieldChange('scheduledFor', event.target.value)}
/> />
</label> </label>
<label className="toggle-field">
<input
checked={form.isEnabled}
disabled={isSaving}
type="checkbox"
onChange={(event) => onFieldChange('isEnabled', event.target.checked)}
/>
Enabled
</label>
</div> </div>
<fieldset className="schedule-options"> <fieldset className="schedule-options">
@@ -348,7 +338,7 @@ export function SchedulesView({
<div className="schedule-chip-list"> <div className="schedule-chip-list">
{plant.careSchedules.map((schedule) => ( {plant.careSchedules.map((schedule) => (
<span className={`status-pill ${schedule.status}`} key={schedule.id}> <span className={`status-pill ${schedule.status}`} key={schedule.id}>
{schedule.action} / {formatRecurrence(schedule)} / {schedule.isEnabled ? schedule.nextCare : 'Disabled'} {schedule.action} / {formatRecurrence(schedule)} / {schedule.nextCare}
</span> </span>
))} ))}
</div> </div>
@@ -372,7 +362,7 @@ function formatPlantScheduleSummary(plant: Plant, selectedActivity?: CareActivit
return `No ${selectedActivity.name} schedule`; return `No ${selectedActivity.name} schedule`;
} }
return `${selectedActivity.name}: ${formatRecurrence(schedule)} - ${schedule.isEnabled ? schedule.nextCare : 'disabled'}`; return `${selectedActivity.name}: ${formatRecurrence(schedule)} - ${schedule.nextCare}`;
} }
function filterPlants(plants: Plant[], query: string) { function filterPlants(plants: Plant[], query: string) {
-16
View File
@@ -31,7 +31,6 @@ export type PlantCareSchedule = {
lastPerformed: string; lastPerformed: string;
nextCare: string; nextCare: string;
status: CareStatus; status: CareStatus;
isEnabled: boolean;
}; };
export type ScheduleRecurrenceMode = 'none' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'custom'; export type ScheduleRecurrenceMode = 'none' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'custom';
@@ -52,22 +51,18 @@ export type PlantLocation = {
id: number; id: number;
name: string; name: string;
notes: string | null; notes: string | null;
isEnabled: boolean;
}; };
export type CareAction = { export type CareAction = {
id: number; id: number;
name: string; name: string;
description: string | null; description: string | null;
isEnabled: boolean;
}; };
export type ActionResource = { export type ActionResource = {
id: number; id: number;
name: string; name: string;
category: string | null;
notes: string | null; notes: string | null;
isEnabled: boolean;
}; };
export type CareActivity = { export type CareActivity = {
@@ -77,7 +72,6 @@ export type CareActivity = {
action: string; action: string;
actions: CareActivityAction[]; actions: CareActivityAction[];
notes: string | null; notes: string | null;
isEnabled: boolean;
}; };
export type CareActivityAction = { export type CareActivityAction = {
@@ -91,7 +85,6 @@ export type CareActivityAction = {
export type CareActivityActionResource = { export type CareActivityActionResource = {
actionResourceId: number; actionResourceId: number;
name: string; name: string;
category: string | null;
quantity: number | null; quantity: number | null;
unit: string | null; unit: string | null;
notes: string | null; notes: string | null;
@@ -101,7 +94,6 @@ export type PlantFlagDefinition = {
id: number; id: number;
name: string; name: string;
color: string; color: string;
isEnabled: boolean;
}; };
export type PlantFlag = { export type PlantFlag = {
@@ -132,7 +124,6 @@ export type PlantCareSchedulePayload = {
endsMode: ScheduleEndsMode; endsMode: ScheduleEndsMode;
endsOn: string | null; endsOn: string | null;
endsAfterOccurrences: number | null; endsAfterOccurrences: number | null;
isEnabled: boolean;
}; };
export type BulkPlantCareSchedulePayload = { export type BulkPlantCareSchedulePayload = {
@@ -147,7 +138,6 @@ export type BulkPlantCareSchedulePayload = {
endsMode: ScheduleEndsMode; endsMode: ScheduleEndsMode;
endsOn: string | null; endsOn: string | null;
endsAfterOccurrences: number | null; endsAfterOccurrences: number | null;
isEnabled: boolean;
}; };
export type PlantTaxonPayload = { export type PlantTaxonPayload = {
@@ -162,27 +152,22 @@ export type PlantTaxonPayload = {
export type PlantLocationPayload = { export type PlantLocationPayload = {
name: string; name: string;
notes: string | null; notes: string | null;
isEnabled: boolean;
}; };
export type CareActionPayload = { export type CareActionPayload = {
name: string; name: string;
description: string | null; description: string | null;
isEnabled: boolean;
}; };
export type ActionResourcePayload = { export type ActionResourcePayload = {
name: string; name: string;
category: string | null;
notes: string | null; notes: string | null;
isEnabled: boolean;
}; };
export type CareActivityPayload = { export type CareActivityPayload = {
name: string; name: string;
actions: CareActivityActionPayload[]; actions: CareActivityActionPayload[];
notes: string | null; notes: string | null;
isEnabled: boolean;
}; };
export type CareActivityActionPayload = { export type CareActivityActionPayload = {
@@ -200,7 +185,6 @@ export type CareActivityActionResourcePayload = {
export type PlantFlagDefinitionPayload = { export type PlantFlagDefinitionPayload = {
name: string; name: string;
color: string | null; color: string | null;
isEnabled: boolean;
}; };
export type AssignPlantFlagPayload = { export type AssignPlantFlagPayload = {
-20
View File
@@ -36,27 +36,22 @@ export const emptyTaxonForm = {
export const emptyLocationForm = { export const emptyLocationForm = {
name: '', name: '',
notes: '', notes: '',
isEnabled: true,
}; };
export const emptyActionForm = { export const emptyActionForm = {
name: '', name: '',
description: '', description: '',
isEnabled: true,
}; };
export const emptyResourceForm = { export const emptyResourceForm = {
name: '', name: '',
category: '',
notes: '', notes: '',
isEnabled: true,
}; };
export const emptyActivityForm = { export const emptyActivityForm = {
name: '', name: '',
actions: [] as CareActivityActionFormState[], actions: [] as CareActivityActionFormState[],
notes: '', notes: '',
isEnabled: true,
}; };
export type CareActivityActionResourceFormState = { export type CareActivityActionResourceFormState = {
@@ -74,7 +69,6 @@ export type CareActivityActionFormState = {
export const emptyFlagDefinitionForm = { export const emptyFlagDefinitionForm = {
name: '', name: '',
color: '#f2f2f2', color: '#f2f2f2',
isEnabled: true,
}; };
export const emptyPlantFlagForm = { export const emptyPlantFlagForm = {
@@ -94,7 +88,6 @@ export const emptyBulkScheduleForm = {
endsMode: 'after', endsMode: 'after',
endsOn: '', endsOn: '',
endsAfterOccurrences: '12', endsAfterOccurrences: '12',
isEnabled: true,
plantIds: [] as string[], plantIds: [] as string[],
}; };
@@ -128,7 +121,6 @@ export function toLocationForm(location: PlantLocation): LocationFormState {
return { return {
name: location.name, name: location.name,
notes: location.notes ?? '', notes: location.notes ?? '',
isEnabled: location.isEnabled,
}; };
} }
@@ -136,7 +128,6 @@ export function toLocationPayload(form: LocationFormState): PlantLocationPayload
return { return {
name: form.name.trim(), name: form.name.trim(),
notes: form.notes.trim() || null, notes: form.notes.trim() || null,
isEnabled: form.isEnabled,
}; };
} }
@@ -166,7 +157,6 @@ export function toActionForm(action: CareAction): ActionFormState {
return { return {
name: action.name, name: action.name,
description: action.description ?? '', description: action.description ?? '',
isEnabled: action.isEnabled,
}; };
} }
@@ -174,25 +164,20 @@ export function toActionPayload(form: ActionFormState): CareActionPayload {
return { return {
name: form.name.trim(), name: form.name.trim(),
description: form.description.trim() || null, description: form.description.trim() || null,
isEnabled: form.isEnabled,
}; };
} }
export function toResourceForm(resource: ActionResource): ResourceFormState { export function toResourceForm(resource: ActionResource): ResourceFormState {
return { return {
name: resource.name, name: resource.name,
category: resource.category ?? '',
notes: resource.notes ?? '', notes: resource.notes ?? '',
isEnabled: resource.isEnabled,
}; };
} }
export function toResourcePayload(form: ResourceFormState): ActionResourcePayload { export function toResourcePayload(form: ResourceFormState): ActionResourcePayload {
return { return {
name: form.name.trim(), name: form.name.trim(),
category: form.category.trim() || null,
notes: form.notes.trim() || null, notes: form.notes.trim() || null,
isEnabled: form.isEnabled,
}; };
} }
@@ -209,7 +194,6 @@ export function toActivityForm(activity: CareActivity): ActivityFormState {
})), })),
})), })),
notes: activity.notes ?? '', notes: activity.notes ?? '',
isEnabled: activity.isEnabled,
}; };
} }
@@ -226,7 +210,6 @@ export function toActivityPayload(form: ActivityFormState): CareActivityPayload
})), })),
})), })),
notes: form.notes.trim() || null, notes: form.notes.trim() || null,
isEnabled: form.isEnabled,
}; };
} }
@@ -234,7 +217,6 @@ export function toFlagDefinitionForm(flag: PlantFlagDefinition): FlagDefinitionF
return { return {
name: flag.name, name: flag.name,
color: flag.color, color: flag.color,
isEnabled: flag.isEnabled,
}; };
} }
@@ -242,7 +224,6 @@ export function toFlagDefinitionPayload(form: FlagDefinitionFormState): PlantFla
return { return {
name: form.name.trim(), name: form.name.trim(),
color: form.color.trim() || null, color: form.color.trim() || null,
isEnabled: form.isEnabled,
}; };
} }
@@ -271,7 +252,6 @@ export function toBulkSchedulePayload(form: BulkScheduleFormState): BulkPlantCar
: form.endsMode === 'after' : form.endsMode === 'after'
? Number(form.endsAfterOccurrences) ? Number(form.endsAfterOccurrences)
: null, : null,
isEnabled: form.isEnabled,
}; };
} }
+19 -42
View File
@@ -24,8 +24,7 @@ namespace plant_manager
string? RepeatOnDays, string? RepeatOnDays,
string? EndsMode, string? EndsMode,
DateOnly? EndsOn, DateOnly? EndsOn,
int? EndsAfterOccurrences, int? EndsAfterOccurrences);
bool IsEnabled);
public record BulkSavePlantCareScheduleRequest( public record BulkSavePlantCareScheduleRequest(
IReadOnlyList<int> PlantIds, IReadOnlyList<int> PlantIds,
@@ -38,8 +37,7 @@ namespace plant_manager
string? RepeatOnDays, string? RepeatOnDays,
string? EndsMode, string? EndsMode,
DateOnly? EndsOn, DateOnly? EndsOn,
int? EndsAfterOccurrences, int? EndsAfterOccurrences);
bool IsEnabled);
public record SavePlantTaxonRequest( public record SavePlantTaxonRequest(
string Name, string Name,
@@ -51,25 +49,20 @@ namespace plant_manager
public record SavePlantLocationRequest( public record SavePlantLocationRequest(
string Name, string Name,
string? Notes, string? Notes);
bool IsEnabled);
public record SaveCareActionRequest( public record SaveCareActionRequest(
string Name, string Name,
string? Description, string? Description);
bool IsEnabled);
public record SaveActionResourceRequest( public record SaveActionResourceRequest(
string Name, string Name,
string? Category, string? Notes);
string? Notes,
bool IsEnabled);
public record SaveCareActivityRequest( public record SaveCareActivityRequest(
string Name, string Name,
IReadOnlyList<SaveCareActivityActionRequest> Actions, IReadOnlyList<SaveCareActivityActionRequest> Actions,
string? Notes, string? Notes);
bool IsEnabled);
public record SaveCareActivityActionRequest( public record SaveCareActivityActionRequest(
int CareActionId, int CareActionId,
@@ -84,7 +77,6 @@ namespace plant_manager
public record CareActivityActionResourceDto( public record CareActivityActionResourceDto(
int ActionResourceId, int ActionResourceId,
string Name, string Name,
string? Category,
decimal? Quantity, decimal? Quantity,
string? Unit, string? Unit,
string? Notes) string? Notes)
@@ -94,7 +86,6 @@ namespace plant_manager
new( new(
resource.ActionResourceId, resource.ActionResourceId,
resource.ActionResource.Name, resource.ActionResource.Name,
resource.ActionResource.Category,
resource.Quantity, resource.Quantity,
resource.Unit, resource.Unit,
resource.Notes); resource.Notes);
@@ -121,8 +112,7 @@ namespace plant_manager
public record SavePlantFlagDefinitionRequest( public record SavePlantFlagDefinitionRequest(
string Name, string Name,
string? Color, string? Color);
bool IsEnabled);
public record AssignPlantFlagRequest( public record AssignPlantFlagRequest(
int PlantFlagDefinitionId, int PlantFlagDefinitionId,
@@ -183,32 +173,28 @@ namespace plant_manager
public record PlantLocationDto( public record PlantLocationDto(
int Id, int Id,
string Name, string Name,
string? Notes, string? Notes)
bool IsEnabled)
{ {
public static PlantLocationDto FromLocation(PlantLocation location) => public static PlantLocationDto FromLocation(PlantLocation location) =>
new(location.Id, location.Name, location.Notes, location.IsEnabled); new(location.Id, location.Name, location.Notes);
} }
public record CareActionDto( public record CareActionDto(
int Id, int Id,
string Name, string Name,
string? Description, string? Description)
bool IsEnabled)
{ {
public static CareActionDto FromCareAction(CareAction action) => public static CareActionDto FromCareAction(CareAction action) =>
new(action.Id, action.Name, action.Description, action.IsEnabled); new(action.Id, action.Name, action.Description);
} }
public record ActionResourceDto( public record ActionResourceDto(
int Id, int Id,
string Name, string Name,
string? Category, string? Notes)
string? Notes,
bool IsEnabled)
{ {
public static ActionResourceDto FromActionResource(ActionResource resource) => public static ActionResourceDto FromActionResource(ActionResource resource) =>
new(resource.Id, resource.Name, resource.Category, resource.Notes, resource.IsEnabled); new(resource.Id, resource.Name, resource.Notes);
} }
public record CareActivityDto( public record CareActivityDto(
@@ -217,8 +203,7 @@ namespace plant_manager
int CareActionId, int CareActionId,
string Action, string Action,
IReadOnlyList<CareActivityActionDto> Actions, IReadOnlyList<CareActivityActionDto> Actions,
string? Notes, string? Notes)
bool IsEnabled)
{ {
public static CareActivityDto FromCareActivity(CareActivity activity) => public static CareActivityDto FromCareActivity(CareActivity activity) =>
new( new(
@@ -230,8 +215,7 @@ namespace plant_manager
.OrderBy(action => action.SortOrder) .OrderBy(action => action.SortOrder)
.Select(CareActivityActionDto.FromCareActivityAction) .Select(CareActivityActionDto.FromCareActivityAction)
.ToList(), .ToList(),
activity.Notes, activity.Notes);
activity.IsEnabled);
} }
public record PlantDto( public record PlantDto(
@@ -257,7 +241,6 @@ namespace plant_manager
today)) today))
.ToList(); .ToList();
var nextCare = schedules var nextCare = schedules
.Where(schedule => schedule.IsEnabled)
.Select(schedule => .Select(schedule =>
{ {
var source = plant.CareSchedules.First(item => item.Id == schedule.Id); var source = plant.CareSchedules.First(item => item.Id == schedule.Id);
@@ -312,8 +295,7 @@ namespace plant_manager
DateOnly? LastPerformedOn, DateOnly? LastPerformedOn,
string LastPerformed, string LastPerformed,
string NextCare, string NextCare,
string Status, string Status)
bool IsEnabled)
{ {
public static PlantCareScheduleDto FromSchedule( public static PlantCareScheduleDto FromSchedule(
PlantCareSchedule schedule, PlantCareSchedule schedule,
@@ -339,8 +321,7 @@ namespace plant_manager
lastPerformedOn, lastPerformedOn,
PlantCareFormatter.FormatRelativeDate(lastPerformedOn, today, "Never"), PlantCareFormatter.FormatRelativeDate(lastPerformedOn, today, "Never"),
PlantCareFormatter.FormatRelativeDate(nextCare, today, "Unscheduled"), PlantCareFormatter.FormatRelativeDate(nextCare, today, "Unscheduled"),
PlantCareFormatter.GetStatus(nextCare, today), PlantCareFormatter.GetStatus(nextCare, today));
schedule.IsEnabled);
} }
private static int GetCompletedOccurrences(PlantCareSchedule schedule) => private static int GetCompletedOccurrences(PlantCareSchedule schedule) =>
@@ -395,15 +376,13 @@ namespace plant_manager
public record PlantFlagDefinitionDto( public record PlantFlagDefinitionDto(
int Id, int Id,
string Name, string Name,
string Color, string Color)
bool IsEnabled)
{ {
public static PlantFlagDefinitionDto FromDefinition(PlantFlagDefinition definition) => public static PlantFlagDefinitionDto FromDefinition(PlantFlagDefinition definition) =>
new( new(
definition.Id, definition.Id,
definition.Name, definition.Name,
definition.Color, definition.Color);
definition.IsEnabled);
} }
public record PlantFlagDto( public record PlantFlagDto(
@@ -455,7 +434,6 @@ namespace plant_manager
public record ActionLogResourceDto( public record ActionLogResourceDto(
int ActionResourceId, int ActionResourceId,
string Name, string Name,
string? Category,
decimal? Quantity, decimal? Quantity,
string? Unit) string? Unit)
{ {
@@ -463,7 +441,6 @@ namespace plant_manager
new( new(
resource.ActionResourceId, resource.ActionResourceId,
resource.ActionResource.Name, resource.ActionResource.Name,
resource.ActionResource.Category,
resource.Quantity, resource.Quantity,
resource.Unit); resource.Unit);
} }
@@ -57,7 +57,6 @@ namespace plant_manager.Data
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
entity.Property(e => e.Name).HasMaxLength(120).IsRequired(); entity.Property(e => e.Name).HasMaxLength(120).IsRequired();
entity.Property(e => e.Notes).HasMaxLength(1000); entity.Property(e => e.Notes).HasMaxLength(1000);
entity.Property(e => e.IsEnabled).IsRequired();
entity.HasIndex(e => e.Name).IsUnique(); entity.HasIndex(e => e.Name).IsUnique();
}); });
@@ -68,7 +67,6 @@ namespace plant_manager.Data
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
entity.Property(e => e.Name).HasMaxLength(80).IsRequired(); entity.Property(e => e.Name).HasMaxLength(80).IsRequired();
entity.Property(e => e.Description).HasMaxLength(400); entity.Property(e => e.Description).HasMaxLength(400);
entity.Property(e => e.IsEnabled).IsRequired();
entity.HasIndex(e => e.Name).IsUnique(); entity.HasIndex(e => e.Name).IsUnique();
}); });
@@ -78,9 +76,7 @@ namespace plant_manager.Data
entity.Property(e => e.Id) entity.Property(e => e.Id)
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
entity.Property(e => e.Name).HasMaxLength(120).IsRequired(); entity.Property(e => e.Name).HasMaxLength(120).IsRequired();
entity.Property(e => e.Category).HasMaxLength(80);
entity.Property(e => e.Notes).HasMaxLength(1000); entity.Property(e => e.Notes).HasMaxLength(1000);
entity.Property(e => e.IsEnabled).IsRequired();
entity.HasIndex(e => e.Name).IsUnique(); entity.HasIndex(e => e.Name).IsUnique();
}); });
@@ -91,7 +87,6 @@ namespace plant_manager.Data
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
entity.Property(e => e.Name).HasMaxLength(120).IsRequired(); entity.Property(e => e.Name).HasMaxLength(120).IsRequired();
entity.Property(e => e.Notes).HasMaxLength(1000); entity.Property(e => e.Notes).HasMaxLength(1000);
entity.Property(e => e.IsEnabled).IsRequired();
entity.HasIndex(e => e.Name).IsUnique(); entity.HasIndex(e => e.Name).IsUnique();
}); });
@@ -174,7 +169,6 @@ namespace plant_manager.Data
entity.Property(e => e.EndsMode).HasMaxLength(20).IsRequired(); entity.Property(e => e.EndsMode).HasMaxLength(20).IsRequired();
entity.Property(e => e.EndsOn); entity.Property(e => e.EndsOn);
entity.Property(e => e.EndsAfterOccurrences); entity.Property(e => e.EndsAfterOccurrences);
entity.Property(e => e.IsEnabled).IsRequired();
entity.HasIndex(e => new { e.PlantId, e.CareActionId }).IsUnique(false); entity.HasIndex(e => new { e.PlantId, e.CareActionId }).IsUnique(false);
entity.HasIndex(e => new { e.PlantId, e.CareActivityId }).IsUnique(); entity.HasIndex(e => new { e.PlantId, e.CareActivityId }).IsUnique();
entity.HasOne(e => e.Plant) entity.HasOne(e => e.Plant)
@@ -198,7 +192,6 @@ namespace plant_manager.Data
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
entity.Property(e => e.Name).HasMaxLength(120).IsRequired(); entity.Property(e => e.Name).HasMaxLength(120).IsRequired();
entity.Property(e => e.Color).HasMaxLength(20).IsRequired(); entity.Property(e => e.Color).HasMaxLength(20).IsRequired();
entity.Property(e => e.IsEnabled).IsRequired();
entity.HasIndex(e => e.Name).IsUnique(); entity.HasIndex(e => e.Name).IsUnique();
}); });
+12 -18
View File
@@ -16,14 +16,14 @@ namespace plant_manager.Data
private static readonly ActionResource[] StarterResources = private static readonly ActionResource[] StarterResources =
[ [
new() { Name = "Water", Category = "Consumable", Notes = "Plain watering resource." }, new() { Name = "Water", Notes = "Plain watering resource." },
new() { Name = "Potting Mix", Category = "Medium", Notes = "General purpose houseplant medium." }, new() { Name = "Potting Mix", Notes = "General purpose houseplant medium." },
new() { Name = "Orchid Bark", Category = "Medium", Notes = "Chunky amendment for airflow and drainage." }, new() { Name = "Orchid Bark", Notes = "Chunky amendment for airflow and drainage." },
new() { Name = "Perlite", Category = "Medium", Notes = "Lightweight amendment for drainage and aeration." }, new() { Name = "Perlite", Notes = "Lightweight amendment for drainage and aeration." },
new() { Name = "Fertilizer", Category = "Fertilizer", Notes = "General plant nutrient." }, new() { Name = "Fertilizer", Notes = "General plant nutrient." },
new() { Name = "Nursery Pot", Category = "Container", Notes = "Basic plastic grow pot." }, new() { Name = "Nursery Pot", Notes = "Basic plastic grow pot." },
new() { Name = "Neem Oil", Category = "Treatment", Notes = "Common pest treatment." }, new() { Name = "Neem Oil", Notes = "Common pest treatment." },
new() { Name = "Pruners", Category = "Equipment", Notes = "Cutting tool for pruning or cleanup." } new() { Name = "Pruners", Notes = "Cutting tool for pruning or cleanup." }
]; ];
private static readonly StarterCareActivity[] StarterCareActivities = private static readonly StarterCareActivity[] StarterCareActivities =
@@ -117,8 +117,7 @@ namespace plant_manager.Data
.Select(starterAction => new CareAction .Select(starterAction => new CareAction
{ {
Name = starterAction.Name, Name = starterAction.Name,
Description = starterAction.Description, Description = starterAction.Description
IsEnabled = starterAction.IsEnabled
}) })
.ToList(); .ToList();
@@ -143,9 +142,7 @@ namespace plant_manager.Data
.Select(starterResource => new ActionResource .Select(starterResource => new ActionResource
{ {
Name = starterResource.Name, Name = starterResource.Name,
Category = starterResource.Category, Notes = starterResource.Notes
Notes = starterResource.Notes,
IsEnabled = starterResource.IsEnabled
}) })
.ToList(); .ToList();
@@ -201,7 +198,6 @@ namespace plant_manager.Data
return new CareActivity return new CareActivity
{ {
Name = starterActivity.Name, Name = starterActivity.Name,
IsEnabled = true,
Actions = actions Actions = actions
}; };
}) })
@@ -229,8 +225,7 @@ namespace plant_manager.Data
.Select(starterFlag => new PlantFlagDefinition .Select(starterFlag => new PlantFlagDefinition
{ {
Name = starterFlag.Name, Name = starterFlag.Name,
Color = starterFlag.Color, Color = starterFlag.Color
IsEnabled = starterFlag.IsEnabled
}) })
.ToList(); .ToList();
@@ -347,8 +342,7 @@ namespace plant_manager.Data
string.Equals(existingName, starterLocation, StringComparison.OrdinalIgnoreCase))) string.Equals(existingName, starterLocation, StringComparison.OrdinalIgnoreCase)))
.Select(starterLocation => new PlantLocation .Select(starterLocation => new PlantLocation
{ {
Name = starterLocation, Name = starterLocation
IsEnabled = true
}) })
.ToList(); .ToList();
@@ -86,14 +86,6 @@ namespace plant_manager.Data.Migrations
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Category")
.HasMaxLength(80)
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(120) .HasMaxLength(120)
@@ -120,10 +112,6 @@ namespace plant_manager.Data.Migrations
b.Property<string>("Description") b.Property<string>("Description")
.HasMaxLength(400) .HasMaxLength(400)
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(80) .HasMaxLength(80)
@@ -142,10 +130,6 @@ namespace plant_manager.Data.Migrations
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(120) .HasMaxLength(120)
@@ -265,10 +249,6 @@ namespace plant_manager.Data.Migrations
b.Property<int>("EveryDays") b.Property<int>("EveryDays")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<int>("PlantId") b.Property<int>("PlantId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -347,10 +327,6 @@ namespace plant_manager.Data.Migrations
.IsRequired() .IsRequired()
.HasMaxLength(20) .HasMaxLength(20)
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(120) .HasMaxLength(120)
@@ -369,10 +345,6 @@ namespace plant_manager.Data.Migrations
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(120) .HasMaxLength(120)
@@ -18,9 +18,7 @@ namespace plant_manager.Data.Migrations
Id = table.Column<int>(type: "INTEGER", nullable: false) Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true), .Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", maxLength: 120, nullable: false), Name = table.Column<string>(type: "TEXT", maxLength: 120, nullable: false),
Category = table.Column<string>(type: "TEXT", maxLength: 80, nullable: true), Notes = table.Column<string>(type: "TEXT", maxLength: 1000, nullable: true)
Notes = table.Column<string>(type: "TEXT", maxLength: 1000, nullable: true),
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@@ -34,8 +32,7 @@ namespace plant_manager.Data.Migrations
Id = table.Column<int>(type: "INTEGER", nullable: false) Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true), .Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", maxLength: 80, nullable: false), Name = table.Column<string>(type: "TEXT", maxLength: 80, nullable: false),
Description = table.Column<string>(type: "TEXT", maxLength: 400, nullable: true), Description = table.Column<string>(type: "TEXT", maxLength: 400, nullable: true)
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@@ -49,8 +46,7 @@ namespace plant_manager.Data.Migrations
Id = table.Column<int>(type: "INTEGER", nullable: false) Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true), .Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", maxLength: 120, nullable: false), Name = table.Column<string>(type: "TEXT", maxLength: 120, nullable: false),
Notes = table.Column<string>(type: "TEXT", maxLength: 1000, nullable: true), Notes = table.Column<string>(type: "TEXT", maxLength: 1000, nullable: true)
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@@ -64,8 +60,7 @@ namespace plant_manager.Data.Migrations
Id = table.Column<int>(type: "INTEGER", nullable: false) Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true), .Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", maxLength: 120, nullable: false), Name = table.Column<string>(type: "TEXT", maxLength: 120, nullable: false),
Color = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false), Color = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false)
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@@ -79,8 +74,7 @@ namespace plant_manager.Data.Migrations
Id = table.Column<int>(type: "INTEGER", nullable: false) Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true), .Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", maxLength: 120, nullable: false), Name = table.Column<string>(type: "TEXT", maxLength: 120, nullable: false),
Notes = table.Column<string>(type: "TEXT", maxLength: 1000, nullable: true), Notes = table.Column<string>(type: "TEXT", maxLength: 1000, nullable: true)
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@@ -238,8 +232,7 @@ namespace plant_manager.Data.Migrations
RepeatOnDays = table.Column<string>(type: "TEXT", maxLength: 40, nullable: true), RepeatOnDays = table.Column<string>(type: "TEXT", maxLength: 40, nullable: true),
EndsMode = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false), EndsMode = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
EndsOn = table.Column<DateOnly>(type: "TEXT", nullable: true), EndsOn = table.Column<DateOnly>(type: "TEXT", nullable: true),
EndsAfterOccurrences = table.Column<int>(type: "INTEGER", nullable: true), EndsAfterOccurrences = table.Column<int>(type: "INTEGER", nullable: true)
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@@ -83,14 +83,6 @@ namespace plant_manager.Data.Migrations
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Category")
.HasMaxLength(80)
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(120) .HasMaxLength(120)
@@ -117,10 +109,6 @@ namespace plant_manager.Data.Migrations
b.Property<string>("Description") b.Property<string>("Description")
.HasMaxLength(400) .HasMaxLength(400)
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(80) .HasMaxLength(80)
@@ -139,10 +127,6 @@ namespace plant_manager.Data.Migrations
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(120) .HasMaxLength(120)
@@ -262,10 +246,6 @@ namespace plant_manager.Data.Migrations
b.Property<int>("EveryDays") b.Property<int>("EveryDays")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<int>("PlantId") b.Property<int>("PlantId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -344,10 +324,6 @@ namespace plant_manager.Data.Migrations
.IsRequired() .IsRequired()
.HasMaxLength(20) .HasMaxLength(20)
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(120) .HasMaxLength(120)
@@ -366,10 +342,6 @@ namespace plant_manager.Data.Migrations
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(120) .HasMaxLength(120)
@@ -5,9 +5,7 @@ namespace plant_manager.Data.Models
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string? Category { get; set; }
public string? Notes { get; set; } public string? Notes { get; set; }
public bool IsEnabled { get; set; } = true;
public List<CareActivityActionResource> CareActivityActionResources { get; set; } = []; public List<CareActivityActionResource> CareActivityActionResources { get; set; } = [];
public List<ActionLogResource> ActionLogResources { get; set; } = []; public List<ActionLogResource> ActionLogResources { get; set; } = [];
-1
View File
@@ -6,7 +6,6 @@ namespace plant_manager.Data.Models
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string? Description { get; set; } public string? Description { get; set; }
public bool IsEnabled { get; set; } = true;
public List<CareActivityAction> CareActivityActions { get; set; } = []; public List<CareActivityAction> CareActivityActions { get; set; } = [];
public List<ActionLog> ActionLogs { get; set; } = []; public List<ActionLog> ActionLogs { get; set; } = [];
@@ -6,7 +6,6 @@ namespace plant_manager.Data.Models
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string? Notes { get; set; } public string? Notes { get; set; }
public bool IsEnabled { get; set; } = true;
public List<CareActivityAction> Actions { get; set; } = []; public List<CareActivityAction> Actions { get; set; } = [];
public List<ActionLog> ActionLogs { get; set; } = []; public List<ActionLog> ActionLogs { get; set; } = [];
@@ -15,7 +15,6 @@ namespace plant_manager.Data.Models
public string EndsMode { get; set; } = "after"; public string EndsMode { get; set; } = "after";
public DateOnly? EndsOn { get; set; } public DateOnly? EndsOn { get; set; }
public int? EndsAfterOccurrences { get; set; } public int? EndsAfterOccurrences { get; set; }
public bool IsEnabled { get; set; } = true;
public Plant Plant { get; set; } = null!; public Plant Plant { get; set; } = null!;
public CareAction CareAction { get; set; } = null!; public CareAction CareAction { get; set; } = null!;
@@ -5,7 +5,6 @@ namespace plant_manager.Data.Models
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string Color { get; set; } = "#f2f2f2"; public string Color { get; set; } = "#f2f2f2";
public bool IsEnabled { get; set; } = true;
public List<PlantFlag> PlantFlags { get; set; } = []; public List<PlantFlag> PlantFlags { get; set; } = [];
} }
@@ -6,7 +6,6 @@ namespace plant_manager.Data.Models
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string? Notes { get; set; } public string? Notes { get; set; }
public bool IsEnabled { get; set; } = true;
public List<Plant> Plants { get; set; } = []; public List<Plant> Plants { get; set; } = [];
} }
@@ -43,9 +43,9 @@ namespace plant_manager.Endpoints
} }
var primaryAction = activity.PrimaryAction(); var primaryAction = activity.PrimaryAction();
if (!activity.IsEnabled || primaryAction is null || activity.Actions.Any(action => !action.CareAction.IsEnabled)) if (primaryAction is null)
{ {
return Results.BadRequest(new { error = "Disabled activities cannot be logged." }); return Results.BadRequest(new { error = "Care activity has no configured actions." });
} }
var performedOn = request.PerformedOn ?? DateOnly.FromDateTime(DateTime.UtcNow); var performedOn = request.PerformedOn ?? DateOnly.FromDateTime(DateTime.UtcNow);
@@ -107,9 +107,9 @@ namespace plant_manager.Endpoints
} }
var primaryAction = activity.PrimaryAction(); var primaryAction = activity.PrimaryAction();
if (!activity.IsEnabled || primaryAction is null || activity.Actions.Any(action => !action.CareAction.IsEnabled)) if (primaryAction is null)
{ {
return Results.BadRequest(new { error = "Disabled activities cannot be logged." }); return Results.BadRequest(new { error = "Care activity has no configured actions." });
} }
var (resources, resourceError) = await BuildLogResources(request.Resources, activity, db); var (resources, resourceError) = await BuildLogResources(request.Resources, activity, db);
@@ -188,11 +188,6 @@ namespace plant_manager.Endpoints
return ([], "One or more resources were not found."); return ([], "One or more resources were not found.");
} }
if (resourcesById.Values.Any(resource => !resource.IsEnabled))
{
return ([], "Disabled resources cannot be logged.");
}
return (requestedResources return (requestedResources
.Select(resource => new ActionLogResource .Select(resource => new ActionLogResource
{ {
@@ -11,9 +11,7 @@ namespace plant_manager.Endpoints
app.MapGet("/api/action-resources", async (ApplicationDbContext db) => app.MapGet("/api/action-resources", async (ApplicationDbContext db) =>
{ {
var resources = await db.ActionResources var resources = await db.ActionResources
.OrderByDescending(resource => resource.IsEnabled) .OrderBy(resource => resource.Name)
.ThenBy(resource => resource.Category)
.ThenBy(resource => resource.Name)
.Select(resource => ActionResourceDto.FromActionResource(resource)) .Select(resource => ActionResourceDto.FromActionResource(resource))
.ToListAsync(); .ToListAsync();
@@ -37,9 +35,7 @@ namespace plant_manager.Endpoints
var resource = new ActionResource var resource = new ActionResource
{ {
Name = name, Name = name,
Category = string.IsNullOrWhiteSpace(request.Category) ? null : request.Category.Trim(), Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim()
Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim(),
IsEnabled = request.IsEnabled
}; };
db.ActionResources.Add(resource); db.ActionResources.Add(resource);
@@ -70,9 +66,7 @@ namespace plant_manager.Endpoints
} }
resource.Name = name; resource.Name = name;
resource.Category = string.IsNullOrWhiteSpace(request.Category) ? null : request.Category.Trim();
resource.Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim(); resource.Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim();
resource.IsEnabled = request.IsEnabled;
await db.SaveChangesAsync(); await db.SaveChangesAsync();
@@ -91,9 +85,7 @@ namespace plant_manager.Endpoints
|| await db.CareActivityActionResources.AnyAsync(activityResource => activityResource.ActionResourceId == id); || await db.CareActivityActionResources.AnyAsync(activityResource => activityResource.ActionResourceId == id);
if (isInUse) if (isInUse)
{ {
resource.IsEnabled = false; return Results.Conflict(new { error = "Resource is in use." });
await db.SaveChangesAsync();
return Results.Conflict(new { error = "Resource is in use, so it was disabled instead of deleted." });
} }
db.ActionResources.Remove(resource); db.ActionResources.Remove(resource);
@@ -11,8 +11,7 @@ namespace plant_manager.Endpoints
app.MapGet("/api/care-actions", async (ApplicationDbContext db) => app.MapGet("/api/care-actions", async (ApplicationDbContext db) =>
{ {
var actions = await db.CareActions var actions = await db.CareActions
.OrderByDescending(action => action.IsEnabled) .OrderBy(action => action.Name)
.ThenBy(action => action.Name)
.Select(action => CareActionDto.FromCareAction(action)) .Select(action => CareActionDto.FromCareAction(action))
.ToListAsync(); .ToListAsync();
@@ -36,8 +35,7 @@ namespace plant_manager.Endpoints
var action = new CareAction var action = new CareAction
{ {
Name = name, Name = name,
Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim(), Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim()
IsEnabled = request.IsEnabled
}; };
db.CareActions.Add(action); db.CareActions.Add(action);
@@ -69,7 +67,6 @@ namespace plant_manager.Endpoints
action.Name = name; action.Name = name;
action.Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim(); action.Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim();
action.IsEnabled = request.IsEnabled;
await db.SaveChangesAsync(); await db.SaveChangesAsync();
@@ -87,9 +84,7 @@ namespace plant_manager.Endpoints
var hasLogs = await db.ActionLogs.AnyAsync(log => log.CareActionId == id); var hasLogs = await db.ActionLogs.AnyAsync(log => log.CareActionId == id);
if (hasLogs) if (hasLogs)
{ {
action.IsEnabled = false; return Results.Conflict(new { error = "Action has care history." });
await db.SaveChangesAsync();
return Results.Conflict(new { error = "Action has care history, so it was disabled instead of deleted." });
} }
db.CareActions.Remove(action); db.CareActions.Remove(action);
@@ -16,8 +16,7 @@ namespace plant_manager.Endpoints
.Include(activity => activity.Actions) .Include(activity => activity.Actions)
.ThenInclude(action => action.Resources) .ThenInclude(action => action.Resources)
.ThenInclude(resource => resource.ActionResource) .ThenInclude(resource => resource.ActionResource)
.OrderByDescending(activity => activity.IsEnabled) .OrderBy(activity => activity.Name)
.ThenBy(activity => activity.Name)
.Select(activity => CareActivityDto.FromCareActivity(activity)) .Select(activity => CareActivityDto.FromCareActivity(activity))
.ToListAsync(); .ToListAsync();
@@ -43,7 +42,6 @@ namespace plant_manager.Endpoints
{ {
Name = name, Name = name,
Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim(), Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim(),
IsEnabled = request.IsEnabled,
Actions = validation.Actions Actions = validation.Actions
}; };
@@ -83,7 +81,6 @@ namespace plant_manager.Endpoints
activity.Name = name; activity.Name = name;
activity.Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim(); activity.Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim();
activity.IsEnabled = request.IsEnabled;
db.CareActivityActions.RemoveRange(activity.Actions); db.CareActivityActions.RemoveRange(activity.Actions);
activity.Actions = validation.Actions; activity.Actions = validation.Actions;
@@ -104,9 +101,7 @@ namespace plant_manager.Endpoints
|| await db.PlantCareSchedules.AnyAsync(schedule => schedule.CareActivityId == id); || await db.PlantCareSchedules.AnyAsync(schedule => schedule.CareActivityId == id);
if (hasHistory) if (hasHistory)
{ {
activity.IsEnabled = false; return Results.Conflict(new { error = "Activity is in use." });
await db.SaveChangesAsync();
return Results.Conflict(new { error = "Activity is in use, so it was disabled instead of deleted." });
} }
db.CareActivities.Remove(activity); db.CareActivities.Remove(activity);
@@ -150,11 +145,6 @@ namespace plant_manager.Endpoints
return ([], "One or more care actions were not found."); return ([], "One or more care actions were not found.");
} }
if (actionsById.Values.Any(action => !action.IsEnabled))
{
return ([], "Disabled actions cannot be used in activities.");
}
var requestedResources = requestedActions var requestedResources = requestedActions
.SelectMany(action => action.Resources ?? []) .SelectMany(action => action.Resources ?? [])
.GroupBy(resource => resource.ActionResourceId) .GroupBy(resource => resource.ActionResourceId)
@@ -181,11 +171,6 @@ namespace plant_manager.Endpoints
return ([], "One or more resources were not found."); return ([], "One or more resources were not found.");
} }
if (resourcesById.Values.Any(resource => !resource.IsEnabled))
{
return ([], "Disabled resources cannot be used in activities.");
}
var activityActions = requestedActions var activityActions = requestedActions
.Select((actionRequest, index) => new CareActivityAction .Select((actionRequest, index) => new CareActivityAction
{ {
+4 -14
View File
@@ -21,10 +21,6 @@ namespace plant_manager.Endpoints
.ThenInclude(activity => activity.Actions) .ThenInclude(activity => activity.Actions)
.ThenInclude(action => action.Resources) .ThenInclude(action => action.Resources)
.ThenInclude(resource => resource.ActionResource) .ThenInclude(resource => resource.ActionResource)
.Where(schedule =>
schedule.IsEnabled
&& schedule.CareActivity.Actions.All(action => action.CareAction.IsEnabled)
&& schedule.CareActivity.IsEnabled)
.OrderBy(schedule => schedule.Plant.Nickname) .OrderBy(schedule => schedule.Plant.Nickname)
.ThenBy(schedule => schedule.CareActivity.Name) .ThenBy(schedule => schedule.CareActivity.Name)
.ToListAsync(); .ToListAsync();
@@ -86,17 +82,16 @@ namespace plant_manager.Endpoints
} }
var primaryAction = activity.PrimaryAction(); var primaryAction = activity.PrimaryAction();
if (!activity.IsEnabled || primaryAction is null || activity.Actions.Any(action => !action.CareAction.IsEnabled)) if (primaryAction is null)
{ {
return Results.BadRequest(new { error = "Disabled activities cannot be logged." }); return Results.BadRequest(new { error = "Care activity has no configured actions." });
} }
var today = DateOnly.FromDateTime(DateTime.UtcNow); var today = DateOnly.FromDateTime(DateTime.UtcNow);
var schedules = await db.PlantCareSchedules var schedules = await db.PlantCareSchedules
.Include(schedule => schedule.Plant) .Include(schedule => schedule.Plant)
.Where(schedule => .Where(schedule =>
schedule.IsEnabled schedule.CareActivityId == activity.Id
&& schedule.CareActivityId == activity.Id
&& plantIds.Contains(schedule.PlantId)) && plantIds.Contains(schedule.PlantId))
.ToListAsync(); .ToListAsync();
var schedulePlantIds = schedules var schedulePlantIds = schedules
@@ -104,7 +99,7 @@ namespace plant_manager.Endpoints
.ToHashSet(); .ToHashSet();
if (schedulePlantIds.Count != plantIds.Count) if (schedulePlantIds.Count != plantIds.Count)
{ {
return Results.BadRequest(new { error = "One or more plants do not have this enabled schedule." }); return Results.BadRequest(new { error = "One or more plants do not have this schedule." });
} }
var latestLogs = await db.ActionLogs var latestLogs = await db.ActionLogs
@@ -169,11 +164,6 @@ namespace plant_manager.Endpoints
return Results.BadRequest(new { error = "One or more resources were not found." }); return Results.BadRequest(new { error = "One or more resources were not found." });
} }
if (resourcesById.Values.Any(resource => !resource.IsEnabled))
{
return Results.BadRequest(new { error = "Disabled resources cannot be logged." });
}
var performedOn = request.PerformedOn ?? today; var performedOn = request.PerformedOn ?? today;
var notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim(); var notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim();
var logs = schedules var logs = schedules
@@ -31,9 +31,9 @@ namespace plant_manager.Endpoints
} }
var primaryAction = activity.PrimaryAction(); var primaryAction = activity.PrimaryAction();
if (!activity.IsEnabled || primaryAction is null || activity.Actions.Any(action => !action.CareAction.IsEnabled)) if (primaryAction is null)
{ {
return Results.BadRequest(new { error = "Disabled activities cannot be scheduled." }); return Results.BadRequest(new { error = "Care activity has no configured actions." });
} }
var plants = await db.Plants var plants = await db.Plants
@@ -73,7 +73,6 @@ namespace plant_manager.Endpoints
schedule.CareActionId = primaryAction.Id; schedule.CareActionId = primaryAction.Id;
schedule.CareActivityId = activity.Id; schedule.CareActivityId = activity.Id;
ApplyRecurrence(schedule, recurrence); ApplyRecurrence(schedule, recurrence);
schedule.IsEnabled = request.IsEnabled;
} }
await db.SaveChangesAsync(); await db.SaveChangesAsync();
+3 -7
View File
@@ -206,8 +206,7 @@ namespace plant_manager.Endpoints
null, null,
"after", "after",
null, null,
12, 12)
true)
]; ];
} }
@@ -234,11 +233,9 @@ namespace plant_manager.Endpoints
} }
if (activitiesById.Values.Any(activity => if (activitiesById.Values.Any(activity =>
!activity.IsEnabled activity.PrimaryAction() is null))
|| activity.PrimaryAction() is null
|| activity.Actions.Any(action => !action.CareAction.IsEnabled)))
{ {
return "Disabled activities cannot be scheduled."; return "Care activities must have at least one action.";
} }
var requestedActivityIds = activityIds.ToHashSet(); var requestedActivityIds = activityIds.ToHashSet();
@@ -280,7 +277,6 @@ namespace plant_manager.Endpoints
requestedSchedule.EndsAfterOccurrences, requestedSchedule.EndsAfterOccurrences,
requestedSchedule.ScheduledFor, requestedSchedule.ScheduledFor,
requestedSchedule.EveryDays)); requestedSchedule.EveryDays));
schedule.IsEnabled = requestedSchedule.IsEnabled;
} }
return null; return null;
+5 -10
View File
@@ -11,8 +11,7 @@ namespace plant_manager.Endpoints
app.MapGet("/api/plant-flags", async (ApplicationDbContext db) => app.MapGet("/api/plant-flags", async (ApplicationDbContext db) =>
{ {
var definitions = await db.PlantFlagDefinitions var definitions = await db.PlantFlagDefinitions
.OrderByDescending(definition => definition.IsEnabled) .OrderBy(definition => definition.Name)
.ThenBy(definition => definition.Name)
.Select(definition => PlantFlagDefinitionDto.FromDefinition(definition)) .Select(definition => PlantFlagDefinitionDto.FromDefinition(definition))
.ToListAsync(); .ToListAsync();
@@ -37,8 +36,7 @@ namespace plant_manager.Endpoints
var definition = new PlantFlagDefinition var definition = new PlantFlagDefinition
{ {
Name = name, Name = name,
Color = NormalizeColor(request.Color), Color = NormalizeColor(request.Color)
IsEnabled = request.IsEnabled
}; };
db.PlantFlagDefinitions.Add(definition); db.PlantFlagDefinitions.Add(definition);
@@ -70,7 +68,6 @@ namespace plant_manager.Endpoints
definition.Name = name; definition.Name = name;
definition.Color = NormalizeColor(request.Color); definition.Color = NormalizeColor(request.Color);
definition.IsEnabled = request.IsEnabled;
await db.SaveChangesAsync(); await db.SaveChangesAsync();
@@ -88,9 +85,7 @@ namespace plant_manager.Endpoints
var isUsed = await db.PlantFlags.AnyAsync(flag => flag.PlantFlagDefinitionId == id); var isUsed = await db.PlantFlags.AnyAsync(flag => flag.PlantFlagDefinitionId == id);
if (isUsed) if (isUsed)
{ {
definition.IsEnabled = false; return Results.Conflict(new { error = "Flag is assigned to plants." });
await db.SaveChangesAsync();
return Results.Conflict(new { error = "Flag is assigned to plants, so it was disabled instead of deleted." });
} }
db.PlantFlagDefinitions.Remove(definition); db.PlantFlagDefinitions.Remove(definition);
@@ -111,9 +106,9 @@ namespace plant_manager.Endpoints
} }
var definition = await db.PlantFlagDefinitions.FindAsync(request.PlantFlagDefinitionId); var definition = await db.PlantFlagDefinitions.FindAsync(request.PlantFlagDefinitionId);
if (definition is null || !definition.IsEnabled) if (definition is null)
{ {
return Results.BadRequest(new { error = "Flag was not found or is disabled." }); return Results.BadRequest(new { error = "Flag was not found." });
} }
var hasActiveFlag = await db.PlantFlags.AnyAsync(flag => var hasActiveFlag = await db.PlantFlags.AnyAsync(flag =>
@@ -11,8 +11,7 @@ namespace plant_manager.Endpoints
app.MapGet("/api/plant-locations", async (ApplicationDbContext db) => app.MapGet("/api/plant-locations", async (ApplicationDbContext db) =>
{ {
var locations = await db.PlantLocations var locations = await db.PlantLocations
.OrderByDescending(location => location.IsEnabled) .OrderBy(location => location.Name)
.ThenBy(location => location.Name)
.Select(location => PlantLocationDto.FromLocation(location)) .Select(location => PlantLocationDto.FromLocation(location))
.ToListAsync(); .ToListAsync();
@@ -36,8 +35,7 @@ namespace plant_manager.Endpoints
var location = new PlantLocation var location = new PlantLocation
{ {
Name = name, Name = name,
Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim(), Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim()
IsEnabled = request.IsEnabled
}; };
db.PlantLocations.Add(location); db.PlantLocations.Add(location);
@@ -69,7 +67,6 @@ namespace plant_manager.Endpoints
location.Name = name; location.Name = name;
location.Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim(); location.Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim();
location.IsEnabled = request.IsEnabled;
await db.SaveChangesAsync(); await db.SaveChangesAsync();
@@ -87,9 +84,7 @@ namespace plant_manager.Endpoints
var isInUse = await db.Plants.AnyAsync(plant => plant.LocationId == id); var isInUse = await db.Plants.AnyAsync(plant => plant.LocationId == id);
if (isInUse) if (isInUse)
{ {
location.IsEnabled = false; return Results.Conflict(new { error = "Location is assigned to one or more plants." });
await db.SaveChangesAsync();
return Results.Conflict(new { error = "Location is in use, so it was disabled instead of deleted." });
} }
db.PlantLocations.Remove(location); db.PlantLocations.Remove(location);