Skip to content

Commit 0567ee3

Browse files
authored
Merge pull request #4150 from LiteFarmOrg/LF-5273-pull-shared-quantity-value-inputs-out-of-crop-sale-form-into-reusable-components
LF-5273: Pull shared quantity+value inputs out of crop sale form into reusable components
2 parents 94d79d5 + 717b8bb commit 0567ee3

22 files changed

Lines changed: 622 additions & 756 deletions
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2026 LiteFarm.org
3+
* This file is part of LiteFarm.
4+
*
5+
* LiteFarm is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* LiteFarm is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
import { useTranslation } from 'react-i18next';
17+
import PureCropTile from '../index';
18+
import styles from '../styles.module.scss';
19+
20+
export interface CropVarietySaleTileData {
21+
crop_variety_name: string;
22+
crop_translation_key: string;
23+
crop_variety_photo_url: string;
24+
}
25+
26+
interface CropVarietySaleTileProps {
27+
cropVariety: CropVarietySaleTileData;
28+
}
29+
30+
export default function CropVarietySaleTile({ cropVariety }: CropVarietySaleTileProps) {
31+
const { t } = useTranslation();
32+
const { crop_variety_name, crop_translation_key, crop_variety_photo_url } = cropVariety;
33+
const translatedCropName = t(`crop:${crop_translation_key}`);
34+
const title = crop_variety_name || translatedCropName;
35+
36+
return (
37+
<PureCropTile src={crop_variety_photo_url} alt={title} title={title}>
38+
<span className={styles.cropSubtitle}>{translatedCropName}</span>
39+
</PureCropTile>
40+
);
41+
}

