2021-01-21 16:09:23 +01:00
import React , { Fragment , useState } from 'react' ;
import NotificationAdapterMutator from './components/notificationAdapter/NotificationAdapterMutator' ;
import NotificationAdapterTable from '../../../components/table/NotificationAdapterTable' ;
import ProviderTable from '../../../components/table/ProviderTable' ;
import ProviderMutator from './components/provider/ProviderMutator' ;
import Headline from '../../../components/headline/Headline' ;
2025-09-18 20:09:11 +02:00
import { useActions , useSelector } from '../../../services/state/store' ;
2021-01-21 16:09:23 +01:00
import { xhrPost } from '../../../services/xhr' ;
2025-09-02 20:18:37 +02:00
import { useNavigate , useParams } from 'react-router-dom' ;
2023-03-20 08:52:13 +01:00
import { Divider , Input , Switch , Button , TagInput , Toast } from '@douyinfe/semi-ui' ;
2021-01-21 16:09:23 +01:00
import './JobMutation.less' ;
2022-03-27 19:42:58 +02:00
import { SegmentPart } from '../../../components/segment/SegmentPart' ;
2023-03-20 08:52:13 +01:00
import { IconPlusCircle } from '@douyinfe/semi-icons' ;
2021-01-21 16:09:23 +01:00
export default function JobMutator ( ) {
const jobs = useSelector ( ( state ) => state . jobs . jobs ) ;
const params = useParams ( ) ;
const jobToBeEdit = params . jobId == null ? null : jobs . find ( ( job ) => job . id === params . jobId ) ;
const defaultBlacklist = jobToBeEdit ? . blacklist || [ ] ;
const defaultName = jobToBeEdit ? . name || null ;
const defaultProviderData = jobToBeEdit ? . provider || [ ] ;
const defaultNotificationAdapter = jobToBeEdit ? . notificationAdapter || [ ] ;
const defaultEnabled = jobToBeEdit ? . enabled ? ? true ;
const [ providerCreationVisible , setProviderCreationVisibility ] = useState ( false ) ;
const [ notificationCreationVisible , setNotificationCreationVisibility ] = useState ( false ) ;
2021-05-20 14:21:29 +02:00
const [ editNotificationAdapter , setEditNotificationAdapter ] = useState ( null ) ;
2021-01-21 16:09:23 +01:00
const [ providerData , setProviderData ] = useState ( defaultProviderData ) ;
const [ name , setName ] = useState ( defaultName ) ;
const [ blacklist , setBlacklist ] = useState ( defaultBlacklist ) ;
const [ notificationAdapterData , setNotificationAdapterData ] = useState ( defaultNotificationAdapter ) ;
const [ enabled , setEnabled ] = useState ( defaultEnabled ) ;
2025-09-02 20:18:37 +02:00
const navigate = useNavigate ( ) ;
2025-09-18 20:09:11 +02:00
const actions = useActions ( ) ;
2021-01-21 16:09:23 +01:00
const isSavingEnabled = ( ) => {
2025-08-01 09:51:42 +02:00
return Boolean ( notificationAdapterData . length && providerData . length && name ) ;
2021-01-21 16:09:23 +01:00
} ;
const mutateJob = async ( ) => {
try {
await xhrPost ( '/api/jobs' , {
provider : providerData ,
notificationAdapter : notificationAdapterData ,
name ,
blacklist ,
enabled ,
jobId : jobToBeEdit ? . id || null ,
} ) ;
2025-09-18 20:09:11 +02:00
await actions . jobs . getJobs ( ) ;
2023-03-20 08:52:13 +01:00
Toast . success ( 'Job successfully saved...' ) ;
2025-09-02 20:18:37 +02:00
navigate ( '/jobs' ) ;
2021-01-21 16:09:23 +01:00
} catch ( Exception ) {
2021-05-13 17:22:59 +02:00
console . error ( Exception . json . message ) ;
2023-03-20 08:52:13 +01:00
Toast . error ( Exception . json != null ? Exception . json . message : Exception ) ;
2021-01-21 16:09:23 +01:00
}
} ;
return (
< Fragment >
< ProviderMutator
visible = { providerCreationVisible }
onVisibilityChanged = { ( visible ) => setProviderCreationVisibility ( visible ) }
onData = { ( data ) => {
setProviderData ( [ ... providerData , data ] ) ;
} }
/ >
2021-05-20 14:21:29 +02:00
{ notificationCreationVisible && (
< NotificationAdapterMutator
visible = { notificationCreationVisible }
onVisibilityChanged = { ( visible ) => {
setEditNotificationAdapter ( null ) ;
setNotificationCreationVisibility ( visible ) ;
} }
selected = { notificationAdapterData }
editNotificationAdapter = {
editNotificationAdapter == null
? null
: notificationAdapterData . find ( ( adapter ) => adapter . id === editNotificationAdapter )
}
onData = { ( data ) => {
const oldData = [ ... notificationAdapterData ] . filter ( ( o ) => o . id !== data . id ) ;
setNotificationAdapterData ( [ ... oldData , data ] ) ;
} }
/ >
) }
2021-01-21 16:09:23 +01:00
2025-09-27 18:01:42 +02:00
< Headline text = { jobToBeEdit ? 'Edit Job' : 'Create new Job' } / >
2023-03-20 08:52:13 +01:00
< form >
2022-03-27 19:42:58 +02:00
< SegmentPart name = "Name" >
2023-03-20 08:52:13 +01:00
< Input
2025-05-27 12:01:26 +02:00
autoFocus
2021-01-21 16:09:23 +01:00
type = "text"
maxLength = { 40 }
placeholder = "Name"
width = { 6 }
2023-03-20 08:52:13 +01:00
value = { name }
onChange = { ( value ) => setName ( value ) }
2021-01-21 16:09:23 +01:00
/ >
2022-03-27 19:42:58 +02:00
< / SegmentPart >
2023-03-20 08:52:13 +01:00
< Divider margin = "1rem" / >
2022-03-27 19:42:58 +02:00
< SegmentPart
2025-08-01 09:51:42 +02:00
name = "Providers"
2022-03-27 19:42:58 +02:00
icon = "briefcase"
2025-08-01 09:51:42 +02:00
helpText = { `
A provider is essentially the service ( e . g . ImmoScout24 , Kleinanzeigen ) that Fredy searches for new listings .
Fredy will open a new tab pointing to the website of this provider . You have to adjust your search parameter
and click on "Search" . If the results are being shown , copy the browser URL in here .
` }
2022-03-27 19:42:58 +02:00
>
2023-03-20 08:52:13 +01:00
< Button
type = "primary"
icon = { < IconPlusCircle / > }
className = "jobMutation__newButton"
onClick = { ( ) => setProviderCreationVisibility ( true ) }
>
2022-03-27 19:42:58 +02:00
Add new Provider
2023-03-20 08:52:13 +01:00
< / Button >
2021-01-21 16:09:23 +01:00
< ProviderTable
providerData = { providerData }
2025-07-19 20:10:19 +02:00
onRemove = { ( providerUrl ) => {
setProviderData ( providerData . filter ( ( provider ) => provider . url !== providerUrl ) ) ;
2021-01-21 16:09:23 +01:00
} }
/ >
2022-03-27 19:42:58 +02:00
< / SegmentPart >
2023-03-20 08:52:13 +01:00
< Divider margin = "1rem" / >
2022-03-27 19:42:58 +02:00
< SegmentPart
icon = "bell"
2025-08-01 09:51:42 +02:00
name = "Notification Adapters"
2022-03-27 19:42:58 +02:00
helpText = "Fredy supports multiple ways to notify you about new findings. These are called notification adapter. You can chose between email, Telegram etc."
>
2023-03-20 08:52:13 +01:00
< Button
type = "primary"
2022-03-27 19:42:58 +02:00
className = "jobMutation__newButton"
2023-03-20 08:52:13 +01:00
icon = { < IconPlusCircle / > }
2022-03-27 19:42:58 +02:00
onClick = { ( ) => setNotificationCreationVisibility ( true ) }
>
Add new Notification Adapter
2023-03-20 08:52:13 +01:00
< / Button >
2021-01-21 16:09:23 +01:00
< NotificationAdapterTable
notificationAdapter = { notificationAdapterData }
onRemove = { ( adapterId ) => {
2021-05-20 14:21:29 +02:00
setEditNotificationAdapter ( null ) ;
2021-01-21 16:09:23 +01:00
setNotificationAdapterData ( notificationAdapterData . filter ( ( adapter ) => adapter . id !== adapterId ) ) ;
} }
2021-05-20 14:21:29 +02:00
onEdit = { ( adapterId ) => {
setEditNotificationAdapter ( adapterId ) ;
setNotificationCreationVisibility ( true ) ;
} }
2021-01-21 16:09:23 +01:00
/ >
2022-03-27 19:42:58 +02:00
< / SegmentPart >
2023-03-20 08:52:13 +01:00
< Divider margin = "1rem" / >
2022-03-27 19:42:58 +02:00
< SegmentPart
icon = "bell"
name = "Blacklist"
2023-03-20 08:52:13 +01:00
helpText = "If a listing contains one of these words, it will be filtered out. Type in a word, then hit enter."
2022-03-27 19:42:58 +02:00
>
2023-03-20 08:52:13 +01:00
< TagInput
value = { blacklist || [ ] }
placeholder = "Add a word for filtering..."
onChange = { ( v ) => setBlacklist ( [ ... v ] ) }
2021-01-21 16:09:23 +01:00
/ >
2022-03-27 19:42:58 +02:00
< / SegmentPart >
2023-03-20 08:52:13 +01:00
< Divider margin = "1rem" / >
2022-03-27 19:42:58 +02:00
< SegmentPart
icon = "play circle outline"
name = "Job activation"
2025-08-01 09:51:42 +02:00
helpText = "Whether or not the job is activated. Inactive jobs will be ignored when Fredy checks for new listings."
2022-03-27 19:42:58 +02:00
>
2021-01-21 16:09:23 +01:00
< Switch className = "jobMutation__spaceTop" onChange = { ( checked ) => setEnabled ( checked ) } checked = { enabled } / >
2022-03-27 19:42:58 +02:00
< / SegmentPart >
2023-03-20 08:52:13 +01:00
< Divider margin = "1rem" / >
2025-09-02 20:18:37 +02:00
< Button type = "danger" style = { { marginRight : '1rem' } } onClick = { ( ) => navigate ( '/jobs' ) } >
2021-01-21 16:09:23 +01:00
Cancel
< / Button >
2023-03-20 08:52:13 +01:00
< Button type = "primary" icon = { < IconPlusCircle / > } disabled = { ! isSavingEnabled ( ) } onClick = { mutateJob } >
2021-01-21 16:09:23 +01:00
Save
< / Button >
2023-03-20 08:52:13 +01:00
< / form >
2021-01-21 16:09:23 +01:00
< / Fragment >
) ;
}