mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
improve filtering for listings
This commit is contained in:
@@ -1,53 +0,0 @@
|
|||||||
import { Card, Checkbox, Descriptions, Divider, Select } from '@douyinfe/semi-ui';
|
|
||||||
import React from 'react';
|
|
||||||
import { useSelector } from '../../../services/state/store.js';
|
|
||||||
import { Typography } from '@douyinfe/semi-ui';
|
|
||||||
|
|
||||||
import './ListingsFilter.less';
|
|
||||||
|
|
||||||
export default function ListingsFilter({ onWatchListFilter, onActivityFilter, onJobNameFilter, onProviderFilter }) {
|
|
||||||
const jobs = useSelector((state) => state.jobs.jobs);
|
|
||||||
const provider = useSelector((state) => state.provider);
|
|
||||||
const { Title } = Typography;
|
|
||||||
return (
|
|
||||||
<Card className="listingsFilter">
|
|
||||||
<Title heading={6}>Filter by:</Title>
|
|
||||||
<Divider />
|
|
||||||
<br />
|
|
||||||
<Descriptions row>
|
|
||||||
<Descriptions.Item itemKey="Watch List">
|
|
||||||
<Checkbox onChange={(e) => onWatchListFilter(e.target.checked)}>Only Watch List</Checkbox>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item itemKey="Activity status">
|
|
||||||
<Checkbox onChange={(e) => onActivityFilter(e.target.checked)}>Only Active Listings</Checkbox>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item itemKey="Job Name">
|
|
||||||
<Select showClear placeholder="Select Job to Filter" onChange={(val) => onJobNameFilter(val)}>
|
|
||||||
{jobs != null &&
|
|
||||||
jobs.length > 0 &&
|
|
||||||
jobs.map((job) => {
|
|
||||||
return (
|
|
||||||
<Select.Option value={job.id} key={job.id}>
|
|
||||||
{job.name}
|
|
||||||
</Select.Option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item itemKey="Provider">
|
|
||||||
<Select showClear placeholder="Select Provider to Filter" onChange={(val) => onProviderFilter(val)}>
|
|
||||||
{provider != null &&
|
|
||||||
provider.length > 0 &&
|
|
||||||
provider.map((prov) => {
|
|
||||||
return (
|
|
||||||
<Select.Option value={prov.id} key={prov.id}>
|
|
||||||
{prov.name}
|
|
||||||
</Select.Option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
</Descriptions.Item>
|
|
||||||
</Descriptions>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
.listingsFilter {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
background: rgb(53, 54, 60);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,18 @@
|
|||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import { Table, Popover, Input, Descriptions, Tag, Image, Empty, Button, Toast, Divider } from '@douyinfe/semi-ui';
|
import {
|
||||||
|
Table,
|
||||||
|
Popover,
|
||||||
|
Input,
|
||||||
|
Descriptions,
|
||||||
|
Tag,
|
||||||
|
Image,
|
||||||
|
Empty,
|
||||||
|
Button,
|
||||||
|
Toast,
|
||||||
|
Divider,
|
||||||
|
Space,
|
||||||
|
Select,
|
||||||
|
} from '@douyinfe/semi-ui';
|
||||||
import { useActions, useSelector } from '../../../services/state/store.js';
|
import { useActions, useSelector } from '../../../services/state/store.js';
|
||||||
import { IconClose, IconDelete, IconSearch, IconStar, IconStarStroked, IconTick } from '@douyinfe/semi-icons';
|
import { IconClose, IconDelete, IconSearch, IconStar, IconStarStroked, IconTick } from '@douyinfe/semi-icons';
|
||||||
import * as timeService from '../../../services/time/timeService.js';
|
import * as timeService from '../../../services/time/timeService.js';
|
||||||
@@ -10,14 +23,20 @@ import './ListingsTable.less';
|
|||||||
import { format } from '../../../services/time/timeService.js';
|
import { format } from '../../../services/time/timeService.js';
|
||||||
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
||||||
import { xhrDelete, xhrPost } from '../../../services/xhr.js';
|
import { xhrDelete, xhrPost } from '../../../services/xhr.js';
|
||||||
import ListingsFilter from './ListingsFilter.jsx';
|
|
||||||
|
|
||||||
const columns = [
|
const getColumns = (provider, setProviderFilter, jobs, setJobNameFilter) => {
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
title: 'Watchlist',
|
title: 'Watchlist',
|
||||||
width: 110,
|
width: 133,
|
||||||
dataIndex: 'isWatched',
|
dataIndex: 'isWatched',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: 'Show only watched listings',
|
||||||
|
value: 'watchList',
|
||||||
|
},
|
||||||
|
],
|
||||||
render: (id, row) => {
|
render: (id, row) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -41,7 +60,9 @@ const columns = [
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
await xhrPost('/api/listings/watch', { listingId: row.id });
|
await xhrPost('/api/listings/watch', { listingId: row.id });
|
||||||
Toast.success(row.isWatched === 1 ? 'Listing removed from Watchlist' : 'Listing added to Watchlist');
|
Toast.success(
|
||||||
|
row.isWatched === 1 ? 'Listing removed from Watchlist' : 'Listing added to Watchlist',
|
||||||
|
);
|
||||||
row.reloadTable();
|
row.reloadTable();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -81,8 +102,14 @@ const columns = [
|
|||||||
{
|
{
|
||||||
title: 'State',
|
title: 'State',
|
||||||
dataIndex: 'is_active',
|
dataIndex: 'is_active',
|
||||||
width: 84,
|
width: 105,
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: 'Show only active listings',
|
||||||
|
value: 'activityStatus',
|
||||||
|
},
|
||||||
|
],
|
||||||
render: (value) => {
|
render: (value) => {
|
||||||
return value ? (
|
return value ? (
|
||||||
<div style={{ color: 'rgba(var(--semi-green-6), 1)' }}>
|
<div style={{ color: 'rgba(var(--semi-green-6), 1)' }}>
|
||||||
@@ -117,6 +144,24 @@ const columns = [
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
dataIndex: 'job_name',
|
dataIndex: 'job_name',
|
||||||
width: 150,
|
width: 150,
|
||||||
|
onFilter: () => true,
|
||||||
|
renderFilterDropdown: () => {
|
||||||
|
return (
|
||||||
|
<Space vertical style={{ padding: 8 }}>
|
||||||
|
<Select showClear placeholder="Select Job to Filter" onChange={(val) => setJobNameFilter(val)}>
|
||||||
|
{jobs != null &&
|
||||||
|
jobs.length > 0 &&
|
||||||
|
jobs.map((job) => {
|
||||||
|
return (
|
||||||
|
<Select.Option value={job.id} key={job.id}>
|
||||||
|
{job.name}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Listing date',
|
title: 'Listing date',
|
||||||
@@ -131,6 +176,24 @@ const columns = [
|
|||||||
dataIndex: 'provider',
|
dataIndex: 'provider',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text) => text.charAt(0).toUpperCase() + text.slice(1),
|
render: (text) => text.charAt(0).toUpperCase() + text.slice(1),
|
||||||
|
onFilter: () => true,
|
||||||
|
renderFilterDropdown: () => {
|
||||||
|
return (
|
||||||
|
<Space vertical style={{ padding: 8 }}>
|
||||||
|
<Select showClear placeholder="Select Provider to Filter" onChange={(val) => setProviderFilter(val)}>
|
||||||
|
{provider != null &&
|
||||||
|
provider.length > 0 &&
|
||||||
|
provider.map((prov) => {
|
||||||
|
return (
|
||||||
|
<Select.Option value={prov.id} key={prov.id}>
|
||||||
|
{prov.name}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Price',
|
title: 'Price',
|
||||||
@@ -158,18 +221,22 @@ const columns = [
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
};
|
||||||
|
|
||||||
const empty = (
|
const empty = (
|
||||||
<Empty
|
<Empty
|
||||||
image={<IllustrationNoResult />}
|
image={<IllustrationNoResult />}
|
||||||
darkModeImage={<IllustrationNoResultDark />}
|
darkModeImage={<IllustrationNoResultDark />}
|
||||||
description="No listings available."
|
description="No listings found."
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function ListingsTable() {
|
export default function ListingsTable() {
|
||||||
const tableData = useSelector((state) => state.listingsTable);
|
const tableData = useSelector((state) => state.listingsTable);
|
||||||
|
const provider = useSelector((state) => state.provider);
|
||||||
|
const jobs = useSelector((state) => state.jobs.jobs);
|
||||||
|
|
||||||
const actions = useActions();
|
const actions = useActions();
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
@@ -179,12 +246,14 @@ export default function ListingsTable() {
|
|||||||
const [jobNameFilter, setJobNameFilter] = useState(null);
|
const [jobNameFilter, setJobNameFilter] = useState(null);
|
||||||
const [activityFilter, setActivityFilter] = useState(null);
|
const [activityFilter, setActivityFilter] = useState(null);
|
||||||
const [providerFilter, setProviderFilter] = useState(null);
|
const [providerFilter, setProviderFilter] = useState(null);
|
||||||
|
const [allFilters, setAllFilters] = useState([]);
|
||||||
|
|
||||||
const [imageWidth, setImageWidth] = useState('100%');
|
const [imageWidth, setImageWidth] = useState('100%');
|
||||||
const handlePageChange = (_page) => {
|
const handlePageChange = (_page) => {
|
||||||
setPage(_page);
|
setPage(_page);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const columns = getColumns(provider, setProviderFilter, jobs, setJobNameFilter);
|
||||||
const loadTable = () => {
|
const loadTable = () => {
|
||||||
let sortfield = null;
|
let sortfield = null;
|
||||||
let sortdir = null;
|
let sortdir = null;
|
||||||
@@ -209,6 +278,20 @@ export default function ListingsTable() {
|
|||||||
|
|
||||||
const handleFilterChange = useMemo(() => debounce((value) => setFreeTextFilter(value), 500), []);
|
const handleFilterChange = useMemo(() => debounce((value) => setFreeTextFilter(value), 500), []);
|
||||||
|
|
||||||
|
const diffArrays = (primary, secondary) => {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
for (const item of secondary) {
|
||||||
|
if (!primary.includes(item)) result[item] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of primary) {
|
||||||
|
if (!secondary.includes(item)) result[item] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [result];
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
// cleanup debounced handler to avoid memory leaks
|
// cleanup debounced handler to avoid memory leaks
|
||||||
@@ -258,12 +341,6 @@ export default function ListingsTable() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ListingsFilter
|
|
||||||
onActivityFilter={setActivityFilter}
|
|
||||||
onWatchListFilter={setWatchListFilter}
|
|
||||||
onJobNameFilter={setJobNameFilter}
|
|
||||||
onProviderFilter={setProviderFilter}
|
|
||||||
/>
|
|
||||||
<Input
|
<Input
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
showClear
|
showClear
|
||||||
@@ -285,7 +362,23 @@ export default function ListingsTable() {
|
|||||||
};
|
};
|
||||||
})}
|
})}
|
||||||
onChange={(changeSet) => {
|
onChange={(changeSet) => {
|
||||||
if (changeSet?.extra?.changeType === 'sorter') {
|
if (changeSet?.extra?.changeType === 'filter') {
|
||||||
|
const transformed = changeSet.filters.map((f) => f.dataIndex);
|
||||||
|
const diff = diffArrays(allFilters, transformed);
|
||||||
|
setAllFilters(transformed);
|
||||||
|
diff.forEach((filter) => {
|
||||||
|
switch (Object.keys(filter)[0]) {
|
||||||
|
case 'isWatched':
|
||||||
|
setWatchListFilter(Object.values(filter)[0]);
|
||||||
|
break;
|
||||||
|
case 'is_active':
|
||||||
|
setActivityFilter(Object.values(filter)[0]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error('Unknown filter: ', filter.dataIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (changeSet?.extra?.changeType === 'sorter') {
|
||||||
setSortData({
|
setSortData({
|
||||||
field: changeSet.sorter.dataIndex,
|
field: changeSet.sorter.dataIndex,
|
||||||
direction: changeSet.sorter.sortOrder === 'ascend' ? 'asc' : 'desc',
|
direction: changeSet.sorter.sortOrder === 'ascend' ? 'asc' : 'desc',
|
||||||
|
|||||||
Reference in New Issue
Block a user