packages/webapp/src/components/CropTile/index.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default function PureCropTile({
1919
isPastVariety,
2020
isCropTemplate,
2121
children,
22-
isSelected,
22+
isSelected = false,
2323
status,
2424
}) {
2525
const { active = 0, abandoned = 0, planned = 0, completed = 0, noPlans = 0 } = cropCount;
@@ -92,7 +92,7 @@ export default function PureCropTile({
9292
);
9393
}
9494

95-
PureCropTile.prototype = {
95+
PureCropTile.propTypes = {
9696
className: PropTypes.string,
9797
onClick: PropTypes.func,
9898
style: PropTypes.object,

packages/webapp/src/components/CropTile/styles.module.scss

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@
8181

8282
.icon {
8383
margin-right: 4px;
84-
fill: var(--teal700)
84+
fill: var(--teal700);
8585
}
8686

8787
.pastIcon {
88-
fill: var(--teal900)
88+
fill: var(--teal900);
8989
}
9090

9191
.cropCountContainer {
@@ -109,4 +109,10 @@
109109
text-overflow: ellipsis;
110110
margin-bottom: 2px;
111111
white-space: nowrap;
112-
}
112+
}
113+
114+
.cropSubtitle {
115+
font-size: 12px;
116+
line-height: 16px;
117+
margin: 4px 0;
118+
}

packages/webapp/src/components/Forms/GeneralRevenue/CropSaleItem.jsx

Lines changed: 16 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -13,94 +13,39 @@
1313
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
1414
*/
1515

16-
import PureManagementPlanTile from '../../CropTile/ManagementPlanTile';
17-
import { harvestAmounts } from '../../../util/convert-units/unit';
18-
import {
19-
CROP_VARIETY_SALE,
20-
CROP_VARIETY_ID,
21-
SALE_VALUE,
22-
QUANTITY,
23-
QUANTITY_UNIT,
24-
} from './constants';
25-
import Unit from '../../Form/Unit';
26-
import Input, { getInputErrors } from '../../Form/Input';
27-
import { useTranslation } from 'react-i18next';
16+
import CropVarietySaleTile from '../../CropTile/CropVarietySaleTile';
17+
import SaleLineItem from './SaleLineItem';
18+
import { CROP_VARIETY_SALE, CROP_VARIETY_ID } from './constants';
2819
import styles from './styles.module.scss';
2920
import PropTypes from 'prop-types';
3021

31-
function CropSaleItem({
32-
managementPlan,
33-
system,
34-
currency,
35-
reactHookFormFunctions,
36-
cropVarietyId,
37-
disabledInput,
38-
}) {
39-
const { t } = useTranslation();
40-
const { management_plan_id, firstTaskDate, status } = managementPlan;
41-
const {
42-
control,
43-
register,
44-
getValues,
45-
setValue,
46-
watch,
47-
formState: { errors },
48-
} = reactHookFormFunctions;
49-
const saleValueRegisterName = `${CROP_VARIETY_SALE}.${cropVarietyId}.${SALE_VALUE}`;
50-
const cropVarietyIdRegisterName = `${CROP_VARIETY_SALE}.${cropVarietyId}.${CROP_VARIETY_ID}`;
51-
52-
register(cropVarietyIdRegisterName, {
53-
required: true,
54-
value: cropVarietyId,
55-
});
56-
22+
function CropSaleItem({ cropVariety, cropVarietyId, system, currency, disabledInput }) {
5723
return (
5824
<div className={styles.saleItemContainer}>
59-
<PureManagementPlanTile
60-
key={management_plan_id}
61-
managementPlan={managementPlan}
62-
date={firstTaskDate}
63-
status={status}
64-
/>
25+
<CropVarietySaleTile cropVariety={cropVariety} />
6526
<div className={styles.saleItemInputGroup}>
66-
<Unit
67-
label={t('common:QUANTITY')}
68-
register={register}
69-
name={`${CROP_VARIETY_SALE}.${cropVarietyId}.${QUANTITY}`}
70-
displayUnitName={`${CROP_VARIETY_SALE}.${cropVarietyId}.${QUANTITY_UNIT}`}
71-
unitType={harvestAmounts}
27+
<SaleLineItem
28+
fieldPrefix={CROP_VARIETY_SALE}
29+
entityId={cropVarietyId}
30+
entityIdFieldKey={CROP_VARIETY_ID}
7231
system={system}
73-
hookFormSetValue={setValue}
74-
hookFormGetValue={getValues}
75-
hookFromWatch={watch}
76-
control={control}
77-
required
78-
disabled={disabledInput}
79-
/>
80-
<Input
81-
label={`${t('SALE.ADD_SALE.TABLE_HEADERS.TOTAL')} (${currency})`}
82-
type="number"
83-
hookFormRegister={register(saleValueRegisterName, {
84-
required: true,
85-
valueAsNumber: true,
86-
min: { value: 0, message: t('SALE.ADD_SALE.SALE_VALUE_ERROR') },
87-
max: { value: 999999999, message: t('SALE.ADD_SALE.SALE_VALUE_ERROR') },
88-
})}
8932
currency={currency}
90-
errors={getInputErrors(errors, saleValueRegisterName)}
91-
disabled={disabledInput}
33+
disabledInput={disabledInput}
9234
/>
9335
</div>
9436
</div>
9537
);
9638
}
9739

9840
CropSaleItem.propTypes = {
99-
managementPlan: PropTypes.object.isRequired,
41+
cropVariety: PropTypes.shape({
42+
crop_variety_name: PropTypes.string.isRequired,
43+
crop_translation_key: PropTypes.string.isRequired,
44+
crop_variety_photo_url: PropTypes.string.isRequired,
45+
}).isRequired,
46+
cropVarietyId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
10047
system: PropTypes.string.isRequired,
10148
currency: PropTypes.string.isRequired,
102-
reactHookFormFunctions: PropTypes.object.isRequired,
103-
cropVarietyId: PropTypes.string.isRequired,
10449
disabledInput: PropTypes.bool.isRequired,
10550
};
10651

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2026 LiteFarm.org
3+
* This file is part of LiteFarm.
4+
*
5+
* LiteFarm is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* LiteFarm is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
import { useFormContext } from 'react-hook-form';
17+
import { harvestAmounts } from '../../../util/convert-units/unit';
18+
import { QUANTITY, QUANTITY_UNIT, SALE_VALUE } from './constants';
19+
import Unit from '../../Form/Unit';
20+
import Input, { getInputErrors } from '../../Form/Input';
21+
import { useTranslation } from 'react-i18next';
22+
23+
interface SaleLineItemProps {
24+
fieldPrefix: string;
25+
entityId: string;
26+
entityIdFieldKey: string;
27+
system: string;
28+
currency: string;
29+
disabledInput: boolean;
30+
}
31+
32+
function SaleLineItem({
33+
fieldPrefix,
34+
entityId,
35+
entityIdFieldKey,
36+
system,
37+
currency,
38+
disabledInput,
39+
}: SaleLineItemProps) {
40+
const { t } = useTranslation();
41+
const {
42+
control,
43+
register,
44+
getValues,
45+
setValue,
46+
watch,
47+
formState: { errors },
48+
} = useFormContext();
49+
const saleValueName = `${fieldPrefix}.${entityId}.${SALE_VALUE}`;
50+
51+
register(`${fieldPrefix}.${entityId}.${entityIdFieldKey}`, {
52+
required: true,
53+
value: entityId,
54+
});
55+
56+
return (
57+
<>
58+
{/* @ts-expect-error */}
59+
<Unit
60+
label={t('common:QUANTITY')}
61+
register={register}
62+
name={`${fieldPrefix}.${entityId}.${QUANTITY}`}
63+
displayUnitName={`${fieldPrefix}.${entityId}.${QUANTITY_UNIT}`}
64+
unitType={harvestAmounts}
65+
system={system}
66+
hookFormSetValue={setValue}
67+
hookFormGetValue={getValues}
68+
hookFromWatch={watch}
69+
control={control}
70+
required
71+
disabled={disabledInput}
72+
/>
73+
<Input
74+
label={`${t('SALE.ADD_SALE.TABLE_HEADERS.TOTAL')} (${currency})`}
75+
type="number"
76+
hookFormRegister={register(saleValueName, {
77+
required: true,
78+
valueAsNumber: true,
79+
min: { value: 0, message: t('SALE.ADD_SALE.SALE_VALUE_ERROR') },
80+
max: { value: 999999999, message: t('SALE.ADD_SALE.SALE_VALUE_ERROR') },
81+
})}
82+
currency={currency}
83+
errors={getInputErrors(errors, saleValueName)}
84+
disabled={disabledInput}
85+
/>
86+
</>
87+
);
88+
}
89+
90+
export default SaleLineItem;

0 commit comments

Comments
 (0)