diff --git a/packages/webapp/src/components/Animals/Inventory/index.tsx b/packages/webapp/src/components/Animals/Inventory/index.tsx index 63dafca6a3..bf8cefbd13 100644 --- a/packages/webapp/src/components/Animals/Inventory/index.tsx +++ b/packages/webapp/src/components/Animals/Inventory/index.tsx @@ -53,6 +53,7 @@ const PureAnimalInventory = ({ onSelectInventory, handleSelectAllClick, selectedIds, + onRowClick, totalInventoryCount, isFilterActive, clearFilters, @@ -67,6 +68,7 @@ const PureAnimalInventory = ({ searchProps: SearchProps; onSelectInventory: (event: ChangeEvent, row: AnimalInventory) => void; handleSelectAllClick: (event: ChangeEvent) => void; + onRowClick: (event: ChangeEvent, row: AnimalInventory) => void; selectedIds: string[]; totalInventoryCount: number; isFilterActive: boolean; @@ -139,6 +141,7 @@ const PureAnimalInventory = ({ maxHeight={tableMaxHeight} spacerRowHeight={isDesktop ? 96 : 120} headerClass={styles.headerClass} + onRowClick={onRowClick} /> ) : ( void; } -export const WithPageTitle = ({ +export const MultiStepWithPageTitle = ({ children, steps, activeStepIndex, cancelModalTitle, onGoBack, onCancel, -}: WithPageTitleProps) => { +}: MultiStepWithPageTitleProps) => { const [showConfirmCancelModal, setShowConfirmCancelModal] = useState(false); const progressBarValue = useMemo( diff --git a/packages/webapp/src/components/Form/MultiStepForm/WithStepperProgressBar.tsx b/packages/webapp/src/components/Form/ContextForm/MultiStepWithStepperProgressBar.tsx similarity index 96% rename from packages/webapp/src/components/Form/MultiStepForm/WithStepperProgressBar.tsx rename to packages/webapp/src/components/Form/ContextForm/MultiStepWithStepperProgressBar.tsx index 3c6d550681..0ae8bc4b6b 100644 --- a/packages/webapp/src/components/Form/MultiStepForm/WithStepperProgressBar.tsx +++ b/packages/webapp/src/components/Form/ContextForm/MultiStepWithStepperProgressBar.tsx @@ -23,7 +23,7 @@ import FixedHeaderContainer from '../../Animals/FixedHeaderContainer'; import CancelFlowModal from '../../Modals/CancelFlowModal'; import styles from './styles.module.scss'; -interface WithStepperProgressBarProps { +interface MultiStepWithStepperProgressBarProps { children: ReactNode; history: History; steps: { formContent: ReactNode; title: string }[]; @@ -49,7 +49,7 @@ interface WithStepperProgressBarProps { setFormResultData: (data: any) => void; } -export const WithStepperProgressBar = ({ +export const MultiStepWithStepperProgressBar = ({ children, history, steps, @@ -66,7 +66,7 @@ export const WithStepperProgressBar = ({ handleSubmit, formState: { isValid, isDirty }, setFormResultData, -}: WithStepperProgressBarProps) => { +}: MultiStepWithStepperProgressBarProps) => { const [transition, setTransition] = useState<{ unblock?: () => void; retry?: () => void }>({ unblock: undefined, retry: undefined, diff --git a/packages/webapp/src/components/Form/ContextForm/SingleStepWithReadonlyEdit.tsx b/packages/webapp/src/components/Form/ContextForm/SingleStepWithReadonlyEdit.tsx new file mode 100644 index 0000000000..3ff54d031f --- /dev/null +++ b/packages/webapp/src/components/Form/ContextForm/SingleStepWithReadonlyEdit.tsx @@ -0,0 +1,155 @@ +/* + * Copyright 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +import { ReactNode, useEffect, useState } from 'react'; +import { UseFormHandleSubmit, FieldValues, FormState } from 'react-hook-form'; +import { History } from 'history'; +import FloatingContainer from '../../FloatingContainer'; +import FormNavigationButtons from '../FormNavigationButtons'; +import CancelFlowModal from '../../Modals/CancelFlowModal'; +import styles from './styles.module.scss'; + +interface WithReadonlyEditProps { + children: ReactNode; + history: History; + steps: { formContent: ReactNode; title: string }[]; + activeStepIndex: number; + cancelModalTitle: string; + isCompactSideMenu: boolean; + hasSummaryWithinForm: boolean; + onSave: ( + data: FieldValues, + onGoForward: () => void, + setFormResultData?: (data: any) => void, + ) => void; + onGoBack: () => void; + onCancel: () => void; + onGoForward: () => void; + formState: FormState; + handleSubmit: UseFormHandleSubmit; + setFormResultData: (data: any) => void; + isEditing: boolean; + setIsEditing: React.Dispatch>; + checkIsFormDirty: boolean; + setCheckIsFormDirty: React.Dispatch>; +} + +export const WithReadonlyEdit = ({ + children, + history, + steps, + activeStepIndex, + cancelModalTitle, + isCompactSideMenu, + hasSummaryWithinForm, + onSave, + onGoBack, + onCancel, + onGoForward, + handleSubmit, + formState: { isValid, isDirty }, + setFormResultData, + isEditing, + setIsEditing, + checkIsFormDirty, + setCheckIsFormDirty, +}: WithReadonlyEditProps) => { + const [transition, setTransition] = useState<{ unblock?: () => void; retry?: () => void }>({ + unblock: undefined, + retry: undefined, + }); + + const isSummaryPage = hasSummaryWithinForm && activeStepIndex === steps.length - 1; + + // Block the page transition + // https://github.com/remix-run/history/blob/dev/docs/blocking-transitions.md + useEffect(() => { + if (isSummaryPage || !isDirty) { + return; + } + const unblock = history.block((tx) => { + setTransition({ unblock, retry: tx.retry }); + }); + + return () => unblock(); + }, [isSummaryPage, isDirty, history]); + + // Also manage the confirmation modal manually + const [showConfirmationModal, setShowConfirmationModal] = useState(false); + + useEffect(() => { + if (isEditing && isDirty && checkIsFormDirty) { + setShowConfirmationModal(true); + } else if (isEditing && !isDirty && checkIsFormDirty) { + setIsEditing(false); + setCheckIsFormDirty(false); + } + }, [checkIsFormDirty]); + + const isFinalStep = + (!hasSummaryWithinForm && activeStepIndex === steps.length - 1) || + (hasSummaryWithinForm && activeStepIndex === steps.length - 2); + + const onContinue = () => { + if (isFinalStep) { + handleSubmit((data: FieldValues) => onSave(data, onGoForward, setFormResultData))(); + return; + } + onGoForward(); + }; + + const handleDismissModal = () => { + setTransition({ unblock: undefined, retry: undefined }); + setShowConfirmationModal(false); + setCheckIsFormDirty(false); // to re-trigger check when cancelling again + }; + + const handleCancel = () => { + try { + transition.unblock?.(); + transition.retry?.(); + } catch (e) { + console.error(`Error during canceling ${cancelModalTitle}: ${e}`); + } + setShowConfirmationModal(false); + setIsEditing(false); + setCheckIsFormDirty(false); + }; + + return ( + <> +
{children}
+ {isEditing && ( + + + + )} + {(transition.unblock || showConfirmationModal) && ( + + )} + + ); +}; diff --git a/packages/webapp/src/components/Form/MultiStepForm/index.jsx b/packages/webapp/src/components/Form/ContextForm/index.jsx similarity index 82% rename from packages/webapp/src/components/Form/MultiStepForm/index.jsx rename to packages/webapp/src/components/Form/ContextForm/index.jsx index 0637745eeb..ea4f3dd013 100644 --- a/packages/webapp/src/components/Form/MultiStepForm/index.jsx +++ b/packages/webapp/src/components/Form/ContextForm/index.jsx @@ -16,24 +16,28 @@ import { useState, useMemo } from 'react'; import PropTypes from 'prop-types'; import { FormProvider, useForm } from 'react-hook-form'; -import { WithPageTitle } from './WithPageTitle'; -import { WithStepperProgressBar } from './WithStepperProgressBar'; +import { MultiStepWithPageTitle } from './MultiStepWithPageTitle'; +import { MultiStepWithStepperProgressBar } from './MultiStepWithStepperProgressBar'; +import { WithReadonlyEdit } from './SingleStepWithReadonlyEdit'; export const VARIANT = { PAGE_TITLE: 'page_title', STEPPER_PROGRESS_BAR: 'stepper_progress_bar', + READONLY_EDIT: 'readonly_edit', }; const components = { - [VARIANT.PAGE_TITLE]: (props) => , - [VARIANT.STEPPER_PROGRESS_BAR]: (props) => , + [VARIANT.PAGE_TITLE]: (props) => , + [VARIANT.STEPPER_PROGRESS_BAR]: (props) => , + [VARIANT.READONLY_EDIT]: (props) => , }; -export const MultiStepForm = ({ +export const ContextForm = ({ history, getSteps, defaultFormValues, variant = VARIANT.PAGE_TITLE, + isEditing = false, ...props }) => { const [activeStepIndex, setActiveStepIndex] = useState(0); @@ -83,6 +87,7 @@ export const MultiStepForm = ({ onCancel={onCancel} onGoForward={onGoForward} setFormResultData={setFormResultData} + isEditing={isEditing} {...form} {...props} > @@ -92,13 +97,14 @@ export const MultiStepForm = ({ form={form} formResultData={formResultData} history={history} + isEditing={isEditing} /> ); }; -MultiStepForm.propTypes = { +ContextForm.propTypes = { variant: PropTypes.oneOf(Object.values(VARIANT)), history: PropTypes.object, getSteps: PropTypes.func, diff --git a/packages/webapp/src/components/Form/MultiStepForm/styles.module.scss b/packages/webapp/src/components/Form/ContextForm/styles.module.scss similarity index 100% rename from packages/webapp/src/components/Form/MultiStepForm/styles.module.scss rename to packages/webapp/src/components/Form/ContextForm/styles.module.scss diff --git a/packages/webapp/src/components/RouterTab/StateTab/index.jsx b/packages/webapp/src/components/RouterTab/StateTab/index.tsx similarity index 79% rename from packages/webapp/src/components/RouterTab/StateTab/index.jsx rename to packages/webapp/src/components/RouterTab/StateTab/index.tsx index e9f3345101..8e1db81527 100644 --- a/packages/webapp/src/components/RouterTab/StateTab/index.jsx +++ b/packages/webapp/src/components/RouterTab/StateTab/index.tsx @@ -1,8 +1,21 @@ +import React from 'react'; import PropTypes from 'prop-types'; import { Semibold } from '../../Typography'; import styles from '../styles.module.scss'; import clsx from 'clsx'; +interface Tab { + label: string; + key: string; +} + +interface StateTabProps { + tabs: Tab[]; + state: string; + setState: React.Dispatch>; + className?: string; +} + /** * A version of RouterTab that toggles an active tab held in parent state, rather than using path changes. * @@ -18,8 +31,8 @@ import clsx from 'clsx'; * @returns {React.Component} The rendered StateTab component. */ -export default function StateTab({ tabs, state, setState, className = '' }) { - const isSelected = (key) => state === key; +const StateTab = ({ tabs, state, setState, className = '' }: StateTabProps) => { + const isSelected = (key: string) => state === key; return (
{tabs.map((tab, index) => ( @@ -34,16 +47,18 @@ export default function StateTab({ tabs, state, setState, className = '' }) { ))}
); -} +}; StateTab.propTypes = { tabs: PropTypes.arrayOf( PropTypes.shape({ label: PropTypes.string.isRequired, key: PropTypes.string.isRequired, - }), + }).isRequired, ).isRequired, state: PropTypes.string.isRequired, setState: PropTypes.func.isRequired, className: PropTypes.string, }; + +export default StateTab; diff --git a/packages/webapp/src/components/Typography/index.tsx b/packages/webapp/src/components/Typography/index.tsx index 0d3fad8318..9480b98347 100644 --- a/packages/webapp/src/components/Typography/index.tsx +++ b/packages/webapp/src/components/Typography/index.tsx @@ -13,6 +13,7 @@ type TypographyProps = { onClick?: (e: React.MouseEvent) => void; children?: ReactNode; className?: string; + id?: string; }; export const Underlined = ({ diff --git a/packages/webapp/src/containers/Animals/AddAnimals/index.tsx b/packages/webapp/src/containers/Animals/AddAnimals/index.tsx index 2286673ea0..40481de1ed 100644 --- a/packages/webapp/src/containers/Animals/AddAnimals/index.tsx +++ b/packages/webapp/src/containers/Animals/AddAnimals/index.tsx @@ -18,7 +18,7 @@ import { useDispatch } from 'react-redux'; import { History } from 'history'; import { useMediaQuery } from '@mui/material'; import theme from '../../../assets/theme'; -import { MultiStepForm, VARIANT } from '../../../components/Form/MultiStepForm'; +import { ContextForm, VARIANT } from '../../../components/Form/ContextForm/'; import AddAnimalBasics, { animalBasicsDefaultValues } from './AddAnimalBasics'; import AddAnimalDetails from './AddAnimalDetails'; import AddAnimalSummary from './AddAnimalSummary'; @@ -123,7 +123,7 @@ function AddAnimals({ isCompactSideMenu, history }: AddAnimalsProps) { }; return ( - { + // row = { + // id: 'ANIMAL_60', + // identification: 'ID32', // or could be name + // path: '/animal/32', // + // ... + // }; + + // TODO: Decide on URL pattern. If crateAnimalDetailsUrl() is desired, then row.path should be removed and replaced with the numeric part of identifier + history.push(createReadonlyEditSingleAnimalView(row.id)); + }; + const makeAnimalsSearchableString = (animal: AnimalInventory) => { return [animal.identification, animal.type, animal.breed, ...animal.groups, animal.count] .filter(Boolean) @@ -235,6 +250,7 @@ function AnimalInventory({ isCompactSideMenu, history }: AnimalInventoryProps) { clearFilters={clearFilters} isLoading={isLoading} history={history} + onRowClick={onRowClick} /> {selectedInventoryIds.length ? ( diff --git a/packages/webapp/src/containers/Animals/SingleAnimalView/AnimalReadonlyEdit.tsx b/packages/webapp/src/containers/Animals/SingleAnimalView/AnimalReadonlyEdit.tsx new file mode 100644 index 0000000000..f4c9b790f7 --- /dev/null +++ b/packages/webapp/src/containers/Animals/SingleAnimalView/AnimalReadonlyEdit.tsx @@ -0,0 +1,95 @@ +/* + * Copyright 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +import useImagePickerUpload from '../../../components/ImagePicker/useImagePickerUpload'; +import { useAnimalOptions } from '../AddAnimals/useAnimalOptions'; +import { useCurrencySymbol } from '../../hooks/useCurrencySymbol'; +import { useTranslation } from 'react-i18next'; +import { AnimalOrBatchKeys } from '../types'; +import GeneralDetails from '../../../components/Animals/DetailCards/General'; +import UniqueDetails from '../../../components/Animals/DetailCards/Unique'; +import Origin from '../../../components/Animals/DetailCards/Origin'; +import OtherDetails from '../../../components/Animals/DetailCards/Other'; + +// TODO: LF-4383 Animal Details Form Container. This is placeholder +const AnimalReadonlyEdit = ({ isEditing = false }) => { + const { getOnFileUpload } = useImagePickerUpload(); + const { t } = useTranslation(['translation', 'common', 'animal']); + + const isAnimal = true; // TODO LF-4383 decide how to handle animals vs batches + + const { + sexOptions, + sexDetailsOptions, + useOptions, + tagTypeOptions, + tagColorOptions, + organicStatusOptions, + originOptions, + } = useAnimalOptions( + 'sex', + 'sexDetails', + 'use', + 'tagType', + 'tagColor', + 'organicStatus', + 'origin', + ); + + // TODO: LF-4383 Form container -- useOptions per type used to be narrowed in the container; now since type can be altered this will have to be moved to the component + + const currency = useCurrencySymbol(); + + const commonProps = { + t, + animalOrBatch: isAnimal ? AnimalOrBatchKeys.ANIMAL : AnimalOrBatchKeys.BATCH, + // mode: isEditing ? 'edit' : 'readonly', // Requires merge of LF-4388 with these modes + }; + + const generalDetailProps = { + sexOptions, + sexDetailsOptions, + useOptions, + }; + const otherDetailsProps = { + organicStatusOptions, + getOnFileUpload, + imageUploadTargetRoute: isAnimal ? 'animal' : 'animalBatch', + }; + const originProps = { + currency: currency, + originOptions, + }; + + const uniqueDetailsProps = { + tagTypeOptions, + tagColorOptions, + }; + + // TODO: LF-4383 Animal details Form container -- Create a real wrapping component with styles for these cards, passing of readonly and edit modes in child components, and decision about whether Animal + Batch will be handled via prop or via separate components (as in AnimalDetails and BatchDetails). ExpandableItem is not necessary for this component as they will never collapse + return ( + <> +

LF-4383 Animal Details Form Container

+

Readonly mode requires merge of LF-4388

+ {isEditing &&

Editing...

} + + + + + + ); +}; + +export default AnimalReadonlyEdit; diff --git a/packages/webapp/src/containers/Animals/SingleAnimalView/AnimalTasks.tsx b/packages/webapp/src/containers/Animals/SingleAnimalView/AnimalTasks.tsx new file mode 100644 index 0000000000..1da07f83e0 --- /dev/null +++ b/packages/webapp/src/containers/Animals/SingleAnimalView/AnimalTasks.tsx @@ -0,0 +1,21 @@ +/* + * Copyright 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +// TODO: Most likely to be scoped after movement tasks. It is not part of this user story. +const AnimalTasks = () => { + return
AnimalTasks
; +}; + +export default AnimalTasks; diff --git a/packages/webapp/src/containers/Animals/SingleAnimalView/index.tsx b/packages/webapp/src/containers/Animals/SingleAnimalView/index.tsx new file mode 100644 index 0000000000..2a488ebce5 --- /dev/null +++ b/packages/webapp/src/containers/Animals/SingleAnimalView/index.tsx @@ -0,0 +1,132 @@ +/* + * Copyright 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { History } from 'history'; +import styles from './styles.module.scss'; +import { ContextForm, VARIANT } from '../../../components/Form/ContextForm/'; +// TODO: LF-4382 Tabs component That ticket includes the decision on whether to create a RouterTab (route change) or StateTab (component state) variant. However RouterTab should be used in the implementation to trigger the destructive action pop-up modal -- StateTab is just the placeholder +import StateTab from '../../../components/RouterTab/StateTab'; +import AnimalReadonlyEdit from './AnimalReadonlyEdit'; +import AnimalTasks from './AnimalTasks'; +import Button from '../../../components/Form/Button'; + +export const STEPS = { + DETAILS: 'details', +} as const; + +enum TABS { + DETAILS = 'DETAILS', + TASKS = 'TASKS', +} + +interface AddAnimalsProps { + isCompactSideMenu: boolean; + history: History; +} + +function SingleAnimalView({ isCompactSideMenu, history }: AddAnimalsProps) { + const { t } = useTranslation(['translation', 'common', 'message']); + + const [isEditing, setIsEditing] = useState(false); + const [checkIsFormDirty, setCheckIsFormDirty] = useState(false); + + // For now, assuming that the only way to exit edit will be through the cancel button and not through the header + const initiateEdit = () => { + setIsEditing(true); + }; + + const [activeTab, setActiveTab] = useState(TABS.DETAILS); + + const onSave = async (data: any, onGoForward: () => void) => { + console.log(data); + setIsEditing(false); + }; + + const getFormSteps = () => [ + { + FormContent: AnimalReadonlyEdit, + title: t('ADD_ANIMAL.ANIMAL_DETAILS'), + }, + ]; + + const defaultFormValues = { + [STEPS.DETAILS]: [], + }; + + // Override the onCancel in ContextForm because we don't want to navigate away + const onCancel = () => { + setCheckIsFormDirty(true); + }; + + return ( +
+
+ {/* TODO: LF-4381 Header component */} +

LF-4381 Header component

+ {isEditing ? ( + + ) : ( + + )} +
+ {/* TODO: LF-4382 Tabs component */} +
+

LF-4382 Tabs component

+ +
+ {activeTab === TABS.DETAILS && ( + + )} + {/* TODO: Has not yet been scoped. Revisit after movement */} + {activeTab === TABS.TASKS && } +
+ ); +} + +export default SingleAnimalView; diff --git a/packages/webapp/src/containers/Animals/SingleAnimalView/styles.module.scss b/packages/webapp/src/containers/Animals/SingleAnimalView/styles.module.scss new file mode 100644 index 0000000000..d0c9d3af66 --- /dev/null +++ b/packages/webapp/src/containers/Animals/SingleAnimalView/styles.module.scss @@ -0,0 +1,20 @@ +/* + * Copyright 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +.container { + display: flex; + flex-direction: column; + gap: 20px; +} diff --git a/packages/webapp/src/routes/AnimalsRoutes.jsx b/packages/webapp/src/routes/AnimalsRoutes.jsx index a7d9f9fd96..a2dbe2834b 100644 --- a/packages/webapp/src/routes/AnimalsRoutes.jsx +++ b/packages/webapp/src/routes/AnimalsRoutes.jsx @@ -20,11 +20,13 @@ import { ANIMALS_LOCATION_URL, ANIMALS_GROUPS_URL, ADD_ANIMALS_URL, + createReadonlyEditSingleAnimalView, } from '../util/siteMapConstants'; const Inventory = React.lazy(() => import('../containers/Animals/Inventory')); const Location = React.lazy(() => import('../containers/Animals/Location')); const Groups = React.lazy(() => import('../containers/Animals/Groups')); const AddAnimals = React.lazy(() => import('../containers/Animals/AddAnimals')); +const SingleAnimalView = React.lazy(() => import('../containers/Animals/SingleAnimalView')); const AnimalsRoutes = ({ isCompactSideMenu }) => ( @@ -40,6 +42,11 @@ const AnimalsRoutes = ({ isCompactSideMenu }) => ( exact render={(props) => } /> + } + /> ); diff --git a/packages/webapp/src/stories/MultiStepForm/MultiStepForm.stories.tsx b/packages/webapp/src/stories/ContextForm/ContextForm.stories.tsx similarity index 90% rename from packages/webapp/src/stories/MultiStepForm/MultiStepForm.stories.tsx rename to packages/webapp/src/stories/ContextForm/ContextForm.stories.tsx index 643b7091aa..c50f1baa08 100644 --- a/packages/webapp/src/stories/MultiStepForm/MultiStepForm.stories.tsx +++ b/packages/webapp/src/stories/ContextForm/ContextForm.stories.tsx @@ -15,17 +15,17 @@ import { Meta, StoryObj } from '@storybook/react'; import { componentDecorators } from '../Pages/config/Decorators'; -import { MultiStepForm, VARIANT } from '../../components/Form/MultiStepForm'; +import { ContextForm, VARIANT } from '../../components/Form/ContextForm'; // https://storybook.js.org/docs/writing-stories/typescript -const meta: Meta = { - title: 'Components/MultiStepForm', - component: MultiStepForm, +const meta: Meta = { + title: 'Components/ContextForm', + component: ContextForm, decorators: componentDecorators, }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const PageTitle: Story = { args: { diff --git a/packages/webapp/src/util/siteMapConstants.ts b/packages/webapp/src/util/siteMapConstants.ts index 5858dccd13..ed55215571 100644 --- a/packages/webapp/src/util/siteMapConstants.ts +++ b/packages/webapp/src/util/siteMapConstants.ts @@ -23,6 +23,9 @@ export const ANIMALS_INVENTORY_URL = '/animals/inventory'; export const ANIMALS_LOCATION_URL = '/animals/location'; export const ANIMALS_GROUPS_URL = '/animals/groups'; export const ADD_ANIMALS_URL = '/animals/inventory/add_animals'; +export const createReadonlyEditSingleAnimalView = (id: string | number): string => { + return `/animals/${id}`; +}; // Finances