merged master

This commit is contained in:
orangecoding
2025-11-27 15:58:12 +01:00
12 changed files with 855 additions and 324 deletions

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { Empty, Table, Button } from '@douyinfe/semi-ui';
import { IconDelete } from '@douyinfe/semi-icons';
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
export default function ProviderTable({ providerData = [], onRemove } = {}) {
export default function ProviderTable({ providerData = [], onRemove, onEdit } = {}) {
return (
<Table
pagination={false}
@@ -30,6 +30,8 @@ export default function ProviderTable({ providerData = [], onRemove } = {}) {
render: (_, record) => {
return (
<div style={{ float: 'right' }}>
<Button type="secondary" icon={<IconEdit />} onClick={() => onEdit(record)} />
<div style={{ display: 'inline-block', width: '16px' }} />
<Button type="danger" icon={<IconDelete />} onClick={() => onRemove(record.url)} />
</div>
);

View File

@@ -11,7 +11,15 @@ import { useNavigate, useParams } from 'react-router-dom';
import { Divider, Input, Switch, Button, TagInput, Toast, Select } from '@douyinfe/semi-ui';
import './JobMutation.less';
import { SegmentPart } from '../../../components/segment/SegmentPart';
import { IconBell, IconBriefcase, IconPaperclip, IconPlayCircle, IconPlusCircle, IconUser } from '@douyinfe/semi-icons';
import {
IconBell,
IconBriefcase,
IconPaperclip,
IconPlayCircle,
IconPlusCircle,
IconUser,
IconClear,
} from '@douyinfe/semi-icons';
export default function JobMutator() {
const jobs = useSelector((state) => state.jobs.jobs);
@@ -26,6 +34,7 @@ export default function JobMutator() {
const defaultNotificationAdapter = jobToBeEdit?.notificationAdapter || [];
const defaultEnabled = jobToBeEdit?.enabled ?? true;
const [providerToEdit, setProviderToEdit] = useState(null);
const [providerCreationVisible, setProviderCreationVisibility] = useState(false);
const [notificationCreationVisible, setNotificationCreationVisibility] = useState(false);
const [editNotificationAdapter, setEditNotificationAdapter] = useState(null);
@@ -42,6 +51,12 @@ export default function JobMutator() {
return Boolean(notificationAdapterData.length && providerData.length && name);
};
const handleProviderEdit = (data) => {
setProviderData(
providerData.map((provider) => (provider.url === data.oldProviderToEdit.url ? data.newData : provider)),
);
};
const mutateJob = async () => {
try {
await xhrPost('/api/jobs', {
@@ -70,6 +85,8 @@ export default function JobMutator() {
onData={(data) => {
setProviderData([...providerData, data]);
}}
onEditData={handleProviderEdit}
providerToEdit={providerToEdit}
/>
{notificationCreationVisible && (
@@ -119,7 +136,10 @@ export default function JobMutator() {
type="primary"
icon={<IconPlusCircle />}
className="jobMutation__newButton"
onClick={() => setProviderCreationVisibility(true)}
onClick={() => {
setProviderToEdit(null);
setProviderCreationVisibility(true);
}}
>
Add new Provider
</Button>
@@ -129,6 +149,10 @@ export default function JobMutator() {
onRemove={(providerUrl) => {
setProviderData(providerData.filter((provider) => provider.url !== providerUrl));
}}
onEdit={(provider) => {
setProviderCreationVisibility(true);
setProviderToEdit(provider);
}}
/>
</SegmentPart>
<Divider margin="1rem" />
@@ -160,7 +184,7 @@ export default function JobMutator() {
</SegmentPart>
<Divider margin="1rem" />
<SegmentPart
Icon={IconBell}
Icon={IconClear}
name="Blacklist"
helpText="If a listing contains one of these words, it will be filtered out. Type in a word, then hit enter."
>

View File

@@ -7,6 +7,7 @@ import { useSelector } from '../../../../../services/state/store';
import { Banner, Button, Form, Modal, Select, Switch } from '@douyinfe/semi-ui';
import './NotificationAdapterMutator.less';
import { useScreenWidth } from '../../../../../hooks/screenWidth.js';
const sortAdapter = (a, b) => {
if (a.name < b.name) {
@@ -72,6 +73,9 @@ export default function NotificationAdapterMutator({
const [validationMessage, setValidationMessage] = useState(null);
const [successMessage, setSuccessMessage] = useState(null);
const width = useScreenWidth();
const isMobile = width <= 850;
const onSubmit = (doStore) => {
if (doStore) {
const validationResults = validate(selectedAdapter);
@@ -172,18 +176,19 @@ export default function NotificationAdapterMutator({
<Modal
title={title != null ? title : 'Adding a new Notification Adapter'}
visible={visible}
style={{ width: '95%' }}
style={{ width: isMobile ? '95%' : '50rem' }}
onCancel={() => onSubmit(false)}
footer={
<div>
<Button type="secondary" disabled={selectedAdapter == null} style={{ float: 'left' }} onClick={() => onTry()}>
<Button type="secondary" disabled={selectedAdapter == null} style={{ float: 'left' }} onClick={onTry}>
Try
</Button>
<Button type="danger" onClick={() => onSubmit(true)}>
Save
</Button>
<Button type="primary" onClick={() => onSubmit(false)}>
<Button theme="light" type="tertiary" onClick={() => onSubmit(false)}>
Cancel
</Button>
<Button theme="solid" type="primary" onClick={() => onSubmit(true)}>
Save
</Button>
</div>
}
>
@@ -212,7 +217,7 @@ export default function NotificationAdapterMutator({
<p>{description}</p>
) : (
<p>
When Fredy found new listings, we like to report them to you. To do so, notification adapter can be
When Fredy finds new listings, we like to report them to you. To do so, notification adapter can be
configured. <br />
There are multiple ways how Fredy can send new listings to you. Chose your weapon...
</p>

View File

@@ -1,10 +1,11 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { Banner, Modal, Select, Input } from '@douyinfe/semi-ui';
import { transform } from '../../../../../services/transformer/providerTransformer';
import { useSelector } from '../../../../../services/state/store';
import { IconLikeHeart } from '@douyinfe/semi-icons';
import './ProviderMutator.less';
import { useScreenWidth } from '../../../../../hooks/screenWidth.js';
const sortProvider = (a, b) => {
if (a.key < b.key) {
@@ -16,11 +17,35 @@ const sortProvider = (a, b) => {
return 0;
};
export default function ProviderMutator({ onVisibilityChanged, visible = false, onData } = {}) {
const returnOriginalSelectedProvider = (providerToEdit, provider) => {
return provider.find((pro) => pro.id === providerToEdit.id);
};
export default function ProviderMutator({
onVisibilityChanged,
visible = false,
onData,
onEditData,
providerToEdit,
} = {}) {
const provider = useSelector((state) => state.provider);
const [selectedProvider, setSelectedProvider] = useState(null);
const [providerUrl, setProviderUrl] = useState(null);
const [validationMessage, setValidationMessage] = useState(null);
useEffect(() => {
if (providerToEdit) {
setSelectedProvider(returnOriginalSelectedProvider(providerToEdit, provider));
setProviderUrl(providerToEdit.url);
} else {
setSelectedProvider(null);
setProviderUrl(null);
}
}, [providerToEdit, visible]);
const width = useScreenWidth();
const isMobile = width <= 850;
const validate = () => {
if (selectedProvider == null || selectedProvider.length === 0 || providerUrl == null || providerUrl.length === 0) {
return 'Please select a provider and copy the browser url into the textfield after configuring your search parameter.';
@@ -41,13 +66,24 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false,
if (doStore) {
const validationResult = validate();
if (validationResult == null) {
onData(
transform({
url: providerUrl,
id: selectedProvider.id,
name: selectedProvider.name,
}),
);
if (providerToEdit != null) {
onEditData({
newData: transform({
url: providerUrl,
id: selectedProvider.id,
name: selectedProvider.name,
}),
oldProviderToEdit: providerToEdit,
});
} else {
onData(
transform({
url: providerUrl,
id: selectedProvider.id,
name: selectedProvider.name,
}),
);
}
setProviderUrl(null);
setSelectedProvider(null);
onVisibilityChanged(false);
@@ -63,11 +99,11 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false,
return (
<Modal
title="Adding a new Provider"
title={providerToEdit ? 'Editing an existing Provider' : 'Adding a new Provider'}
visible={visible}
onOk={() => onSubmit(true)}
onCancel={() => onSubmit(false)}
style={{ width: '50rem' }}
style={{ width: isMobile ? '95%' : '50rem' }}
okText="Save"
>
{validationMessage != null && (
@@ -80,19 +116,26 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false,
description={validationMessage}
/>
)}
<p>
Provider are the <IconLikeHeart style={{ color: '#ff0000' }} /> of Fredy. We're supporting multiple Provider
such as Immowelt, Kalaydo etc. Select a provider from the list below.
<br />
Fredy will then open the provider's url in a new tab.
</p>
<p>
You will need to configure your search parameter like you would do when you do a regular search on the
provider's website.
<br />
When the search results are shown on the website, copy the url and paste it into the textfield below.
</p>
{providerToEdit != null ? (
<p>
You can now edit the <strong>{providerToEdit.name}</strong> provider's URL in the input field below.
</p>
) : (
<>
<p>
Provider are the <IconLikeHeart style={{ color: '#ff0000' }} /> of Fredy. We're supporting multiple Provider
such as Immowelt, Kalaydo etc. Select a provider from the list below.
<br />
Fredy will then open the provider's url in a new tab.
</p>
<p>
You will need to configure your search parameter like you would do when you do a regular search on the
provider's website.
<br />
When the search results are shown on the website, copy the url and paste it into the textfield below.
</p>
</>
)}
<Banner
fullMode={false}
type="warning"
@@ -112,6 +155,7 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false,
filter
placeholder="Select a provider"
className="providerMutator__fields"
disabled={providerToEdit != null}
optionList={provider
.map((pro) => {
return {
@@ -126,7 +170,6 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false,
onChange={(value) => {
const selectedProvider = provider.find((pro) => pro.id === value);
setSelectedProvider(selectedProvider);
window.open(selectedProvider.baseUrl);
}}
/>
@@ -137,7 +180,8 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false,
placeholder="Provider Url"
width={10}
className="providerMutator__fields"
onBlur={(e) => {
value={providerUrl}
onInput={(e) => {
setProviderUrl(e.target.value);
}}
/>