mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
adding ability to tag listings eg if you have applied to it / adding ability to add notes to a listing
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
Banner,
|
||||
Spin,
|
||||
Toast,
|
||||
TextArea,
|
||||
} from '@douyinfe/semi-ui-19';
|
||||
import {
|
||||
IconArrowLeft,
|
||||
@@ -44,6 +45,7 @@ import { xhrPost, xhrDelete } from '../../services/xhr.js';
|
||||
import ListingDeletionModal from '../../components/ListingDeletionModal.jsx';
|
||||
|
||||
import Headline from '../../components/headline/Headline.jsx';
|
||||
import StatusControl from '../../components/listings/StatusControl.jsx';
|
||||
import './ListingDetail.less';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
@@ -65,6 +67,8 @@ export default function ListingDetail() {
|
||||
const map = useRef(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
|
||||
const [notesDraft, setNotesDraft] = useState('');
|
||||
const [notesSaving, setNotesSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchListing() {
|
||||
@@ -82,6 +86,10 @@ export default function ListingDetail() {
|
||||
fetchListing();
|
||||
}, [listingId]);
|
||||
|
||||
useEffect(() => {
|
||||
setNotesDraft(listing?.notes ?? '');
|
||||
}, [listing?.id, listing?.notes]);
|
||||
|
||||
const hasGeo =
|
||||
listing?.latitude != null && listing?.longitude != null && listing?.latitude !== -1 && listing?.longitude !== -1;
|
||||
|
||||
@@ -271,6 +279,32 @@ export default function ListingDetail() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleStatusChange = async (next) => {
|
||||
try {
|
||||
await actions.listingsData.setListingStatus(listing.id, next);
|
||||
await actions.listingsData.getListing(listingId);
|
||||
Toast.success(next ? `Marked as ${next}` : 'Status cleared');
|
||||
} catch (e) {
|
||||
console.error('Failed to update status:', e);
|
||||
Toast.error('Failed to update status');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveNotes = async () => {
|
||||
if (!listing) return;
|
||||
setNotesSaving(true);
|
||||
try {
|
||||
await actions.listingsData.setListingNotes(listing.id, notesDraft);
|
||||
await actions.listingsData.getListing(listingId);
|
||||
Toast.success('Notes saved');
|
||||
} catch (e) {
|
||||
console.error('Failed to save notes:', e);
|
||||
Toast.error('Failed to save notes');
|
||||
} finally {
|
||||
setNotesSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
|
||||
@@ -347,6 +381,7 @@ export default function ListingDetail() {
|
||||
>
|
||||
{listing.isWatched === 1 ? 'Watched' : 'Watch'}
|
||||
</Button>
|
||||
<StatusControl status={listing.status ?? null} onChange={handleStatusChange} />
|
||||
<a href={listing.link} target="_blank" rel="noopener noreferrer" className="listing-detail__open-btn">
|
||||
<IconLink style={{ marginRight: 6 }} />
|
||||
Open listing
|
||||
@@ -380,6 +415,32 @@ export default function ListingDetail() {
|
||||
preview={!!listing.image_url}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="listing-detail__notes">
|
||||
<Title heading={4} className="listing-detail__notes-title">
|
||||
Notes
|
||||
</Title>
|
||||
<TextArea
|
||||
value={notesDraft}
|
||||
onChange={(val) => setNotesDraft(val)}
|
||||
placeholder="Your private notes about this listing…"
|
||||
rows={5}
|
||||
autosize={{ minRows: 4, maxRows: 12 }}
|
||||
className="listing-detail__notes-textarea"
|
||||
showClear
|
||||
/>
|
||||
<Space className="listing-detail__notes-actions">
|
||||
<Button
|
||||
theme="solid"
|
||||
type="primary"
|
||||
loading={notesSaving}
|
||||
disabled={notesSaving || (notesDraft ?? '') === (listing.notes ?? '')}
|
||||
onClick={handleSaveNotes}
|
||||
>
|
||||
Store notes
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24} lg={12}>
|
||||
<div className="listing-detail__info-section">
|
||||
|
||||
@@ -89,6 +89,49 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__notes {
|
||||
padding: 1.5rem;
|
||||
border-top: 1px solid var(--semi-color-border);
|
||||
}
|
||||
|
||||
&__notes-title {
|
||||
margin-bottom: 1rem !important;
|
||||
}
|
||||
|
||||
&__notes-actions {
|
||||
margin-top: 0.75rem;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__notes-textarea {
|
||||
background: #2a2a2a !important;
|
||||
border: 1px solid @color-border-bright !important;
|
||||
border-radius: @radius-input !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
transition: border-color @transition-fast, background @transition-fast !important;
|
||||
|
||||
textarea {
|
||||
background: transparent !important;
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
color: @color-text !important;
|
||||
font-family: @font-ui !important;
|
||||
font-size: @text-base !important;
|
||||
}
|
||||
|
||||
&.semi-input-textarea-focus,
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
border-color: @color-accent !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
background: #2f2f2f !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__watch-btn {
|
||||
color: @color-muted !important;
|
||||
border: 1px solid @color-border-bright !important;
|
||||
|
||||
@@ -6,11 +6,15 @@
|
||||
import ListingsOverview from '../../components/listings/ListingsOverview.jsx';
|
||||
import Headline from '../../components/headline/Headline.jsx';
|
||||
|
||||
export default function Listings() {
|
||||
/**
|
||||
* @param {{ mode?: 'all' | 'watchlist' }} props
|
||||
*/
|
||||
export default function Listings({ mode = 'all' }) {
|
||||
const title = mode === 'watchlist' ? 'Watchlist' : 'Listings';
|
||||
return (
|
||||
<>
|
||||
<Headline text="Listings" />
|
||||
<ListingsOverview />
|
||||
<Headline text={title} />
|
||||
<ListingsOverview mode={mode} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user