fredy goes multilingual 🇩🇪 🇺🇸

This commit is contained in:
orangecoding
2026-06-04 10:35:42 +02:00
parent 019b9ac87b
commit 1dcb852ea1
40 changed files with 2072 additions and 879 deletions

View File

@@ -19,119 +19,130 @@ import * as timeService from '../../../services/time/timeService.js';
import StatusControl from '../../listings/StatusControl.jsx';
import './ListingsGrid.less';
import { useTranslation, useLocale } from '../../../services/i18n/i18n.jsx';
/**
* @param {{ listings: object[], onWatch: Function, onNavigate: Function, onDelete: Function, onStatusChange: Function }} props
*/
const ListingsGrid = ({ listings, onWatch, onNavigate, onDelete, onStatusChange }) => (
<div className="listingsGrid__grid">
{listings.map((item) => (
<div
key={item.id}
className="listingsGrid__card"
style={{ cursor: 'pointer' }}
role="button"
tabIndex={0}
onClick={() => onNavigate(item.id)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') onNavigate(item.id);
}}
>
<div className="listingsGrid__card__image-wrapper">
<img
src={item.image_url || no_image}
alt={item.title}
onError={(e) => {
e.target.src = no_image;
}}
/>
{!item.is_active && (
<div className="listingsGrid__card__inactive-watermark">
<span>Inactive</span>
</div>
)}
<Tooltip content={item.isWatched === 1 ? 'Remove from Watchlist' : 'Add to Watchlist'}>
<button
type="button"
className="listingsGrid__card__star"
onClick={(e) => onWatch(e, item)}
aria-label={item.isWatched === 1 ? 'Remove from watchlist' : 'Add to watchlist'}
const ListingsGrid = ({ listings, onWatch, onNavigate, onDelete, onStatusChange }) => {
const t = useTranslation();
const locale = useLocale();
return (
<div className="listingsGrid__grid">
{listings.map((item) => (
<div
key={item.id}
className="listingsGrid__card"
style={{ cursor: 'pointer' }}
role="button"
tabIndex={0}
onClick={() => onNavigate(item.id)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') onNavigate(item.id);
}}
>
<div className="listingsGrid__card__image-wrapper">
<img
src={item.image_url || no_image}
alt={item.title}
onError={(e) => {
e.target.src = no_image;
}}
/>
{!item.is_active && (
<div className="listingsGrid__card__inactive-watermark">
<span>{t('listings.cardInactive')}</span>
</div>
)}
<Tooltip
content={
item.isWatched === 1 ? t('listings.tooltipRemoveFromWatchlist') : t('listings.tooltipAddToWatchlist')
}
>
{item.isWatched === 1 ? <IconStar /> : <IconStarStroked />}
</button>
</Tooltip>
</div>
<div className="listingsGrid__card__body">
<div className="listingsGrid__card__title" title={item.title}>
{item.title}
<button
type="button"
className="listingsGrid__card__star"
onClick={(e) => onWatch(e, item)}
aria-label={
item.isWatched === 1 ? t('listings.tooltipRemoveFromWatchlist') : t('listings.tooltipAddToWatchlist')
}
>
{item.isWatched === 1 ? <IconStar /> : <IconStarStroked />}
</button>
</Tooltip>
</div>
{item.price && (
<div className="listingsGrid__card__price">
<IconCart size="small" />
{item.price}
<div className="listingsGrid__card__body">
<div className="listingsGrid__card__title" title={item.title}>
{item.title}
</div>
)}
{item.address && (
{item.price && (
<div className="listingsGrid__card__price">
<IconCart size="small" />
{item.price}
</div>
)}
{item.address && (
<div className="listingsGrid__card__meta">
<IconMapPin />
{item.address}
</div>
)}
<div className="listingsGrid__card__meta">
<IconMapPin />
{item.address}
<IconBriefcase />
{item.provider}
</div>
)}
<div className="listingsGrid__card__meta">
<IconBriefcase />
{item.provider}
<div className="listingsGrid__card__provider">{timeService.format(item.created_at, false, locale)}</div>
</div>
<div className="listingsGrid__card__provider">{timeService.format(item.created_at, false)}</div>
</div>
<div className="listingsGrid__card__actions" onClick={(e) => e.stopPropagation()}>
<StatusControl
status={item.status?.status ?? null}
compact
onChange={(next) => onStatusChange?.(item, next)}
onTriggerClick={(e) => e.stopPropagation()}
/>
<Tooltip content="Original Listing">
<Button
size="small"
icon={<IconLink />}
style={{ color: '#60a5fa' }}
theme="borderless"
onClick={(e) => {
e.stopPropagation();
window.open(item.link, '_blank');
}}
<div className="listingsGrid__card__actions" onClick={(e) => e.stopPropagation()}>
<StatusControl
status={item.status?.status ?? null}
compact
onChange={(next) => onStatusChange?.(item, next)}
onTriggerClick={(e) => e.stopPropagation()}
/>
</Tooltip>
<Tooltip content="View in Fredy">
<Button
size="small"
icon={<IconEyeOpened />}
style={{ color: '#34d399' }}
theme="borderless"
onClick={(e) => {
e.stopPropagation();
onNavigate(item.id);
}}
/>
</Tooltip>
<Tooltip content="Remove">
<Button
size="small"
icon={<IconDelete />}
style={{ color: '#fb7185' }}
theme="borderless"
onClick={(e) => {
e.stopPropagation();
onDelete(item.id);
}}
/>
</Tooltip>
<Tooltip content={t('listings.tooltipOriginalListing')}>
<Button
size="small"
icon={<IconLink />}
style={{ color: '#60a5fa' }}
theme="borderless"
onClick={(e) => {
e.stopPropagation();
window.open(item.link, '_blank');
}}
/>
</Tooltip>
<Tooltip content={t('listings.tooltipViewInFredy')}>
<Button
size="small"
icon={<IconEyeOpened />}
style={{ color: '#34d399' }}
theme="borderless"
onClick={(e) => {
e.stopPropagation();
onNavigate(item.id);
}}
/>
</Tooltip>
<Tooltip content={t('listings.tooltipRemove')}>
<Button
size="small"
icon={<IconDelete />}
style={{ color: '#fb7185' }}
theme="borderless"
onClick={(e) => {
e.stopPropagation();
onDelete(item.id);
}}
/>
</Tooltip>
</div>
</div>
</div>
))}
</div>
);
))}
</div>
);
};
export default ListingsGrid;