109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
import { Paperclip } from "lucide-react";
|
|
import { useRef } from "react";
|
|
|
|
import { ApiError } from "../api/client";
|
|
import { useToast } from "../context/ToastContext";
|
|
import type { AttachmentRead } from "../domain/types";
|
|
|
|
/**
|
|
* Сетка фото партии. Переиспользуется на странице партии (игрок) и в админ-правке —
|
|
* URL/хуки приходят снаружи (cookie игрока и админа доходят до своих путей).
|
|
*/
|
|
export function MatchMedia({
|
|
attachments,
|
|
editable = false,
|
|
onUpload,
|
|
onDelete,
|
|
pending = false,
|
|
max = 10,
|
|
}: {
|
|
attachments: AttachmentRead[];
|
|
editable?: boolean;
|
|
onUpload?: (file: File) => Promise<unknown>;
|
|
onDelete?: (id: number) => Promise<unknown>;
|
|
pending?: boolean;
|
|
max?: number;
|
|
}) {
|
|
const toast = useToast();
|
|
const fileRef = useRef<HTMLInputElement>(null);
|
|
const full = attachments.length >= max;
|
|
|
|
const pick = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const files = Array.from(e.target.files ?? []);
|
|
e.target.value = "";
|
|
if (!onUpload) return;
|
|
let slots = max - attachments.length;
|
|
for (const f of files) {
|
|
if (slots <= 0) {
|
|
toast.show(`Максимум ${max} фото`);
|
|
break;
|
|
}
|
|
try {
|
|
await onUpload(f);
|
|
slots--;
|
|
} catch (err) {
|
|
toast.show(err instanceof ApiError ? err.message : "Не удалось загрузить");
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
const remove = async (id: number) => {
|
|
if (!onDelete) return;
|
|
try {
|
|
await onDelete(id);
|
|
} catch (e) {
|
|
toast.show(e instanceof ApiError ? e.message : "Не удалось удалить");
|
|
}
|
|
};
|
|
|
|
if (!editable && attachments.length === 0) return null;
|
|
|
|
return (
|
|
<div>
|
|
{attachments.length > 0 && (
|
|
<div className="media-grid">
|
|
{attachments.map((a) => (
|
|
<div key={a.id} className="media-item">
|
|
<a href={a.url} target="_blank" rel="noreferrer">
|
|
<img src={a.url} alt="фото партии" />
|
|
</a>
|
|
{editable && (
|
|
<button
|
|
className="media-del"
|
|
onClick={() => remove(a.id)}
|
|
disabled={pending}
|
|
aria-label="Удалить фото"
|
|
>
|
|
✕
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
{editable && !full && (
|
|
<>
|
|
<button
|
|
className="btn small"
|
|
style={{ marginTop: 8, gap: 6 }}
|
|
onClick={() => fileRef.current?.click()}
|
|
disabled={pending}
|
|
>
|
|
<Paperclip size={16} />
|
|
Прикрепить медиа
|
|
</button>
|
|
<input
|
|
ref={fileRef}
|
|
type="file"
|
|
accept="image/png,image/jpeg,image/webp"
|
|
multiple
|
|
style={{ display: "none" }}
|
|
onChange={pick}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|