Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lf 4747 create sensor readings kpi component #3705

Merged
merged 33 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6f5af71
LF-4747 Add Bento layout style
Duncan-Brain Feb 28, 2025
8b95619
LF-4747 Customize gap and max columns
Duncan-Brain Mar 1, 2025
bec9e2e
LF-4747 Add update licence
Duncan-Brain Mar 1, 2025
44d999d
LF-4747 Update type in meta definiton
Duncan-Brain Mar 1, 2025
92011d5
LF-4747 Add sensor reading KPI tile
Duncan-Brain Mar 1, 2025
9c8a735
LF-4747 Add stories for sensor reading kpi tile
Duncan-Brain Mar 1, 2025
2625440
LF-4747 Add ruler icon
Duncan-Brain Mar 3, 2025
15e543b
LF-4747 Update Bento layout to have default values
Duncan-Brain Mar 3, 2025
52b7feb
LF-4747 Update Layout stories to require props for story
Duncan-Brain Mar 3, 2025
7c0eede
LF-4747 Create sensor KPI tile
Duncan-Brain Mar 3, 2025
809a617
LF-4747 Add stories for sensor KPI tile
Duncan-Brain Mar 3, 2025
b8db5b6
LF-4747 Extract out reusable Measurement type
Duncan-Brain Mar 3, 2025
f911d5a
LF-4747 Adjust font and positioning styles for sensorKPI
Duncan-Brain Mar 3, 2025
e269c45
LF-4747 Adjust date on scss file
Duncan-Brain Mar 3, 2025
96b70d6
LF-4747 Update bento layout
Duncan-Brain Mar 4, 2025
a9bde5f
LF-4747 Opt out of default mobile behaviour in nested bento layout
Duncan-Brain Mar 4, 2025
1b0cad4
LF-4747 Disallow text wrapping for measure text
Duncan-Brain Mar 4, 2025
088ec03
LF-4747 Update stories
Duncan-Brain Mar 4, 2025
3b8237f
LF-4747 Update sensor tiles to accept intrinsic atributes like key etc
Duncan-Brain Mar 4, 2025
e4e667c
LF-4747 Add color as prop and remove opacity layer due to buggy corne…
Duncan-Brain Mar 4, 2025
68ea95d
LF-4747 Add default color black and handle 3 character colors
Duncan-Brain Mar 4, 2025
60ada2a
LF-4747 improve stories structure and add colors
Duncan-Brain Mar 4, 2025
7a3325d
LF-4747 Use profile discriminator measurement type to decide icon
Duncan-Brain Mar 5, 2025
682ad9e
LF-4747 Remove unused import and unused function
Duncan-Brain Mar 5, 2025
b2a16d0
LF-4747 Fix typing and remove extra css property
Duncan-Brain Mar 6, 2025
a537169
LF-4747 Update css comments
Duncan-Brain Mar 18, 2025
38ebea3
LF-4747 remove excess css style
Duncan-Brain Mar 18, 2025
8523139
LF-4747 combine identical css
Duncan-Brain Mar 18, 2025
7271b58
LF-4747 Remove color validation and replace with JsDoc
Duncan-Brain Mar 18, 2025
2b3b847
LF-4747 fix stories color
Duncan-Brain Mar 18, 2025
a011940
LF-4747 Make stories better so props can be adjusted in storybook - o…
Duncan-Brain Mar 18, 2025
c5ff9e2
LF-4747 Move into SensorTile folder - tbd if moving up to components …
Duncan-Brain Mar 18, 2025
8b8474f
LF-4747 Fix types - thanks Joyce
Duncan-Brain Mar 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/webapp/src/assets/images/ruler.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion packages/webapp/src/components/Icons/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* GNU General Public License for more details, see <<https://www.gnu.org/licenses/>.>
*/

import { FunctionComponent } from 'react';

