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;
+}