diff --git a/packages/api/.env.default b/packages/api/.env.default index 784f28265d..26f5280e05 100644 --- a/packages/api/.env.default +++ b/packages/api/.env.default @@ -93,3 +93,8 @@ API_HOST=localhost #For sentry error logging #SENTRY_DSN=? + +# For displaying an OOO message in the emails sent out in response to help requests +OOO_MESSAGE_ENABLED= +# Format YYYY/MM/DD +OOO_END_DATE= diff --git a/packages/api/src/controllers/supportTicketController.js b/packages/api/src/controllers/supportTicketController.js index d8a69fd613..e97cb6b1bc 100644 --- a/packages/api/src/controllers/supportTicketController.js +++ b/packages/api/src/controllers/supportTicketController.js @@ -37,6 +37,7 @@ const supportTicketController = { contact_method: capitalize(result.contact_method), contact: result[result.contact_method], locale: user.language_preference, + ...getOOOMessageReplacements(user.language_preference), }; const email = data.contact_method === 'email' && data.email; if (email && email !== user.email) { @@ -60,6 +61,16 @@ const supportTicketController = { }, }; +const getOOOMessageReplacements = (locale) => { + const ooo_message_enabled = process.env.OOO_MESSAGE_ENABLED === 'true'; + let ooo_end_date = process.env.OOO_END_DATE; + if (ooo_message_enabled && ooo_end_date) { + const dateOptions = { year: 'numeric', month: 'long', day: 'numeric' }; + ooo_end_date = new Date(ooo_end_date).toLocaleDateString(locale, dateOptions); + } + return { ooo_message_enabled, ooo_end_date }; +}; + const capitalize = (string) => { return string[0].toUpperCase() + string.slice(1); }; diff --git a/packages/api/src/templates/emails/help_request_email/html.pug b/packages/api/src/templates/emails/help_request_email/html.pug index d392ab2bc3..ee9fd89105 100644 --- a/packages/api/src/templates/emails/help_request_email/html.pug +++ b/packages/api/src/templates/emails/help_request_email/html.pug @@ -1,22 +1,27 @@ extends ../template + block content - .support-container - .support-content + .support-container + .support-content + #ooo_message_enabled + if ooo_message_enabled + span=`${t('HELP_REQUEST.OOO_RESPONSE', { oooEndDate: ooo_end_date })}` + else span=`${t('HELP_REQUEST.RESPONSE')}` - .support-container - .support-header - span=`${t('HELP_REQUEST.TYPE_HEADER')}:` - .support-content - p=`${t('HELP_REQUEST.' + support_type.toUpperCase().replace(/\s/g, '_'))}` - .support-container - .support-header - span=`${t('HELP_REQUEST.MESSAGE_HEADER')}:` - .support-content - | #{message} - .support-container - .support-header - span=`${t('HELP_REQUEST.CONTACT_METHOD_HEADER')}:` - .support-content - | #{contact_method} - .support-content - | #{contact} + .support-container + .support-header + span=`${t('HELP_REQUEST.TYPE_HEADER')}:` + .support-content + p=`${t('HELP_REQUEST.' + support_type.toUpperCase().replace(/\s/g, '_'))}` + .support-container + .support-header + span=`${t('HELP_REQUEST.MESSAGE_HEADER')}:` + .support-content + | #{message} + .support-container + .support-header + span=`${t('HELP_REQUEST.CONTACT_METHOD_HEADER')}:` + .support-content + | #{contact_method} + .support-content + | #{contact} diff --git a/packages/api/src/templates/locales/de.json b/packages/api/src/templates/locales/de.json index 79b9650c9e..29a4753720 100644 --- a/packages/api/src/templates/locales/de.json +++ b/packages/api/src/templates/locales/de.json @@ -18,7 +18,8 @@ "REQUEST_A_FEATURE": "Verbesserung vorschlagen", "OTHER": "Anderes", "CONTACT_METHOD_HEADER": "Bevorzugte Kontaktmethode", - "SUBJECT": "Deine LiteFarm-Anfrage um Unterstützung am" + "SUBJECT": "Deine LiteFarm-Anfrage um Unterstützung am", + "OOO_RESPONSE": "MISSING" }, "INVITE": { "GREAT_FOLK": "Die großartigen Leute von", diff --git a/packages/api/src/templates/locales/en.json b/packages/api/src/templates/locales/en.json index ee61ff1bf7..1991e924ab 100644 --- a/packages/api/src/templates/locales/en.json +++ b/packages/api/src/templates/locales/en.json @@ -18,7 +18,8 @@ "REQUEST_A_FEATURE": "Request a feature", "OTHER": "Other", "CONTACT_METHOD_HEADER": "Preferred contact method", - "SUBJECT": "Your LiteFarm request for help at" + "SUBJECT": "Your LiteFarm request for help at", + "OOO_RESPONSE": "Thanks for reaching out. Please note that our team is out of the office until {{oooEndDate}}. We will respond to your message as soon as possible after we return. Thank you for your understanding! A summary of your request is shown below." }, "INVITE": { "GREAT_FOLK": "The great folks at", diff --git a/packages/api/src/templates/locales/es.json b/packages/api/src/templates/locales/es.json index 7ac34d2be7..33a1a2f8d3 100644 --- a/packages/api/src/templates/locales/es.json +++ b/packages/api/src/templates/locales/es.json @@ -18,7 +18,8 @@ "REQUEST_A_FEATURE": "Solicitar una característica", "OTHER": "Otro", "CONTACT_METHOD_HEADER": "Método de contacto preferido", - "SUBJECT": "Su solicitud de ayuda a LiteFarm a las" + "SUBJECT": "Su solicitud de ayuda a LiteFarm a las", + "OOO_RESPONSE": "MISSING" }, "INVITE": { "GREAT_FOLK": "Las personas de", diff --git a/packages/api/src/templates/locales/fr.json b/packages/api/src/templates/locales/fr.json index ac27a53e40..93a844efa1 100644 --- a/packages/api/src/templates/locales/fr.json +++ b/packages/api/src/templates/locales/fr.json @@ -18,7 +18,8 @@ "REQUEST_A_FEATURE": "Demander une fonctionnalité", "OTHER": "Autre", "CONTACT_METHOD_HEADER": "Méthode de contact préférée", - "SUBJECT": "Votre demande d'aide LiteFarm à" + "SUBJECT": "Votre demande d'aide LiteFarm à", + "OOO_RESPONSE": "MISSING" }, "INVITE": { "GREAT_FOLK": "Les gens formidables de", diff --git a/packages/api/src/templates/locales/hi.json b/packages/api/src/templates/locales/hi.json index 611dd3feec..97999a4aaa 100644 --- a/packages/api/src/templates/locales/hi.json +++ b/packages/api/src/templates/locales/hi.json @@ -18,7 +18,8 @@ "REQUEST_A_FEATURE": "एक सुविधा का अनुरोध करें", "OTHER": "कुछ और", "CONTACT_METHOD_HEADER": "पसंदीदा संपर्क विधि", - "SUBJECT": "आपका लाइटफार्म सहायता के लिए अनुरोध" + "SUBJECT": "आपका लाइटफार्म सहायता के लिए अनुरोध", + "OOO_RESPONSE": "MISSING" }, "INVITE": { "GREAT_FOLK": "लाइटफार्म के उपयोगकर्ता", diff --git a/packages/api/src/templates/locales/ml.json b/packages/api/src/templates/locales/ml.json index 197e4dfb0c..9e76ea9b59 100644 --- a/packages/api/src/templates/locales/ml.json +++ b/packages/api/src/templates/locales/ml.json @@ -18,7 +18,8 @@ "REQUEST_A_FEATURE": "ഒരു സവിശേഷത അഭ്യർത്ഥിക്കുക", "OTHER": "മറ്റെന്തെങ്കിലും", "CONTACT_METHOD_HEADER": "തിരഞ്ഞെടുത്ത സമ്പർക്ക രീതി", - "SUBJECT": "ലൈറ്റ്ഫോം പിന്തുണയ്ക്കായുള്ള നിങ്ങളുടെ അഭ്യർത്ഥന" + "SUBJECT": "ലൈറ്റ്ഫോം പിന്തുണയ്ക്കായുള്ള നിങ്ങളുടെ അഭ്യർത്ഥന", + "OOO_RESPONSE": "MISSING" }, "INVITE": { "GREAT_FOLK": "മഹാന്മാർ", diff --git a/packages/api/src/templates/locales/pa.json b/packages/api/src/templates/locales/pa.json index 16fea1a3b2..fc1631c5fa 100644 --- a/packages/api/src/templates/locales/pa.json +++ b/packages/api/src/templates/locales/pa.json @@ -18,7 +18,8 @@ "REQUEST_A_FEATURE": "ਫੀਚਰ ਦੀ ਬੇਨਤੀ ਕਰੋ", "OTHER": "ਬਾਕੀ", "CONTACT_METHOD_HEADER": "ਪਸੰਦੀਦਾ ਸੰਪਰਕ ਵਿਧੀ", - "SUBJECT": "ਲਾਈਟਫਾਰਮ ਸਹਾਇਤਾ ਲਈ ਤੁਹਾਡੀ ਬੇਨਤੀ" + "SUBJECT": "ਲਾਈਟਫਾਰਮ ਸਹਾਇਤਾ ਲਈ ਤੁਹਾਡੀ ਬੇਨਤੀ", + "OOO_RESPONSE": "MISSING" }, "INVITE": { "GREAT_FOLK": "ਲਾਈਟਫਾਰਮ ਦੀ ਟੀਮ", diff --git a/packages/api/src/templates/locales/pt.json b/packages/api/src/templates/locales/pt.json index 676175ce82..a6f41d1677 100644 --- a/packages/api/src/templates/locales/pt.json +++ b/packages/api/src/templates/locales/pt.json @@ -18,7 +18,8 @@ "REQUEST_A_FEATURE": "Solicite um recurso", "OTHER": "Outro", "CONTACT_METHOD_HEADER": "Método de contato preferido", - "SUBJECT": "Seu pedido de ajuda para LiteFarm às" + "SUBJECT": "Seu pedido de ajuda para LiteFarm às", + "OOO_RESPONSE": "MISSING" }, "INVITE": { "GREAT_FOLK": "O grande pessoal da", diff --git a/packages/webapp/public/locales/de/translation.json b/packages/webapp/public/locales/de/translation.json index 92ae3fbbd3..4c70b5cc01 100644 --- a/packages/webapp/public/locales/de/translation.json +++ b/packages/webapp/public/locales/de/translation.json @@ -1518,7 +1518,7 @@ }, "REQUEST_CONFIRMATION_MODAL": { "BUTTON": "Verstanden", - "DESCRIPTION": "Jemand wird sich innerhalb von 48 Stunden mit Ihnen in Verbindung setzen.", + "DESCRIPTION": "MISSING", "TITLE": "Anfrage abgeschickt" }, "REVENUE": { diff --git a/packages/webapp/public/locales/en/translation.json b/packages/webapp/public/locales/en/translation.json index 73fe352fed..f2504b60ca 100644 --- a/packages/webapp/public/locales/en/translation.json +++ b/packages/webapp/public/locales/en/translation.json @@ -1656,7 +1656,7 @@ }, "REQUEST_CONFIRMATION_MODAL": { "BUTTON": "Got it", - "DESCRIPTION": "Someone will be in touch within 48 hours.", + "DESCRIPTION": "Someone will be in touch soon.", "TITLE": "Help request submitted" }, "REVENUE": { diff --git a/packages/webapp/public/locales/es/translation.json b/packages/webapp/public/locales/es/translation.json index 7e639ff4c4..3e8c679dc1 100644 --- a/packages/webapp/public/locales/es/translation.json +++ b/packages/webapp/public/locales/es/translation.json @@ -1660,7 +1660,7 @@ }, "REQUEST_CONFIRMATION_MODAL": { "BUTTON": "Entendido", - "DESCRIPTION": "Alguien lo contactará dentro de 48 horas.", + "DESCRIPTION": "MISSING", "TITLE": "Solicitud de ayuda enviada" }, "REVENUE": { diff --git a/packages/webapp/public/locales/fr/translation.json b/packages/webapp/public/locales/fr/translation.json index d243b7ee5a..8da2bf629f 100644 --- a/packages/webapp/public/locales/fr/translation.json +++ b/packages/webapp/public/locales/fr/translation.json @@ -1659,7 +1659,7 @@ }, "REQUEST_CONFIRMATION_MODAL": { "BUTTON": "J'ai compris", - "DESCRIPTION": "Vous serez contacté dans les 48 heures.", + "DESCRIPTION": "MISSING", "TITLE": "Demande d'aide soumise" }, "REVENUE": { diff --git a/packages/webapp/public/locales/hi/translation.json b/packages/webapp/public/locales/hi/translation.json index 50cb39c264..2286c40d96 100644 --- a/packages/webapp/public/locales/hi/translation.json +++ b/packages/webapp/public/locales/hi/translation.json @@ -1515,7 +1515,7 @@ }, "REQUEST_CONFIRMATION_MODAL": { "BUTTON": "समझ लिया", - "DESCRIPTION": "कोई व्यक्ति 48 घंटों के भीतर आपसे संपर्क करेगा।", + "DESCRIPTION": "MISSING", "TITLE": "सहायता अनुरोध सबमिट किया गया" }, "REVENUE": { diff --git a/packages/webapp/public/locales/ml/translation.json b/packages/webapp/public/locales/ml/translation.json index 83242ec4cc..fc2ff39196 100644 --- a/packages/webapp/public/locales/ml/translation.json +++ b/packages/webapp/public/locales/ml/translation.json @@ -1515,7 +1515,7 @@ }, "REQUEST_CONFIRMATION_MODAL": { "BUTTON": "മനസ്സിലായി", - "DESCRIPTION": "48 മണിക്കൂറിനുള്ളിൽ ആരെങ്കിലും ബന്ധപ്പെടും.", + "DESCRIPTION": "MISSING", "TITLE": "സഹായ അഭ്യർത്ഥന സമർപ്പിച്ചു" }, "REVENUE": { diff --git a/packages/webapp/public/locales/pa/translation.json b/packages/webapp/public/locales/pa/translation.json index 64a1566e65..b3c6e85b48 100644 --- a/packages/webapp/public/locales/pa/translation.json +++ b/packages/webapp/public/locales/pa/translation.json @@ -1515,7 +1515,7 @@ }, "REQUEST_CONFIRMATION_MODAL": { "BUTTON": "ਸਮਝ ਗਿਆ", - "DESCRIPTION": "ਕੋਈ ਵਿਅਕਤੀ 48 ਘੰਟਿਆਂ ਦੇ ਅੰਦਰ ਸੰਪਰਕ ਵਿੱਚ ਹੋਵੇਗਾ।", + "DESCRIPTION": "MISSING", "TITLE": "ਮਦਦ ਦੀ ਬੇਨਤੀ ਸਪੁਰਦ ਕੀਤੀ ਗਈ" }, "REVENUE": { diff --git a/packages/webapp/public/locales/pt/translation.json b/packages/webapp/public/locales/pt/translation.json index ec5be675d6..2e0e9a6827 100644 --- a/packages/webapp/public/locales/pt/translation.json +++ b/packages/webapp/public/locales/pt/translation.json @@ -1659,7 +1659,7 @@ }, "REQUEST_CONFIRMATION_MODAL": { "BUTTON": "Entendi", - "DESCRIPTION": "Alguém entrará em contato em 48 horas.", + "DESCRIPTION": "MISSING", "TITLE": "Pedido de ajuda enviado" }, "REVENUE": { diff --git a/packages/webapp/src/components/Animals/AnimalSingleViewHeader/index.tsx b/packages/webapp/src/components/Animals/AnimalSingleViewHeader/index.tsx index 4d5249bf31..107bce9ad6 100644 --- a/packages/webapp/src/components/Animals/AnimalSingleViewHeader/index.tsx +++ b/packages/webapp/src/components/Animals/AnimalSingleViewHeader/index.tsx @@ -126,7 +126,8 @@ export type AnimalSingleViewHeaderProps = { onEdit: () => void; onRemove: () => void; onBack: () => void; - animalOrBatch: (Animal | AnimalBatch) & { location?: string }; // TODO: LF-4481 + animalOrBatch: Animal | AnimalBatch; + locationText?: string; defaultTypes: DefaultAnimalType[]; customTypes: CustomAnimalType[]; defaultBreeds: DefaultAnimalBreed[]; @@ -140,6 +141,7 @@ const AnimalSingleViewHeader = ({ onRemove, onBack, animalOrBatch, + locationText, defaultTypes, customTypes, defaultBreeds, @@ -163,7 +165,7 @@ const AnimalSingleViewHeader = ({ ); const age = ; - const location = ; + const location = ; const menuOptions = [ { label: , onClick: onEdit }, diff --git a/packages/webapp/src/components/Table/Cell/CellTypes/HoverPillOverflow.tsx b/packages/webapp/src/components/Table/Cell/CellTypes/HoverPillOverflow.tsx index c8c8f74f61..b956844ceb 100644 --- a/packages/webapp/src/components/Table/Cell/CellTypes/HoverPillOverflow.tsx +++ b/packages/webapp/src/components/Table/Cell/CellTypes/HoverPillOverflow.tsx @@ -22,7 +22,7 @@ const HoverPillOverFlow = ({ items, noneText = '' }: HoverPillOverflowProps) => return (
{items.length === 0 ? ( - {noneText} + {noneText} ) : ( {items[0]} )} diff --git a/packages/webapp/src/components/Table/Cell/styles.module.scss b/packages/webapp/src/components/Table/Cell/styles.module.scss index b4dbba0620..2433accedd 100644 --- a/packages/webapp/src/components/Table/Cell/styles.module.scss +++ b/packages/webapp/src/components/Table/Cell/styles.module.scss @@ -6,7 +6,8 @@ font-weight: 400; } -.overflowText, .overflowText div { +.overflowText, +.overflowText div { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -45,7 +46,6 @@ width: 32px; padding: 4px; @include svgColorFill(var(--Colors-Primary-Primary-teal-300)); - } .withSubtextText { @@ -73,7 +73,7 @@ padding: 4px; border-radius: 2px; background: var(--Colors-Accent---singles-Purple-light); - box-shadow: 1px 1px 0px 0px #FFF; + box-shadow: 1px 1px 0px 0px #fff; color: var(--Colors-Accent---singles-Purple-full); font-size: 12px; font-weight: 700; @@ -84,7 +84,3 @@ align-items: center; gap: 8px; } - -.italics { - font-style: italic; -} \ No newline at end of file diff --git a/packages/webapp/src/containers/Animals/Inventory/index.tsx b/packages/webapp/src/containers/Animals/Inventory/index.tsx index 84d4c7ace9..3ca1d70f48 100644 --- a/packages/webapp/src/containers/Animals/Inventory/index.tsx +++ b/packages/webapp/src/containers/Animals/Inventory/index.tsx @@ -48,6 +48,7 @@ import useExpandable from '../../../components/Expandable/useExpandableItem'; import clsx from 'clsx'; import AnimalsBetaSpotlight from './AnimalsBetaSpotlight'; import { sumObjectValues } from '../../../util'; +import Icon from '../../../components/Icons'; const HEIGHTS = { filterAndSearch: 64, @@ -319,6 +320,22 @@ export default function AnimalInventory({ ), sortable: false, }, + { + id: isDesktop ? 'location' : null, + label: t('ANIMAL.ANIMAL_LOCATIONS').toLocaleUpperCase(), + format: (d: AnimalInventoryType) => ( +
+ {d.location ? ( + <> + + {d.location} + + ) : ( + t('common:UNKNOWN') + )} +
+ ), + }, { id: showLinks ? 'path' : null, label: '', @@ -335,7 +352,14 @@ export default function AnimalInventory({ ); const makeAnimalsSearchableString = (animal: AnimalInventoryType) => { - return [animal.identification, animal.type, animal.breed, ...animal.groups, animal.count] + return [ + animal.identification, + animal.type, + animal.breed, + ...animal.groups, + animal.count, + animal.location, + ] .filter(Boolean) .join(' '); }; diff --git a/packages/webapp/src/containers/Animals/Inventory/styles.module.scss b/packages/webapp/src/containers/Animals/Inventory/styles.module.scss index ff9f2ad854..5696ed4c9a 100644 --- a/packages/webapp/src/containers/Animals/Inventory/styles.module.scss +++ b/packages/webapp/src/containers/Animals/Inventory/styles.module.scss @@ -33,9 +33,11 @@ height: auto; overflow-y: auto; } - + .taskViewMaxHeight { - max-height: calc(100vh - var(--global-navbar-height) - var(--global-multi-step-task-layout-aggregated-height)); + max-height: calc( + 100vh - var(--global-navbar-height) - var(--global-multi-step-task-layout-aggregated-height) + ); } .summaryViewHeight { @@ -105,4 +107,28 @@ .disableHover { pointer-events: none; -} \ No newline at end of file +} + +.location { + display: flex; + align-items: center; + gap: 4px; + font-size: 14px; + font-weight: 600; + color: var(--Colors-Neutral-Neutral-600); + + &.unknown { + font-weight: normal; + } +} + +.locationIcon { + padding: 0; + min-width: 16px; + background-color: transparent; +} + +.locationText { + width: calc(100% - 20px); // icon width 16px + gap 4px + @include truncateText(); +} diff --git a/packages/webapp/src/containers/Animals/Inventory/useAnimalInventory.tsx b/packages/webapp/src/containers/Animals/Inventory/useAnimalInventory.tsx index 97b864e8b2..56372fe84f 100644 --- a/packages/webapp/src/containers/Animals/Inventory/useAnimalInventory.tsx +++ b/packages/webapp/src/containers/Animals/Inventory/useAnimalInventory.tsx @@ -39,6 +39,9 @@ import { AnimalOrBatchKeys } from '../types'; import { generateInventoryId } from '../../../util/animal'; import { AnimalTypeIconKey, isAnimalTypeIconKey } from '../../../components/Icons/icons'; import { createSingleAnimalViewURL } from '../../../util/siteMapConstants'; +import { useSelector } from 'react-redux'; +import { locationsSelector } from '../../locationSlice'; +import { Location } from '../../../types'; export type AnimalInventory = { id: string; @@ -51,12 +54,14 @@ export type AnimalInventory = { count: number; batch: boolean; group_ids: number[]; + location: string; sex_id?: number; sex_detail?: { sex_id: number; count: number }[]; custom_type_id: number | null; default_type_id: number | null; custom_breed_id: number | null; default_breed_id: number | null; + location_id?: string | null; tasks: Animal['tasks']; }; @@ -140,6 +145,7 @@ const formatAnimalsData = ( customAnimalTypes: CustomAnimalType[], defaultAnimalBreeds: DefaultAnimalBreed[], defaultAnimalTypes: DefaultAnimalType[], + locationsMap: { [key: string]: string }, ): AnimalInventory[] => { return animals .filter( @@ -158,6 +164,7 @@ const formatAnimalsData = ( path: createSingleAnimalViewURL(animal.internal_identifier), count: 1, batch: false, + location: animal.location_id ? locationsMap[animal.location_id] : '', // preserve some untransformed data for filtering group_ids: animal.group_ids, sex_id: animal.sex_id, @@ -165,6 +172,7 @@ const formatAnimalsData = ( default_type_id: animal.default_type_id, custom_breed_id: animal.custom_breed_id, default_breed_id: animal.default_breed_id, + location_id: animal.location_id, tasks: animal.tasks, }; }); @@ -177,6 +185,7 @@ const formatAnimalBatchesData = ( customAnimalTypes: CustomAnimalType[], defaultAnimalBreeds: DefaultAnimalBreed[], defaultAnimalTypes: DefaultAnimalType[], + locationsMap: { [key: string]: string }, ): AnimalInventory[] => { return animalBatches .filter( @@ -195,6 +204,7 @@ const formatAnimalBatchesData = ( path: createSingleAnimalViewURL(batch.internal_identifier), count: batch.count, batch: true, + location: batch.location_id ? locationsMap[batch.location_id] : '', // preserve some untransformed data for filtering group_ids: batch.group_ids, sex_detail: batch.sex_detail, @@ -202,6 +212,7 @@ const formatAnimalBatchesData = ( default_type_id: batch.default_type_id, custom_breed_id: batch.custom_breed_id, default_breed_id: batch.default_breed_id, + location_id: batch.location_id, tasks: batch.tasks, }; }); @@ -215,6 +226,7 @@ interface BuildInventoryArgs { customAnimalTypes: CustomAnimalType[]; defaultAnimalBreeds: DefaultAnimalBreed[]; defaultAnimalTypes: DefaultAnimalType[]; + locationsMap: { [key: string]: string }; } export const buildInventory = ({ @@ -225,6 +237,7 @@ export const buildInventory = ({ customAnimalTypes, defaultAnimalBreeds, defaultAnimalTypes, + locationsMap, }: BuildInventoryArgs) => { const inventory = [ ...formatAnimalsData( @@ -234,6 +247,7 @@ export const buildInventory = ({ customAnimalTypes, defaultAnimalBreeds, defaultAnimalTypes, + locationsMap, ), ...formatAnimalBatchesData( animalBatches, @@ -242,6 +256,7 @@ export const buildInventory = ({ customAnimalTypes, defaultAnimalBreeds, defaultAnimalTypes, + locationsMap, ), ]; @@ -272,6 +287,12 @@ const useAnimalInventory = () => { defaultAnimalTypes, } = data; + const locations: Location[] = useSelector(locationsSelector); + const locationsMap = locations?.reduce( + (map, { location_id, name }) => ({ ...map, [location_id]: name }), + {}, + ); + const inventory = useMemo(() => { if (isLoading) { return []; @@ -283,7 +304,8 @@ const useAnimalInventory = () => { customAnimalBreeds && customAnimalTypes && defaultAnimalBreeds && - defaultAnimalTypes + defaultAnimalTypes && + locationsMap ) { return buildInventory({ animals, @@ -293,6 +315,7 @@ const useAnimalInventory = () => { customAnimalTypes, defaultAnimalBreeds, defaultAnimalTypes, + locationsMap, }); } return []; diff --git a/packages/webapp/src/containers/Animals/Inventory/useFilteredInventory.ts b/packages/webapp/src/containers/Animals/Inventory/useFilteredInventory.ts index acdb3677d0..8d4eb627ca 100644 --- a/packages/webapp/src/containers/Animals/Inventory/useFilteredInventory.ts +++ b/packages/webapp/src/containers/Animals/Inventory/useFilteredInventory.ts @@ -70,10 +70,9 @@ export const useFilteredInventory = ( isInactive(groupsFilter) || entity.group_ids.some((groupId) => groupsFilter[groupId]?.active); - // *** Location is not yet implemented as a property on animal or batch *** - const locationMatches = isInactive(locationsFilter); - // const locationMatches = - // isInactive(locationsFilter) || locationsFilter[entity.location]?.active; + const locationMatches = + isInactive(locationsFilter) || + (entity.location_id && locationsFilter[entity.location_id]?.active); return ( animalOrBatchMatches && diff --git a/packages/webapp/src/containers/Animals/SingleAnimalView/index.tsx b/packages/webapp/src/containers/Animals/SingleAnimalView/index.tsx index 90847ec046..1a9ad60fc2 100644 --- a/packages/webapp/src/containers/Animals/SingleAnimalView/index.tsx +++ b/packages/webapp/src/containers/Animals/SingleAnimalView/index.tsx @@ -34,6 +34,7 @@ import { useGetDefaultAnimalBreedsQuery, useGetDefaultAnimalTypesQuery, } from '../../../store/api/apiSlice'; +import { locationsSelector } from '../../locationSlice'; import { formatAnimalDetailsToDBStructure, formatBatchDetailsToDBStructure, @@ -44,7 +45,7 @@ import { AnimalDetailsFormFields } from '../AddAnimals/types'; import RemoveAnimalsModal, { FormFields } from '../../../components/Animals/RemoveAnimalsModal'; import useAnimalOrBatchRemoval from '../Inventory/useAnimalOrBatchRemoval'; import { generateInventoryId } from '../../../util/animal'; -import { CustomRouteComponentProps } from '../../../types'; +import { CustomRouteComponentProps, Location } from '../../../types'; import { isAdminSelector } from '../../userFarmSlice'; export const STEPS = { @@ -90,6 +91,7 @@ function SingleAnimalView({ isCompactSideMenu, history, match, location }: AddAn // Form setup const dispatch = useDispatch(); + const locations: Location[] = useSelector(locationsSelector); const getFormSteps = () => [ { @@ -106,6 +108,9 @@ function SingleAnimalView({ isCompactSideMenu, history, match, location }: AddAn }); const isRemoved = !!defaultFormValues?.animal_removal_reason_id; + const locationText = locations?.find( + ({ location_id }) => location_id === defaultFormValues?.location_id, + )?.name; useEffect(() => { if (!isFetchingAnimalsOrBatches && !selectedAnimal && !selectedBatch) { @@ -203,6 +208,7 @@ function SingleAnimalView({ isCompactSideMenu, history, match, location }: AddAn onBack={history.back} /* @ts-ignore */ animalOrBatch={defaultFormValues} + locationText={locationText} defaultBreeds={defaultAnimalBreeds} defaultTypes={defaultAnimalTypes} customBreeds={customAnimalBreeds} diff --git a/packages/webapp/src/containers/Filter/Animals/index.tsx b/packages/webapp/src/containers/Filter/Animals/index.tsx index bc4c0de7aa..2500047e11 100644 --- a/packages/webapp/src/containers/Filter/Animals/index.tsx +++ b/packages/webapp/src/containers/Filter/Animals/index.tsx @@ -17,7 +17,8 @@ import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import FilterGroup from '../../../components/Filter/FilterGroup'; -import type { ReduxFilterEntity, ContainerOnChangeCallback, Location, FilterState } from '../types'; +import type { ReduxFilterEntity, ContainerOnChangeCallback, FilterState } from '../types'; +import { Location } from '../../../types'; import { FilterType, type ComponentFilter } from '../../../components/Filter/types'; import { useGetDefaultAnimalTypesQuery, diff --git a/packages/webapp/src/containers/Filter/types.ts b/packages/webapp/src/containers/Filter/types.ts index 67c8ad5c5c..69367cd084 100644 --- a/packages/webapp/src/containers/Filter/types.ts +++ b/packages/webapp/src/containers/Filter/types.ts @@ -32,11 +32,3 @@ export interface FilterState { } export type ContainerOnChangeCallback = (filterKey: string, filterState: FilterState) => void; - -// Only typing the properties relevant to filtering on location; there are no .ts files yet related to this entity -export interface Location { - name: string; - location_id: string; - - [key: string]: any; -} diff --git a/packages/webapp/src/store/api/types/index.ts b/packages/webapp/src/store/api/types/index.ts index eba843f884..fc6e31d0ac 100644 --- a/packages/webapp/src/store/api/types/index.ts +++ b/packages/webapp/src/store/api/types/index.ts @@ -53,6 +53,7 @@ export interface Animal { animal_removal_reason_id: number | null; removal_explanation: string | null; removal_date: string | null; + location_id: string | null; tasks: { task_id: number }[]; type_name?: string; // request only breed_name?: string; // request only @@ -93,6 +94,7 @@ export interface AnimalBatch { animal_removal_reason_id: number | null; removal_explanation: string | null; removal_date: string | null; + location_id: string | null; tasks: { task_id: number }[]; type_name?: string; // request only breed_name?: string; // request only diff --git a/packages/webapp/src/stories/Animals/AnimalSingleViewHeader.stories.tsx b/packages/webapp/src/stories/Animals/AnimalSingleViewHeader.stories.tsx index 7d6e6936f7..78309d64a0 100644 --- a/packages/webapp/src/stories/Animals/AnimalSingleViewHeader.stories.tsx +++ b/packages/webapp/src/stories/Animals/AnimalSingleViewHeader.stories.tsx @@ -49,13 +49,15 @@ type Story = StoryObj; export const AnimalHeader: Story = { args: { - animalOrBatch: { ...mockAnimal1, location: 'Pig House' }, + locationText: 'Pig House', + animalOrBatch: mockAnimal1, }, }; export const AnimalWithoutPhoto: Story = { args: { - animalOrBatch: { ...mockAnimal1, location: 'Pig House', photo_url: null }, + locationText: 'Pig House', + animalOrBatch: { ...mockAnimal1, photo_url: null }, }, }; @@ -67,32 +69,37 @@ export const AnimalWithoutLocation: Story = { export const AnimalWithoutAge: Story = { args: { - animalOrBatch: { ...mockAnimal1, location: 'Pig House', birth_date: null }, + locationText: 'Pig House', + animalOrBatch: { ...mockAnimal1, birth_date: null }, }, }; export const AnimalWithLongTexts: Story = { args: { - animalOrBatch: { ...mockAnimal2, location: 'Pig House ABCDEFG', birth_date: null }, + locationText: 'Pig House ABCDEFG', + animalOrBatch: { ...mockAnimal2, birth_date: null }, }, }; export const EditMode: Story = { args: { - animalOrBatch: { ...mockAnimal1, location: 'Pig House' }, + locationText: 'Pig House', + animalOrBatch: mockAnimal1, isEditing: true, }, }; export const EditModeWithLongText: Story = { args: { - animalOrBatch: { ...mockAnimal2, location: 'Pig House ABCDEFG', birth_date: null }, + locationText: 'Pig House ABCDEFG', + animalOrBatch: { ...mockAnimal2, birth_date: null }, isEditing: true, }, }; export const BatchHeader: Story = { args: { - animalOrBatch: { ...mockBatch1, location: 'Chicken coop' }, + locationText: 'Chicken coop', + animalOrBatch: mockBatch1, }, }; diff --git a/packages/webapp/src/stories/Animals/mockData.ts b/packages/webapp/src/stories/Animals/mockData.ts index 0757bab292..ea7faf892e 100644 --- a/packages/webapp/src/stories/Animals/mockData.ts +++ b/packages/webapp/src/stories/Animals/mockData.ts @@ -76,6 +76,7 @@ export const mockAnimal1: Animal = { price: 1000, internal_identifier: 12, group_ids: [], + location_id: 'xxxxx', tasks: [], }; @@ -119,5 +120,6 @@ export const mockBatch1: AnimalBatch = { { sex_id: 1, count: 30 }, { sex_id: 2, count: 40 }, ], + location_id: 'xxxxx', tasks: [], }; diff --git a/packages/webapp/src/stories/Table/TableCells.stories.jsx b/packages/webapp/src/stories/Table/TableCells.stories.jsx index 732c55f1fe..07f965d6e1 100644 --- a/packages/webapp/src/stories/Table/TableCells.stories.jsx +++ b/packages/webapp/src/stories/Table/TableCells.stories.jsx @@ -16,7 +16,6 @@ import React from 'react'; import { v2TableDecorator } from '../Pages/config/Decorators'; import Table from '../../components/Table'; import Cell from '../../components/Table/Cell'; -import { ReactComponent as CropIcon } from '../../assets/images/nav/crops.svg'; import { TableKind, CellKind } from '../../components/Table/types'; export default { @@ -30,7 +29,7 @@ const getFakeColumns = () => { { id: 'crop', label: 'Crops', - format: (d) => , + format: (d) => , }, { id: 'tasks', @@ -56,29 +55,34 @@ const getFakeData = (length) => { return [ { crop: 'White corn, Corn', - icon: CropIcon, + iconName: 'CROP', tasks: ['Task 1', 'Task 2', 'Task 3'], revenue: 8796.0, }, { crop: 'Koto, Buckwheat', - icon: CropIcon, + iconName: 'CROP', tasks: ['Task 1', 'Task 2', 'Task 3'], revenue: 692.5, }, { crop: 'Lutz green leaf, Beetroot', - icon: CropIcon, + iconName: 'CROP', tasks: ['Task 1', 'Task 2'], revenue: 210.0, }, - { crop: 'Cox’s orange pippin, Apple', icon: CropIcon, tasks: ['Task 1'], revenue: 340.0 }, - { crop: 'Macoun, Apples', icon: CropIcon, tasks: [], revenue: 1234.0 }, - { crop: 'Butter Boy Hybrid, Butternut ', icon: CropIcon, tasks: ['Task 1'], revenue: 785.5 }, - { crop: 'King Edward, Potato', icon: CropIcon, tasks: ['Task 1'], revenue: 237.0 }, - { crop: 'Blanco Veneto, Celeriac', icon: CropIcon, tasks: ['Task 1'], revenue: 895.0 }, - { crop: 'Hollow Crown, Parsnips ', icon: CropIcon, tasks: ['Task 1'], revenue: 354.0 }, - { crop: 'Early White Hybrid, Cauliflower', icon: CropIcon, tasks: ['Task 1'], revenue: 789.5 }, + { crop: 'Cox’s orange pippin, Apple', iconName: 'CROP', tasks: ['Task 1'], revenue: 340.0 }, + { crop: 'Macoun, Apples', iconName: 'CROP', tasks: [], revenue: 1234.0 }, + { crop: 'Butter Boy Hybrid, Butternut ', iconName: 'CROP', tasks: ['Task 1'], revenue: 785.5 }, + { crop: 'King Edward, Potato', iconName: 'CROP', tasks: ['Task 1'], revenue: 237.0 }, + { crop: 'Blanco Veneto, Celeriac', iconName: 'CROP', tasks: ['Task 1'], revenue: 895.0 }, + { crop: 'Hollow Crown, Parsnips ', iconName: 'CROP', tasks: ['Task 1'], revenue: 354.0 }, + { + crop: 'Early White Hybrid, Cauliflower', + iconName: 'CROP', + tasks: ['Task 1'], + revenue: 789.5, + }, ].slice(0, length); }; diff --git a/packages/webapp/src/types/index.ts b/packages/webapp/src/types/index.ts index d6c6fc890a..318ee7f60b 100644 --- a/packages/webapp/src/types/index.ts +++ b/packages/webapp/src/types/index.ts @@ -29,3 +29,11 @@ export type CustomRouteComponentProps & { history: History; // Custom history type }; + +// Only typing the properties in use; additional properties are expected in the Location object +export interface Location { + name: string; + location_id: string; + + [key: string]: any; +}