Public Access
feat(sync): show sync changes in banner and details dialog
This commit is contained in:
1 parent
e054e13a59
commit
dabb0c941c
9 files changed
+425
No files matched your search
@@ -0,0 +1,66 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
|
||||
import {Dialog, DialogContent, DialogDescription, DialogTitle} from '@/components/ui/dialog';
|
||||
import {Skeleton} from '@/components/ui/skeleton';
|
||||
import GameRow from './stats/GameRow.jsx';
|
||||
import {formatPlaytime} from '../utils/games.js';
|
||||
import {fetchSyncChanges, groupByKind} from '../utils/syncChanges.js';
|
||||
|
||||
/**
|
||||
* The changes of the last sync, grouped by kind. Loads the result again when it opens: the achievements of a sync
|
||||
* arrive after its games.
|
||||
*/
|
||||
export default function SyncChangesDialog({onClose}) {
|
||||
const {t} = useTranslation();
|
||||
const [changes, setChanges] = useState(null);
|
||||
const [failed, setFailed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let current = true;
|
||||
fetchSyncChanges()
|
||||
.then((body) => {
|
||||
if (!current) return;
|
||||
if (body) setChanges(body.changes ?? []);
|
||||
else setFailed(true);
|
||||
})
|
||||
.catch(() => current && setFailed(true));
|
||||
return () => {
|
||||
current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const value = (change) => {
|
||||
switch (change.kind) {
|
||||
case 'PLAYTIME': return `+${formatPlaytime(change.amount)}`;
|
||||
case 'ACHIEVEMENTS': return `+${change.amount}`;
|
||||
case 'STATUS': return `${t(`BACKLOG.STATUS.${change.fromStatus}`)} → ${t(`BACKLOG.STATUS.${change.toStatus}`)}`;
|
||||
default: return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent
|
||||
className="max-h-[85vh] overflow-y-auto overflow-x-hidden max-sm:top-auto max-sm:bottom-0 max-sm:max-w-full max-sm:translate-y-0 max-sm:rounded-b-none sm:max-w-xl">
|
||||
<DialogTitle className="pr-6">{t('SYNC_CHANGES.TITLE')}</DialogTitle>
|
||||
<DialogDescription className="sr-only">{t('SYNC_CHANGES.TITLE')}</DialogDescription>
|
||||
{changes === null && !failed && <Skeleton className="h-24 rounded-xl"/>}
|
||||
{failed && <p className="text-sm text-muted-foreground">{t('SYNC_CHANGES.ERROR')}</p>}
|
||||
{changes && changes.length === 0 && <p className="text-sm text-muted-foreground">{t('SYNC_CHANGES.EMPTY')}</p>}
|
||||
{changes && groupByKind(changes).map(({kind, changes: rows}) => (
|
||||
<section key={kind} className="space-y-2">
|
||||
<h3 className="text-sm font-semibold">{t(`SYNC_CHANGES.HEADING.${kind}`)}</h3>
|
||||
<ul className="space-y-2">
|
||||
{rows.map((change, index) => (
|
||||
<li key={`${change.appid}-${change.fromStatus}-${index}`}>
|
||||
<GameRow game={change} value={value(change)} linked/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user