762 lines
20 KiB
TypeScript
762 lines
20 KiB
TypeScript
import { useState } from 'react';
|
|
import {
|
|
assignPlantFlag,
|
|
completeCareTasksBulk,
|
|
createActionResource,
|
|
createCareAction,
|
|
createCareActivity,
|
|
createPlant,
|
|
createPlantFlag,
|
|
createPlantGroup,
|
|
createPlantLocation,
|
|
createPlantTaxon,
|
|
createRecipe,
|
|
deleteActionResource,
|
|
deleteCareAction,
|
|
deleteCareActivity,
|
|
deletePlant,
|
|
deletePlantFlag,
|
|
deletePlantGroup,
|
|
deletePlantLocation,
|
|
deletePlantTaxon,
|
|
deleteRecipe,
|
|
downloadSpreadsheetExport,
|
|
importPlantTaxon,
|
|
removePlantCareSchedulesBulk,
|
|
removePlantFlagAssignment,
|
|
resolvePlantFlag,
|
|
savePlantCareSchedulesBulk,
|
|
searchPlantInfo,
|
|
updateActionResource,
|
|
updateCareAction,
|
|
updateCareActivity,
|
|
updatePlant,
|
|
updatePlantFlag,
|
|
updatePlantGroup,
|
|
updatePlantLocation,
|
|
updatePlantTaxon,
|
|
updateRecipe,
|
|
} from './api';
|
|
import type { CareTask, Plant, PlantFlag, PlantInfoSearchResult } from './domain';
|
|
import {
|
|
emptyBulkScheduleForm,
|
|
emptyPlantFlagForm,
|
|
toActionPayload,
|
|
toActivityPayload,
|
|
toBulkSchedulePayload,
|
|
toFlagDefinitionPayload,
|
|
toLocationPayload,
|
|
toPlantGroupPayload,
|
|
toPlantFlagPayload,
|
|
toPlantPayload,
|
|
toRecipePayload,
|
|
toResourcePayload,
|
|
toTaxonPayload,
|
|
} from './form-state';
|
|
import type { useAppEditors } from './use-app-editors';
|
|
import type { useDashboardData } from './use-dashboard-data';
|
|
|
|
type Editors = ReturnType<typeof useAppEditors>;
|
|
type DashboardData = ReturnType<typeof useDashboardData>;
|
|
|
|
type AppActionOptions = {
|
|
editors: Editors;
|
|
loadCareModel: DashboardData['loadCareModel'];
|
|
loadFlagsAndPlants: DashboardData['loadFlagsAndPlants'];
|
|
loadGroupsAndPlants: DashboardData['loadGroupsAndPlants'];
|
|
loadLocations: DashboardData['loadLocations'];
|
|
loadPlants: DashboardData['loadPlants'];
|
|
loadPlantsAndCareTasks: DashboardData['loadPlantsAndCareTasks'];
|
|
loadRecipesAndResources: DashboardData['loadRecipesAndResources'];
|
|
loadTaxaAndPlants: DashboardData['loadTaxaAndPlants'];
|
|
setError: DashboardData['setError'];
|
|
};
|
|
|
|
export function useAppActions({
|
|
editors,
|
|
loadCareModel,
|
|
loadFlagsAndPlants,
|
|
loadGroupsAndPlants,
|
|
loadLocations,
|
|
loadPlants,
|
|
loadPlantsAndCareTasks,
|
|
loadRecipesAndResources,
|
|
loadTaxaAndPlants,
|
|
setError,
|
|
}: AppActionOptions) {
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [isExporting, setIsExporting] = useState(false);
|
|
const [isSearchingPlantInfo, setIsSearchingPlantInfo] = useState(false);
|
|
const [plantInfoQuery, setPlantInfoQuery] = useState('');
|
|
const [plantInfoResults, setPlantInfoResults] = useState<PlantInfoSearchResult[]>([]);
|
|
const [hasSearchedPlantInfo, setHasSearchedPlantInfo] = useState(false);
|
|
|
|
async function savePlant() {
|
|
if (!editors.form.nickname.trim()) {
|
|
setError('Plant name is required.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = editors.editingPlantId === null
|
|
? toPlantPayload(editors.form)
|
|
: {
|
|
...toPlantPayload(editors.form),
|
|
taxonId: editors.activePlant?.taxonId ?? null,
|
|
locationId: editors.activePlant?.locationId ?? null,
|
|
};
|
|
if (editors.editingPlantId === null) {
|
|
await createPlant(payload);
|
|
} else {
|
|
await updatePlant(editors.editingPlantId, payload);
|
|
}
|
|
editors.cancelEditing();
|
|
await loadPlantsAndCareTasks();
|
|
} catch {
|
|
setError('Could not save the plant.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function updateManagedPlant(nextValues: {
|
|
taxonId?: number | null;
|
|
locationId?: number | null;
|
|
}) {
|
|
if (!editors.selectedPlant) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await updatePlant(editors.selectedPlant.id, {
|
|
nickname: editors.selectedPlant.nickname,
|
|
birthday: editors.selectedPlant.birthday,
|
|
taxonId: nextValues.taxonId === undefined ? editors.selectedPlant.taxonId : nextValues.taxonId,
|
|
locationId: nextValues.locationId === undefined ? editors.selectedPlant.locationId : nextValues.locationId,
|
|
careSchedules: null,
|
|
});
|
|
await loadPlants();
|
|
} catch {
|
|
setError('Could not update the plant assignment.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
function setManagedPlantTaxon(taxonId: string) {
|
|
void updateManagedPlant({ taxonId: taxonId ? Number(taxonId) : null });
|
|
}
|
|
|
|
function setManagedPlantLocation(locationId: string) {
|
|
void updateManagedPlant({ locationId: locationId ? Number(locationId) : null });
|
|
}
|
|
|
|
async function removePlant(plant: Plant) {
|
|
const confirmed = window.confirm(`Delete ${plant.nickname}? This also removes its care log.`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await deletePlant(plant.id);
|
|
if (editors.selectedPlantId === plant.id) {
|
|
editors.setSelectedPlantId(null);
|
|
}
|
|
if (editors.editingPlantId === plant.id) {
|
|
editors.cancelEditing();
|
|
}
|
|
await loadPlantsAndCareTasks();
|
|
} catch {
|
|
setError('Could not delete the plant.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function exportSpreadsheet() {
|
|
setIsExporting(true);
|
|
try {
|
|
setError(null);
|
|
await downloadSpreadsheetExport();
|
|
} catch {
|
|
setError('Could not export the spreadsheet.');
|
|
} finally {
|
|
setIsExporting(false);
|
|
}
|
|
}
|
|
|
|
async function saveTaxon() {
|
|
if (!editors.taxonForm.name.trim() || !editors.taxonForm.genus.trim() || !editors.taxonForm.species.trim()) {
|
|
setError('Name, genus, and species are required.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = toTaxonPayload(editors.taxonForm);
|
|
if (editors.editingTaxonId === null) {
|
|
await createPlantTaxon(payload);
|
|
} else {
|
|
await updatePlantTaxon(editors.editingTaxonId, payload);
|
|
}
|
|
editors.cancelEditingTaxon();
|
|
await loadTaxaAndPlants();
|
|
} catch {
|
|
setError('Could not save the taxon.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function searchTaxonInfo(queryOverride?: string) {
|
|
const query = queryOverride ?? plantInfoQuery;
|
|
setPlantInfoQuery(query);
|
|
|
|
if (query.trim().length < 2) {
|
|
setError('Search needs at least 2 characters.');
|
|
return;
|
|
}
|
|
|
|
setIsSearchingPlantInfo(true);
|
|
try {
|
|
setError(null);
|
|
setHasSearchedPlantInfo(true);
|
|
setPlantInfoResults(await searchPlantInfo(query));
|
|
} catch {
|
|
setError('Could not search offline plant info.');
|
|
} finally {
|
|
setIsSearchingPlantInfo(false);
|
|
}
|
|
}
|
|
|
|
async function importTaxonFromPlantInfo(result: PlantInfoSearchResult) {
|
|
setIsSaving(true);
|
|
try {
|
|
await importPlantTaxon(result);
|
|
setPlantInfoResults([]);
|
|
setPlantInfoQuery('');
|
|
setHasSearchedPlantInfo(false);
|
|
await loadTaxaAndPlants();
|
|
} catch {
|
|
setError('Could not import the GBIF taxon.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removeTaxon(taxon: Parameters<typeof editors.startEditingTaxon>[0]) {
|
|
const confirmed = window.confirm(`Delete ${taxon.name}? Taxa used by plants cannot be deleted.`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await deletePlantTaxon(taxon.id);
|
|
if (editors.editingTaxonId === taxon.id) {
|
|
editors.cancelEditingTaxon();
|
|
}
|
|
await loadTaxaAndPlants();
|
|
} catch {
|
|
setError('Could not delete the taxon. It may still be used by a plant.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function saveLocation() {
|
|
if (!editors.locationForm.name.trim()) {
|
|
setError('Location name is required.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = toLocationPayload(editors.locationForm);
|
|
if (editors.editingLocationId === null) {
|
|
await createPlantLocation(payload);
|
|
} else {
|
|
await updatePlantLocation(editors.editingLocationId, payload);
|
|
}
|
|
editors.cancelEditingLocation();
|
|
await loadLocations();
|
|
} catch {
|
|
setError('Could not save the location.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removeLocation(location: Parameters<typeof editors.startEditingLocation>[0]) {
|
|
const confirmed = window.confirm(`Delete ${location.name}? Locations assigned to plants cannot be deleted.`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await deletePlantLocation(location.id);
|
|
if (editors.editingLocationId === location.id) {
|
|
editors.cancelEditingLocation();
|
|
}
|
|
await loadLocations();
|
|
} catch {
|
|
await loadLocations();
|
|
setError('Could not delete the location. It may still be assigned to a plant.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function savePlantGroup() {
|
|
if (!editors.plantGroupForm.name.trim()) {
|
|
setError('Group name is required.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = toPlantGroupPayload(editors.plantGroupForm);
|
|
if (editors.editingPlantGroupId === null) {
|
|
await createPlantGroup(payload);
|
|
} else {
|
|
await updatePlantGroup(editors.editingPlantGroupId, payload);
|
|
}
|
|
editors.cancelEditingPlantGroup();
|
|
await loadGroupsAndPlants();
|
|
} catch {
|
|
setError('Could not save the plant group.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removePlantGroup(group: Parameters<typeof editors.startEditingPlantGroup>[0]) {
|
|
const confirmed = window.confirm(`Delete ${group.name}?`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await deletePlantGroup(group.id);
|
|
if (editors.editingPlantGroupId === group.id) {
|
|
editors.cancelEditingPlantGroup();
|
|
}
|
|
await loadGroupsAndPlants();
|
|
} catch {
|
|
setError('Could not delete the plant group.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function saveAction() {
|
|
if (!editors.actionForm.name.trim()) {
|
|
setError('Action name is required.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = toActionPayload(editors.actionForm);
|
|
if (editors.editingActionId === null) {
|
|
await createCareAction(payload);
|
|
} else {
|
|
await updateCareAction(editors.editingActionId, payload);
|
|
}
|
|
editors.cancelEditingAction();
|
|
await loadCareModel();
|
|
} catch {
|
|
setError('Could not save the action.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removeAction(action: Parameters<typeof editors.startEditingAction>[0]) {
|
|
const confirmed = window.confirm(`Delete ${action.name}? Actions with care history cannot be deleted.`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await deleteCareAction(action.id);
|
|
if (editors.editingActionId === action.id) {
|
|
editors.cancelEditingAction();
|
|
}
|
|
await loadCareModel();
|
|
} catch {
|
|
await loadCareModel();
|
|
setError('Could not delete the action. It may still have care history.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function saveResource() {
|
|
if (!editors.resourceForm.name.trim()) {
|
|
setError('Resource name is required.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = toResourcePayload(editors.resourceForm);
|
|
if (editors.editingResourceId === null) {
|
|
await createActionResource(payload);
|
|
} else {
|
|
await updateActionResource(editors.editingResourceId, payload);
|
|
}
|
|
editors.cancelEditingResource();
|
|
await loadCareModel();
|
|
await loadRecipesAndResources();
|
|
} catch {
|
|
setError('Could not save the resource.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removeResource(resource: Parameters<typeof editors.startEditingResource>[0]) {
|
|
const confirmed = window.confirm(`Delete ${resource.name}?`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await deleteActionResource(resource.id);
|
|
if (editors.editingResourceId === resource.id) {
|
|
editors.cancelEditingResource();
|
|
}
|
|
await loadCareModel();
|
|
await loadRecipesAndResources();
|
|
} catch {
|
|
setError('Could not delete the resource.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function saveRecipe() {
|
|
if (!editors.recipeForm.name.trim() || !editors.recipeForm.type.trim() || !editors.recipeForm.outputResourceName.trim() || editors.recipeForm.components.length === 0) {
|
|
setError('Recipe name, type, produced resource, and at least one component are required.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = toRecipePayload(editors.recipeForm);
|
|
if (editors.editingRecipeId === null) {
|
|
await createRecipe(payload);
|
|
} else {
|
|
await updateRecipe(editors.editingRecipeId, payload);
|
|
}
|
|
editors.cancelEditingRecipe();
|
|
await loadRecipesAndResources();
|
|
} catch {
|
|
setError('Could not save the recipe.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removeRecipe(recipe: Parameters<typeof editors.startEditingRecipe>[0]) {
|
|
const confirmed = window.confirm(`Delete ${recipe.name}?`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await deleteRecipe(recipe.id);
|
|
if (editors.selectedRecipeId === recipe.id) {
|
|
editors.setSelectedRecipeId(null);
|
|
}
|
|
if (editors.editingRecipeId === recipe.id) {
|
|
editors.cancelEditingRecipe();
|
|
}
|
|
await loadRecipesAndResources();
|
|
} catch {
|
|
setError('Could not delete the recipe.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function saveActivity() {
|
|
if (!editors.activityForm.name.trim() || editors.activityForm.actions.length === 0) {
|
|
setError('Activity name and at least one action are required.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = toActivityPayload(editors.activityForm);
|
|
if (editors.editingActivityId === null) {
|
|
await createCareActivity(payload);
|
|
} else {
|
|
await updateCareActivity(editors.editingActivityId, payload);
|
|
}
|
|
editors.cancelEditingActivity();
|
|
await loadCareModel();
|
|
} catch {
|
|
setError('Could not save the activity.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removeActivity(activity: Parameters<typeof editors.startEditingActivity>[0]) {
|
|
const confirmed = window.confirm(`Delete ${activity.name}? Activities in use cannot be deleted.`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await deleteCareActivity(activity.id);
|
|
if (editors.editingActivityId === activity.id) {
|
|
editors.cancelEditingActivity();
|
|
}
|
|
await loadCareModel();
|
|
} catch {
|
|
await loadCareModel();
|
|
setError('Could not delete the activity. It may still be in use.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function saveFlagDefinition() {
|
|
if (!editors.flagDefinitionForm.name.trim()) {
|
|
setError('Flag name is required.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
const payload = toFlagDefinitionPayload(editors.flagDefinitionForm);
|
|
if (editors.editingFlagDefinitionId === null) {
|
|
await createPlantFlag(payload);
|
|
} else {
|
|
await updatePlantFlag(editors.editingFlagDefinitionId, payload);
|
|
}
|
|
editors.cancelEditingFlagDefinition();
|
|
await loadFlagsAndPlants();
|
|
} catch {
|
|
setError('Could not save the plant flag.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function saveBulkSchedule() {
|
|
if (!editors.bulkScheduleForm.careActivityId || editors.bulkScheduleForm.plantIds.length === 0) {
|
|
setError('Select an activity and at least one plant.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await savePlantCareSchedulesBulk(toBulkSchedulePayload(editors.bulkScheduleForm));
|
|
editors.setBulkScheduleForm(emptyBulkScheduleForm);
|
|
await loadPlantsAndCareTasks();
|
|
} catch {
|
|
setError('Could not apply the care schedule.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removeBulkSchedule() {
|
|
if (!editors.bulkScheduleForm.careActivityId || editors.bulkScheduleForm.plantIds.length === 0) {
|
|
setError('Select an activity and at least one plant.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await removePlantCareSchedulesBulk(toBulkSchedulePayload(editors.bulkScheduleForm));
|
|
editors.setBulkScheduleForm(emptyBulkScheduleForm);
|
|
await loadPlantsAndCareTasks();
|
|
} catch {
|
|
setError('Could not remove the care schedule.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removeFlagDefinition(flag: Parameters<typeof editors.startEditingFlagDefinition>[0]) {
|
|
const confirmed = window.confirm(`Delete ${flag.name}? Flags assigned to plants cannot be deleted.`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await deletePlantFlag(flag.id);
|
|
if (editors.editingFlagDefinitionId === flag.id) {
|
|
editors.cancelEditingFlagDefinition();
|
|
}
|
|
await loadFlagsAndPlants();
|
|
} catch {
|
|
await loadFlagsAndPlants();
|
|
setError('Could not delete the flag. It may still be assigned to a plant.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function assignFlagToSelectedPlant() {
|
|
if (!editors.selectedPlantId || !editors.plantFlagForm.plantFlagDefinitionId) {
|
|
setError('Select a plant and flag first.');
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await assignPlantFlag(editors.selectedPlantId, toPlantFlagPayload(editors.plantFlagForm));
|
|
editors.setPlantFlagForm(emptyPlantFlagForm);
|
|
await loadPlants();
|
|
} catch {
|
|
setError('Could not attach the plant flag.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function resolveAssignedPlantFlag(flag: PlantFlag) {
|
|
if (!editors.selectedPlantId) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await resolvePlantFlag(editors.selectedPlantId, flag.id);
|
|
await loadPlants();
|
|
} catch {
|
|
setError('Could not resolve the plant flag.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function removeAssignedPlantFlag(flag: PlantFlag) {
|
|
if (!editors.selectedPlantId) {
|
|
return;
|
|
}
|
|
|
|
const confirmed = window.confirm(`Remove ${flag.name} from this plant?`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await removePlantFlagAssignment(editors.selectedPlantId, flag.id);
|
|
await loadPlants();
|
|
} catch {
|
|
setError('Could not remove the plant flag.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function completeTask(task: CareTask) {
|
|
setIsSaving(true);
|
|
try {
|
|
await completeCareTasksBulk({
|
|
careActivityId: task.careActivityId,
|
|
plantIds: [task.plantId],
|
|
performedOn: getTodayInputDate(),
|
|
notes: '',
|
|
resources: [],
|
|
});
|
|
await loadPlantsAndCareTasks();
|
|
} catch {
|
|
setError('Could not log the care task.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
async function completeBulkTasks(tasks: CareTask[]) {
|
|
const dueTasks = tasks.filter((task) => task.status === 'due');
|
|
if (dueTasks.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const action = dueTasks[0].action;
|
|
const confirmed = window.confirm(`Log ${action} for ${dueTasks.length} due plants?`);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
try {
|
|
await completeCareTasksBulk({
|
|
careActivityId: dueTasks[0].careActivityId,
|
|
plantIds: dueTasks.map((task) => task.plantId),
|
|
performedOn: getTodayInputDate(),
|
|
notes: '',
|
|
resources: [],
|
|
});
|
|
await loadPlantsAndCareTasks();
|
|
} catch {
|
|
setError('Could not log the due tasks.');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
return {
|
|
assignFlagToSelectedPlant,
|
|
completeBulkTasks,
|
|
completeTask,
|
|
exportSpreadsheet,
|
|
hasSearchedPlantInfo,
|
|
isExporting,
|
|
isSearchingPlantInfo,
|
|
isSaving,
|
|
importTaxonFromPlantInfo,
|
|
plantInfoQuery,
|
|
plantInfoResults,
|
|
removeAction,
|
|
removeActivity,
|
|
removeAssignedPlantFlag,
|
|
removeBulkSchedule,
|
|
removeFlagDefinition,
|
|
removeLocation,
|
|
removePlant,
|
|
removePlantGroup,
|
|
removeRecipe,
|
|
removeResource,
|
|
removeTaxon,
|
|
resolveAssignedPlantFlag,
|
|
saveAction,
|
|
saveActivity,
|
|
saveBulkSchedule,
|
|
saveFlagDefinition,
|
|
saveLocation,
|
|
savePlant,
|
|
savePlantGroup,
|
|
saveRecipe,
|
|
saveResource,
|
|
saveTaxon,
|
|
searchTaxonInfo,
|
|
setManagedPlantLocation,
|
|
setManagedPlantTaxon,
|
|
setPlantInfoQuery,
|
|
};
|
|
}
|
|
|
|
function getTodayInputDate() {
|
|
return new Date().toISOString().slice(0, 10);
|
|
}
|