mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
fredy goes multilingual 🇩🇪 🇺🇸
This commit is contained in:
@@ -22,8 +22,10 @@ import ListingsTable from '../table/ListingsTable.jsx';
|
||||
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
||||
|
||||
import './ListingsOverview.less';
|
||||
import { useTranslation } from '../../services/i18n/i18n.jsx';
|
||||
|
||||
const ListingsOverview = ({ mode = 'all' }) => {
|
||||
const t = useTranslation();
|
||||
const isWatchlistMode = mode === 'watchlist';
|
||||
const listingsData = useSelector((state) => state.listingsData);
|
||||
const providers = useSelector((state) => state.provider);
|
||||
@@ -106,22 +108,24 @@ const ListingsOverview = ({ mode = 'all' }) => {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
await xhrPost('/api/listings/watch', { listingId: item.id });
|
||||
Toast.success(item.isWatched === 1 ? 'Listing removed from Watchlist' : 'Listing added to Watchlist');
|
||||
Toast.success(
|
||||
item.isWatched === 1 ? t('listings.toastRemovedFromWatchlist') : t('listings.toastAddedToWatchlist'),
|
||||
);
|
||||
loadData();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Toast.error('Failed to operate Watchlist');
|
||||
Toast.error(t('listings.toastWatchlistError'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleStatusChange = async (item, nextStatus) => {
|
||||
try {
|
||||
await actions.listingsData.setListingStatus(item.id, nextStatus);
|
||||
Toast.success(nextStatus ? `Marked as ${nextStatus}` : 'Status cleared');
|
||||
Toast.success(nextStatus ? `Marked as ${nextStatus}` : t('listings.toastStatusCleared'));
|
||||
loadData();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Toast.error('Failed to update status');
|
||||
Toast.error(t('listings.toastStatusUpdateError'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -142,10 +146,10 @@ const ListingsOverview = ({ mode = 'all' }) => {
|
||||
await actions.userSettings.setListingDeletionPreference({ skipPrompt: true, hardDelete });
|
||||
}
|
||||
await xhrDelete('/api/listings/', { ids: [id], hardDelete });
|
||||
Toast.success('Listing successfully removed');
|
||||
Toast.success(t('listings.toastDeleted'));
|
||||
loadData();
|
||||
} catch (error) {
|
||||
Toast.error(error.message || 'Error deleting listing');
|
||||
Toast.error(error.message || t('listings.toastDeleteError'));
|
||||
} finally {
|
||||
setDeleteModalVisible(false);
|
||||
setListingToDelete(null);
|
||||
@@ -161,7 +165,7 @@ const ListingsOverview = ({ mode = 'all' }) => {
|
||||
className="listingsOverview__topbar__search"
|
||||
prefix={<IconSearch />}
|
||||
showClear
|
||||
placeholder="Search"
|
||||
placeholder={t('listings.searchPlaceholder')}
|
||||
defaultValue={freeTextFilter ?? ''}
|
||||
onChange={handleFilterChange}
|
||||
/>
|
||||
@@ -176,9 +180,9 @@ const ListingsOverview = ({ mode = 'all' }) => {
|
||||
setPage(1);
|
||||
}}
|
||||
>
|
||||
<Radio value="all">All</Radio>
|
||||
<Radio value="true">Active</Radio>
|
||||
<Radio value="false">Inactive</Radio>
|
||||
<Radio value="all">{t('listings.filterAll')}</Radio>
|
||||
<Radio value="true">{t('listings.filterActive')}</Radio>
|
||||
<Radio value="false">{t('listings.filterInactive')}</Radio>
|
||||
</RadioGroup>
|
||||
|
||||
{!isWatchlistMode && (
|
||||
@@ -192,14 +196,14 @@ const ListingsOverview = ({ mode = 'all' }) => {
|
||||
setPage(1);
|
||||
}}
|
||||
>
|
||||
<Radio value="all">All</Radio>
|
||||
<Radio value="true">Watched</Radio>
|
||||
<Radio value="false">Unwatched</Radio>
|
||||
<Radio value="all">{t('listings.filterAll')}</Radio>
|
||||
<Radio value="true">{t('listings.filterWatched')}</Radio>
|
||||
<Radio value="false">{t('listings.filterUnwatched')}</Radio>
|
||||
</RadioGroup>
|
||||
)}
|
||||
|
||||
<Select
|
||||
placeholder="Status"
|
||||
placeholder={t('listings.filterStatusPlaceholder')}
|
||||
showClear
|
||||
onChange={(val) => {
|
||||
setStatusFilter(val ?? null);
|
||||
@@ -208,14 +212,14 @@ const ListingsOverview = ({ mode = 'all' }) => {
|
||||
value={statusFilter}
|
||||
style={{ width: 150 }}
|
||||
>
|
||||
<Select.Option value="applied">Applied</Select.Option>
|
||||
<Select.Option value="rejected">Rejected</Select.Option>
|
||||
<Select.Option value="accepted">Accepted</Select.Option>
|
||||
<Select.Option value="none">No status</Select.Option>
|
||||
<Select.Option value="applied">{t('listings.filterStatusApplied')}</Select.Option>
|
||||
<Select.Option value="rejected">{t('listings.filterStatusRejected')}</Select.Option>
|
||||
<Select.Option value="accepted">{t('listings.filterStatusAccepted')}</Select.Option>
|
||||
<Select.Option value="none">{t('listings.filterStatusNone')}</Select.Option>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
placeholder="Provider"
|
||||
placeholder={t('listings.filterProviderPlaceholder')}
|
||||
showClear
|
||||
onChange={(val) => {
|
||||
setProviderFilter(val);
|
||||
@@ -232,7 +236,7 @@ const ListingsOverview = ({ mode = 'all' }) => {
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
placeholder="Job"
|
||||
placeholder={t('listings.filterJobPlaceholder')}
|
||||
showClear
|
||||
onChange={(val) => {
|
||||
setJobNameFilter(val);
|
||||
@@ -249,40 +253,40 @@ const ListingsOverview = ({ mode = 'all' }) => {
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
prefix="Sort by"
|
||||
prefix={t('listings.sortPrefix')}
|
||||
className="listingsOverview__topbar__sort"
|
||||
style={{ width: 220 }}
|
||||
value={sortField}
|
||||
onChange={(val) => setSortField(val)}
|
||||
>
|
||||
<Select.Option value="job_name">Job Name</Select.Option>
|
||||
<Select.Option value="created_at">Listing Date</Select.Option>
|
||||
<Select.Option value="price">Price</Select.Option>
|
||||
<Select.Option value="provider">Provider</Select.Option>
|
||||
<Select.Option value="job_name">{t('listings.sortByJobName')}</Select.Option>
|
||||
<Select.Option value="created_at">{t('listings.sortByDate')}</Select.Option>
|
||||
<Select.Option value="price">{t('listings.sortByPrice')}</Select.Option>
|
||||
<Select.Option value="provider">{t('listings.sortByProvider')}</Select.Option>
|
||||
</Select>
|
||||
|
||||
<Button
|
||||
icon={sortDir === 'asc' ? <IconArrowUp /> : <IconArrowDown />}
|
||||
onClick={() => setSortDir(sortDir === 'asc' ? 'desc' : 'asc')}
|
||||
title={sortDir === 'asc' ? 'Ascending' : 'Descending'}
|
||||
title={sortDir === 'asc' ? t('listings.sortAscending') : t('listings.sortDescending')}
|
||||
/>
|
||||
|
||||
<div className="listingsOverview__topbar__view-toggle">
|
||||
<Tooltip content="Grid view">
|
||||
<Tooltip content={t('listings.tooltipGridView')}>
|
||||
<Button
|
||||
icon={<IconGridView />}
|
||||
theme={viewMode === 'grid' ? 'solid' : 'borderless'}
|
||||
onClick={() => actions.userSettings.setListingsViewMode('grid')}
|
||||
aria-label="Grid view"
|
||||
aria-label={t('common.ariaGridView')}
|
||||
aria-pressed={viewMode === 'grid'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content="Table view">
|
||||
<Tooltip content={t('listings.tooltipTableView')}>
|
||||
<Button
|
||||
icon={<IconList />}
|
||||
theme={viewMode === 'table' ? 'solid' : 'borderless'}
|
||||
onClick={() => actions.userSettings.setListingsViewMode('table')}
|
||||
aria-label="Table view"
|
||||
aria-label={t('common.ariaTableView')}
|
||||
aria-pressed={viewMode === 'table'}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -293,7 +297,7 @@ const ListingsOverview = ({ mode = 'all' }) => {
|
||||
<Empty
|
||||
image={<IllustrationNoResult />}
|
||||
darkModeImage={<IllustrationNoResultDark />}
|
||||
description="No listings available yet..."
|
||||
description={t('listings.empty')}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -8,27 +8,12 @@ import { Dropdown, Button, Tooltip } from '@douyinfe/semi-ui-19';
|
||||
import { IconChevronDown } from '@douyinfe/semi-icons';
|
||||
|
||||
import './StatusControl.less';
|
||||
|
||||
const STATUS_TOOLTIP =
|
||||
'Track where you stand with this listing: Applied once you have reached out, Rejected if it did not work out, or Accepted if you got it.';
|
||||
import { useTranslation } from '../../services/i18n/i18n.jsx';
|
||||
|
||||
/**
|
||||
* @typedef {('applied'|'rejected'|'accepted'|null)} ListingStatus
|
||||
*/
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ value: null, label: 'None' },
|
||||
{ value: 'applied', label: 'Applied' },
|
||||
{ value: 'rejected', label: 'Rejected' },
|
||||
{ value: 'accepted', label: 'Accepted' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Look up the option metadata for a status value.
|
||||
* @param {ListingStatus} status
|
||||
*/
|
||||
const optionFor = (status) => STATUS_OPTIONS.find((o) => o.value === status) ?? STATUS_OPTIONS[0];
|
||||
|
||||
/**
|
||||
* Shared control for setting a listing's user-decision status
|
||||
* (Applied / Rejected / Accepted).
|
||||
@@ -44,8 +29,21 @@ const optionFor = (status) => STATUS_OPTIONS.find((o) => o.value === status) ??
|
||||
* @param {(e: React.MouseEvent) => void} [props.onTriggerClick] - Optional click handler to stop propagation on the trigger.
|
||||
*/
|
||||
export default function StatusControl({ status = null, onChange, compact = false, onTriggerClick }) {
|
||||
const t = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false);
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ value: null, label: t('listings.status.none') },
|
||||
{ value: 'applied', label: t('listings.status.applied') },
|
||||
{ value: 'rejected', label: t('listings.status.rejected') },
|
||||
{ value: 'accepted', label: t('listings.status.accepted') },
|
||||
];
|
||||
|
||||
const STATUS_TOOLTIP = t('listings.status.tooltip');
|
||||
|
||||
const optionFor = (status) => STATUS_OPTIONS.find((o) => o.value === status) ?? STATUS_OPTIONS[0];
|
||||
|
||||
const current = optionFor(status);
|
||||
|
||||
const handlePick = (next) => {
|
||||
@@ -94,7 +92,7 @@ export default function StatusControl({ status = null, onChange, compact = false
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
{status ? current.label : 'Status'}
|
||||
{status ? current.label : t('listings.status.statusLabel')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user