// Finances Carousel
import { ReactComponent as ExpenseIcon } from '../../assets/images/finance/Expense-icn.svg';
import { ReactComponent as CropIcon } from '../../assets/images/finance/Crop-icn.svg';
Expand Down Expand Up @@ -67,7 +69,7 @@ import { ReactComponent as PlusCircleIcon } from '../../assets/images/plus-circl
import { ReactComponent as TrashIcon } from '../../assets/images/animals/trash_icon_new.svg';
import { ReactComponent as EditIcon } from '../../assets/images/edit.svg';
import { ReactComponent as ChevronLeft } from '../../assets/images/buttons/chevron-left.svg';
import { FunctionComponent } from 'react';
import { ReactComponent as Ruler } from '../../assets/images/ruler.svg';

// Input
import { ReactComponent as LockedIcon } from '../../assets/images/lock-03.svg';
Expand Down Expand Up @@ -139,6 +141,7 @@ export const iconMap = {
TRASH: TrashIcon,
EDIT: EditIcon,
CHEVRON_LEFT: ChevronLeft,
RULER: Ruler,
// Input
LOCKED: LockedIcon,

Expand Down
43 changes: 43 additions & 0 deletions packages/webapp/src/components/Layout/BentoLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2025 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 <https://www.gnu.org/licenses/>.
*/

import clsx from 'clsx';
import { ReactNode } from 'react';
import styles from './layout.module.scss';

export type BentoLayoutProps = {
children: ReactNode;
gap?: number;
maxColumns?: number;
bentoOffMedium?: boolean;
};

export default function BentoLayout({
children,
gap = 8,
maxColumns = 3,
bentoOffMedium = true,
}: BentoLayoutProps) {
const style = {
'--bentoGap': `${gap}px`,
'--bentoColumns': maxColumns,
} as React.CSSProperties;

return (
<div className={clsx(styles.bento, bentoOffMedium && styles.bentoOffMedium)} style={style}>
{children}
</div>
);
}
22 changes: 22 additions & 0 deletions packages/webapp/src/components/Layout/layout.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
*/
@import '@assets/mixin.scss';

.container {
width: 100%;
Expand Down Expand Up @@ -53,4 +54,25 @@

.marginBottom {
margin-bottom: 104px;
}

.bento {
display: flex;
gap: var(--bentoGap);
flex-wrap: wrap;

& > div {
flex: 1 1 calc((100% - var(--bentoGap) * (var(--bentoColumns) - 1)) / var(--bentoColumns));
}
// This overrides default flex responsiveness and jumps to single column of rows style below medium
// Currently this is based of design so making it the default behaviour here despite it being unusual
&.bentoOffMedium {
@include md-breakpoint {
flex-direction: column;

& > div {
flex: unset;
}
}
}
}
99 changes: 99 additions & 0 deletions packages/webapp/src/components/Tile/SensorTile/SensorKPI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2025 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 <https://www.gnu.org/licenses/>.
*/

import Icon, { IconName } from '../../Icons';
import BentoLayout from '../../Layout/BentoLayout';
import { StatusIndicatorPill, StatusIndicatorPillProps } from '../../StatusIndicatorPill';
import styles from './styles.module.scss';
import { TMeasurement } from './SensorReadingKPI';

export interface SensorKPIprops extends React.HTMLAttributes<HTMLDivElement> {
sensor: {
id: string | number;
status: StatusIndicatorPillProps;
};
discriminator: TMeasurement;
measurements: TMeasurement[];
color?: string;
}

export interface MeasurementProps extends TMeasurement, React.HTMLAttributes<HTMLDivElement> {}

const discriminatorIconName: { [key: string]: IconName } = {
depth_elevation: 'RULER',
};

const Measurement = ({ measurement, value, unit, ...rest }: MeasurementProps) => (
<div {...rest} className={styles.measurement}>
<div className={styles.measureText}>{measurement}</div>
<div className={styles.valueText}>
{value}
{unit}
</div>
</div>
);

/**
* A component that displays a sensor KPI.
* AI-assisted JsDoc
*
* @component
* @param props - The props for the component.
* @param props.sensor - The sensor details data.
* @param props.discriminator - The primary differntiator between sensors in a profile.
* @param props.measurements - An array of measurements for each sensor parameter.
* @param props.color - The primary color for styling - should be a six digit hex color.
* @param props.rest - Additional props to spread onto the root div.
* @returns The rendered SensorKPI component.
*/
export default function SensorKPI({
sensor,
discriminator,
measurements,
color = '#000000',
...rest
}: SensorKPIprops) {
const { measurement, value, unit } = discriminator;
const { status, id } = sensor;

// 0D is the code for 5% opacity, 95% transparency
const style = {
'--color': color,
'--colorWithOpacity': `${color}0D`,
} as React.CSSProperties;
return (
<div {...rest} className={styles.sensorKpi} style={style}>
<div className={styles.topDetails}>
<div className={styles.sensor}>
<Icon iconName="SENSOR" className={styles.icon} />
{id}
</div>
<div className={styles.discriminator}>
<Icon iconName={discriminatorIconName[measurement] || 'RULER'} className={styles.icon} />
<div className={styles.discriminatorText}>
{value}
{unit}
</div>
</div>
<StatusIndicatorPill {...status} />
</div>
<BentoLayout maxColumns={2} bentoOffMedium={false}>
{measurements.map((m, i) => (
<Measurement key={`${m.measurement}-${i}`} {...m} />
))}
</BentoLayout>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2025 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 <https://www.gnu.org/licenses/>.
*/

import styles from './styles.module.scss';

export type TMeasurement = {
measurement: string;
value: string | number;
unit: string;
};

export interface SensorReadingKPIprops extends TMeasurement, React.HTMLAttributes<HTMLDivElement> {
color?: string;
}

/**
* A component that displays a sensor reading KPI (Key Performance Indicator).
* AI-assisted JsDoc
*
* @component
* @param props - The props for the component.
* @param props.measurement - The name of the measurement (e.g., "Temperature").
* @param props.value - The value of the sensor reading.
* @param props.unit - The unit of measurement (e.g., "°C", "ppm").
* @param props.color - The primary color for styling - should be a six digit hex color.
* @param props.rest - Additional props to spread onto the root div.
* @returns The rendered SensorReadingKPI component.
*/
export default function SensorReadingKPI({
measurement,
value,
unit,
color = '#000000',
...rest
}: SensorReadingKPIprops) {
// 0D is the code for 5% opacity, 95% transparency
const style = {
'--color': color,
'--colorWithOpacity': `${color}0D`,
} as React.CSSProperties;
return (
<div {...rest} className={styles.sensorReadingKpi} style={style}>
<div className={styles.opaqueLayer}>
<div className={styles.measureText}>{measurement}</div>
<div className={styles.valueText}>
{value}
{unit}
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2025 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 <https://www.gnu.org/licenses/>.
*/

@import '@assets/mixin.scss';

.sensorReadingKpi, .sensorKpi {
border-radius: 4px;
display: flex;
// CSS trick to avoid the opacity layer: https://stackoverflow.com/a/77781112
background: linear-gradient(90deg, var(--colorWithOpacity) 0%, var(--colorWithOpacity) 100%) white;
color: var(--color);
padding: 8px 12px;
flex-direction: column;
flex: 1 0 0;
@include fontFamily();
font-style: normal;
gap: 8px;
}

.measureText {
white-space: nowrap;
align-self: stretch;
font-size: 14px;
font-weight: 300;
line-height: normal;
}

.valueText {
align-self: stretch;
font-size: 24px;
font-weight: 700;
line-height: 24px; /* 100% */
}

.measurement {
display: block;
}

.topDetails {
display: flex;
gap: 8px;
justify-content: space-between;
align-items: center;
align-self: stretch;
}

.sensor {
display: flex;
gap: 8px;
align-items: center;
@include svgColorFill(var(--color));
font-size: 16px;
font-weight: 600;
line-height: 30px; /* 187.5% */
}

.discriminator {
display: flex;
gap: 8px;
align-items: center;
@include svgColorFill(var(--color));
font-size: 14px;
font-weight: 400;
line-height: 30px; /* 214.286% */
}

.icon {
padding: 0px;
background-color: transparent;
}

.discriminatorText {
position: relative;
left: -16px;
}
Loading
Loading