Files
MindSift/frontend/src/components/SharedImportToast.tsx
T
sandrodachandClaude Opus 5.5 f76b0f34ff
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m5s
feat: Kanban touch drag, admins delete backups, bigger share message
- Kanban on phones/tablets: hold a card, drag it onto a card or column, release; the page scrolls at the edges
- Admin settings: database backups can be deleted (DELETE /api/v1/admin/backups/{name})
- Share message: large Liste/Aufgabe buttons, close button, tap outside closes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:54:17 +01:00

117 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useState } from 'react';
import { ArrowRight, CheckSquare, Inbox, ShoppingCart, X } from 'lucide-react';
import { api, triggerHaptic } from '../lib/api';
import type { SharedList } from '../lib/types';
// After sharing from another app: the items are already in the Quick Dump (nothing to tap), and for a
// few seconds they can be sent on as a task or onto a list instead – one tap, the flow stays as fast.
const AUTO_HIDE_MS = 8000;
interface SharedImportToastProps {
thoughtIds: string[]; // created on the server (offline captures have no id yet and get no buttons)
lists: SharedList[];
onClose: () => void;
onConverted: (message: string) => void;
}
export const SharedImportToast: React.FC<SharedImportToastProps> = ({ thoughtIds, lists, onClose, onConverted }) => {
const [choosingList, setChoosingList] = useState(false);
const [busy, setBusy] = useState(false);
const [failed, setFailed] = useState(false);
const writableLists = lists.filter((l) => l.isOwner || l.role !== 'viewer');
const count = thoughtIds.length;
// Disappears on its own unless a list is being picked or a conversion runs
useEffect(() => {
if (choosingList || busy) return;
const timer = window.setTimeout(onClose, AUTO_HIDE_MS);
return () => window.clearTimeout(timer);
}, [choosingList, busy, onClose]);
const convert = async (convertTo: 'task' | 'list_item', list?: SharedList) => {
triggerHaptic(15);
setBusy(true);
setFailed(false);
try {
for (const id of thoughtIds) {
await api.triageAction(id, 'keep', { convertTo, targetListId: list?.id });
}
const what = count === 1 ? 'Eintrag' : `${count} Einträge`;
onConverted(convertTo === 'task' ? `${count === 1 ? 'Als Aufgabe' : `${count} Aufgaben`} angelegt` : `${what} auf „${list?.title}“`);
} catch {
setFailed(true);
setBusy(false);
}
};
const toList = () => {
if (writableLists.length === 1) convert('list_item', writableLists[0]);
else setChoosingList(true);
};
const big =
'min-h-[52px] px-3 py-2 rounded-xl text-sm font-semibold flex items-center justify-center gap-2 touch-manipulation cursor-pointer active:scale-98 disabled:opacity-50';
return (
<>
{/* Tapping anywhere else closes it (transparent, the page stays visible) */}
<div className="fixed inset-0 z-[59]" onClick={onClose} aria-hidden />
<div
role="status"
className="fixed left-1/2 -translate-x-1/2 bottom-[calc(5.5rem+env(safe-area-inset-bottom))] sm:bottom-4 z-[60] w-[calc(100vw-1.5rem)] max-w-md panel-surface shadow-panel overflow-hidden p-4 space-y-3 animate-in fade-in slide-in-from-bottom-2 duration-200"
>
<button
type="button"
onClick={onClose}
aria-label="Schließen"
className="absolute top-2 right-2 h-8 w-8 flex items-center justify-center rounded-btn text-faint hover:text-main"
>
<X className="w-4 h-4" />
</button>
<div className="pr-8">
<p className="flex items-center gap-2 text-base font-semibold text-main">
<Inbox className="w-5 h-5 text-accent shrink-0" />
{count === 1 ? 'Im Quick Dump abgelegt' : `${count} Einträge im Quick Dump abgelegt`}
</p>
<p className="text-xs text-muted mt-1">
{choosingList ? 'Auf welche Liste?' : 'Oder gleich woanders hin:'}
</p>
</div>
{failed && <p className="text-xs text-status-err">Umwandeln hat nicht geklappt – der Eintrag bleibt im Quick Dump.</p>}
{!choosingList ? (
<div className={`grid gap-2 ${writableLists.length > 0 ? 'grid-cols-2' : 'grid-cols-1'}`}>
{writableLists.length > 0 && (
<button type="button" disabled={busy} onClick={toList} className={`${big} bg-canvas border border-border text-main`}>
<ShoppingCart className="w-4 h-4 text-accent" />
<span>Liste</span>
</button>
)}
<button type="button" disabled={busy} onClick={() => convert('task')} className={`${big} bg-accent text-on-accent`}>
<CheckSquare className="w-4 h-4" />
<span>Aufgabe</span>
<ArrowRight className="w-4 h-4" />
</button>
</div>
) : (
<div className="grid grid-cols-2 gap-2">
{writableLists.map((l) => (
<button
key={l.id}
type="button"
disabled={busy}
onClick={() => convert('list_item', l)}
className={`${big} bg-canvas border border-border text-main`}
>
<span>{l.icon}</span>
<span className="truncate">{l.title}</span>
</button>
))}
</div>
)}
</div>
</>
);
};