Calculating the distance (#255)

* migra for distance

* adding distance calculator

* adding ability to store home address

* improve distance calculation

* calculating distance

* show distance in grid view

* upgrading dependencies

* moving to react 19

* ability to clone a job

* fixing tests

* polishing
This commit is contained in:
Christian Kellner
2026-01-22 16:09:36 +01:00
committed by GitHub
parent 51b4e51f3f
commit 4dd0370ec1
47 changed files with 1135 additions and 615 deletions

View File

@@ -6,7 +6,7 @@
import React from 'react';
import './FredyFooter.less';
import { useSelector } from '../../services/state/store.js';
import { Typography } from '@douyinfe/semi-ui';
import { Typography } from '@douyinfe/semi-ui-19';
export default function FredyFooter() {
const { Text } = Typography;

View File

@@ -20,12 +20,13 @@ import {
Pagination,
Toast,
Empty,
} from '@douyinfe/semi-ui';
} from '@douyinfe/semi-ui-19';
import {
IconAlertTriangle,
IconDelete,
IconDescend2,
IconEdit,
IconCopy,
IconPlayCircle,
IconBriefcase,
IconBell,
@@ -198,12 +199,14 @@ const JobGrid = () => {
<div className="jobGrid__searchbar">
<Input prefix={<IconSearch />} showClear placeholder="Search" onChange={handleFilterChange} />
<Popover content="Filter / Sort Results" style={{ color: 'white', padding: '.5rem' }}>
<Button
icon={<IconFilter />}
onClick={() => {
setShowFilterBar(!showFilterBar);
}}
/>
<div>
<Button
icon={<IconFilter />}
onClick={() => {
setShowFilterBar(!showFilterBar);
}}
/>
</div>
</Popover>
</div>
</div>
@@ -287,7 +290,9 @@ const JobGrid = () => {
'This job has been shared with you by another user, therefor it is read-only.',
)}
>
<IconAlertTriangle style={{ color: 'rgba(var(--semi-yellow-7), 1)', marginLeft: '8px' }} />
<div>
<IconAlertTriangle style={{ color: 'rgba(var(--semi-yellow-7), 1)', marginLeft: '8px' }} />
</div>
</Popover>
)}
</div>
@@ -343,40 +348,59 @@ const JobGrid = () => {
<div className="jobGrid__actions">
<Popover content={getPopoverContent('Run Job')}>
<Button
type="primary"
theme="solid"
icon={<IconPlayCircle />}
disabled={job.isOnlyShared || job.running}
onClick={() => onJobRun(job.id)}
/>
<div>
<Button
type="primary"
theme="solid"
icon={<IconPlayCircle />}
disabled={job.isOnlyShared || job.running}
onClick={() => onJobRun(job.id)}
/>
</div>
</Popover>
<Popover content={getPopoverContent('Edit a Job')}>
<Button
type="secondary"
theme="solid"
icon={<IconEdit />}
disabled={job.isOnlyShared}
onClick={() => navigate(`/jobs/edit/${job.id}`)}
/>
<div>
<Button
type="secondary"
theme="solid"
icon={<IconEdit />}
disabled={job.isOnlyShared}
onClick={() => navigate(`/jobs/edit/${job.id}`)}
/>
</div>
</Popover>
<Popover content={getPopoverContent('Clone Job')}>
<div>
<Button
type="tertiary"
theme="solid"
icon={<IconCopy />}
disabled={job.isOnlyShared}
onClick={() => navigate('/jobs/new', { state: { cloneFrom: job.id } })}
/>
</div>
</Popover>
<Popover content={getPopoverContent('Delete all found Listings of this Job')}>
<Button
type="danger"
theme="solid"
icon={<IconDescend2 />}
disabled={job.isOnlyShared}
onClick={() => onListingRemoval(job.id)}
/>
<div>
<Button
type="danger"
theme="solid"
icon={<IconDescend2 />}
disabled={job.isOnlyShared}
onClick={() => onListingRemoval(job.id)}
/>
</div>
</Popover>
<Popover content={getPopoverContent('Delete Job')}>
<Button
type="danger"
theme="solid"
icon={<IconDelete />}
disabled={job.isOnlyShared}
onClick={() => onJobRemoval(job.id)}
/>
<div>
<Button
type="danger"
theme="solid"
icon={<IconDelete />}
disabled={job.isOnlyShared}
onClick={() => onJobRemoval(job.id)}
/>
</div>
</Popover>
</div>
</div>

View File

@@ -19,7 +19,7 @@ import {
Select,
Popover,
Empty,
} from '@douyinfe/semi-ui';
} from '@douyinfe/semi-ui-19';
import {
IconBriefcase,
IconCart,
@@ -31,6 +31,7 @@ import {
IconStarStroked,
IconSearch,
IconFilter,
IconActivity,
} from '@douyinfe/semi-icons';
import no_image from '../../../assets/no_image.jpg';
import * as timeService from '../../../services/time/timeService.js';
@@ -107,12 +108,14 @@ const ListingsGrid = () => {
<div className="listingsGrid__searchbar">
<Input prefix={<IconSearch />} showClear placeholder="Search" onChange={handleFilterChange} />
<Popover content="Filter / Sort Results" style={{ color: 'white', padding: '.5rem' }}>
<Button
icon={<IconFilter />}
onClick={() => {
setShowFilterBar(!showFilterBar);
}}
/>
<div>
<Button
icon={<IconFilter />}
onClick={() => {
setShowFilterBar(!showFilterBar);
}}
/>
</div>
</Popover>
</div>
{showFilterBar && (
@@ -272,6 +275,15 @@ const ListingsGrid = () => {
<Text type="tertiary" size="small" icon={<IconBriefcase />}>
{item.provider.charAt(0).toUpperCase() + item.provider.slice(1)}
</Text>
{item.distance_to_destination ? (
<Text type="tertiary" size="small" icon={<IconActivity />}>
{item.distance_to_destination} m to chosen address
</Text>
) : (
<Text type="tertiary" size="small" icon={<IconActivity />}>
Distance cannot be calculated, provide an address
</Text>
)}
</Space>
<Divider margin=".6rem" />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>

View File

@@ -4,7 +4,7 @@
*/
import React from 'react';
import { Typography } from '@douyinfe/semi-ui';
import { Typography } from '@douyinfe/semi-ui-19';
export default function Headline({ text, size = 3 } = {}) {
const { Title } = Typography;

View File

@@ -4,7 +4,7 @@
*/
import React from 'react';
import { Button } from '@douyinfe/semi-ui';
import { Button } from '@douyinfe/semi-ui-19';
import { xhrPost } from '../../services/xhr';
import { IconUser } from '@douyinfe/semi-icons';

View File

@@ -4,7 +4,7 @@
*/
import React, { useEffect, useState } from 'react';
import { Button, Nav } from '@douyinfe/semi-ui';
import { Button, Nav } from '@douyinfe/semi-ui-19';
import { IconStar, IconSetting, IconTerminal, IconHistogram, IconSidebar } from '@douyinfe/semi-icons';
import logoWhite from '../../assets/logo_white.png';
import heart from '../../assets/heart.png';
@@ -12,7 +12,6 @@ import Logout from '../logout/Logout.jsx';
import { useLocation, useNavigate } from 'react-router-dom';
import './Navigate.less';
import { useFeature } from '../../hooks/featureHook.js';
import { useScreenWidth } from '../../hooks/screenWidth.js';
export default function Navigation({ isAdmin }) {
@@ -21,7 +20,6 @@ export default function Navigation({ isAdmin }) {
const width = useScreenWidth();
const [collapsed, setCollapsed] = useState(width <= 850);
const watchlistFeature = useFeature('WATCHLIST_MANAGEMENT') || false;
useEffect(() => {
if (width <= 850) {
@@ -46,11 +44,9 @@ export default function Navigation({ isAdmin }) {
if (isAdmin) {
const settingsItems = [
{ itemKey: '/users', text: 'User Management' },
{ itemKey: '/userSettings', text: 'User Specific Settings' },
{ itemKey: '/generalSettings', text: 'General Settings' },
];
if (watchlistFeature) {
settingsItems.push({ itemKey: '/watchlistManagement', text: 'Watchlist Management' });
}
items.push({
itemKey: 'settings',
@@ -58,6 +54,13 @@ export default function Navigation({ isAdmin }) {
icon: <IconSetting />,
items: settingsItems,
});
} else {
items.push({
itemKey: 'settings',
text: 'Settings',
icon: <IconSetting />,
items: [{ itemKey: '/userSettings', text: 'User Specific Settings' }],
});
}
function parsePathName(name) {

View File

@@ -4,7 +4,7 @@
*/
import React from 'react';
import { Card } from '@douyinfe/semi-ui';
import { Card } from '@douyinfe/semi-ui-19';
import './SegmentParts.less';

View File

@@ -5,7 +5,7 @@
import React from 'react';
import { Empty, Table, Button } from '@douyinfe/semi-ui';
import { Empty, Table, Button } from '@douyinfe/semi-ui-19';
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
export default function NotificationAdapterTable({ notificationAdapter = [], onRemove, onEdit } = {}) {

View File

@@ -5,7 +5,7 @@
import React from 'react';
import { Empty, Table, Button } from '@douyinfe/semi-ui';
import { Empty, Table, Button } from '@douyinfe/semi-ui-19';
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
export default function ProviderTable({ providerData = [], onRemove, onEdit } = {}) {

View File

@@ -7,7 +7,7 @@ import React from 'react';
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
import { format } from '../../services/time/timeService';
import { Table, Button, Empty } from '@douyinfe/semi-ui';
import { Table, Button, Empty } from '@douyinfe/semi-ui-19';
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
const empty = (

View File

@@ -4,7 +4,7 @@
*/
import React from 'react';
import { Modal } from '@douyinfe/semi-ui';
import { Modal } from '@douyinfe/semi-ui-19';
import Logo from '../logo/Logo.jsx';
import { xhrPost } from '../../services/xhr.js';

View File

@@ -4,9 +4,9 @@
*/
import React from 'react';
import { Collapse, Descriptions } from '@douyinfe/semi-ui';
import { Collapse, Descriptions } from '@douyinfe/semi-ui-19';
import { useSelector } from '../../services/state/store.js';
import { MarkdownRender } from '@douyinfe/semi-ui';
import { MarkdownRender } from '@douyinfe/semi-ui-19';
import './VersionBanner.less';