infoalloggi-monorepo/apps/infoalloggi/src/pages/test.tsx

282 lines
6.6 KiB
TypeScript
Raw Normal View History

import type { NextPage } from "next";
/*
import { useState } from "react";
import { useDebounce } from "react-use";
import z from "zod";
import Input from "~/components/custom_ui/input";
import { LoadingPage } from "~/components/loading";
import { Button } from "~/components/ui/button";
type NominatimResponse = z.infer<typeof responseSchema>;
export const responseSchema = z.object({
type: z.string(),
geocoding: z.object({
version: z.string(),
attribution: z.string(),
licence: z.string(),
query: z.string(),
}),
features: z.array(
z.object({
type: z.string(),
properties: z.object({
geocoding: z.object({
place_id: z.number(),
osm_type: z.string(),
osm_id: z.number(),
osm_key: z.string(),
osm_value: z.string(),
type: z.string(),
label: z.string(),
name: z.string(),
postcode: z.string().optional(),
county: z.string().optional(),
state: z.string().optional(),
country: z.string(),
country_code: z.string(),
admin: z.any(),
district: z.string().optional(),
city: z.string().optional(),
}),
}),
geometry: z.object({
type: z.string(),
coordinates: z.array(z.number()),
}),
}),
),
});
const Test: NextPage = () => {
const [value, setValue] = useState("");
const [isLoading, setLoading] = useState(false);
const [options, setOptions] = useState<NominatimResponse["features"]>([]);
useDebounce(
() => {
if (value !== "") {
handleFetch();
}
},
1000,
[value],
);
const handleFetch = () => {
setLoading(true);
const api = `https://nominatim.openstreetmap.org/search?format=geocodejson&limit=5&addressdetails=1&q=${encodeURI(
value,
)}`;
fetch(api)
.then((response) => response.json())
.then((data) => {
const parsed = responseSchema.safeParse(data);
if (parsed.success) {
const filtered = parsed.data.features.filter(
(f) =>
f.properties.geocoding.osm_type === "way" &&
f.properties.geocoding.type === "street" &&
f.properties.geocoding.postcode,
);
// Then deduplicate by city
const uniqueResults = filtered.reduce(
(acc, current) => {
const city = current.properties.geocoding.city;
// If this city doesn't exist or we haven't seen it yet
if (
!city ||
!acc.some((item) => item.properties.geocoding.city === city)
) {
acc.push(current);
}
return acc;
},
[] as typeof filtered,
);
setOptions(uniqueResults);
} else {
console.error("Failed to fetch location data", parsed.error);
}
setLoading(false);
})
.catch((error) => {
console.error(error);
});
};
return (
<div className="flex flex-col gap-2 p-4">
<Input
onChange={({ currentTarget }) => {
setValue(currentTarget.value);
}}
value={value}
/>
{isLoading ? (
<LoadingPage />
) : (
options.map((option, idx) => (
<Button
className="w-full justify-start truncate"
key={idx}
onClick={() => {
console.log(option.properties.geocoding);
}}
variant="outline"
>
<p>
{option.properties.geocoding.name} -{" "}
{option.properties.geocoding.city} -{" "}
{option.properties.geocoding.postcode} -{" "}
{option.properties.geocoding.county} -{" "}
{option.properties.geocoding.country}{" "}
{option.properties.geocoding.country_code}
</p>
</Button>
))
)}
</div>
);
};
export default Test;
*/
const Test: NextPage = () => {
2025-10-22 15:41:53 +02:00
return <FileManagementPage />;
};
export default Test;
2025-10-22 15:41:53 +02:00
import { useEffect, useState } from "react";
import { LoadingPage } from "~/components/loading";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import {
deleteFile,
type FileMetadata,
fetchFiles,
uploadFile,
} from "~/server/storage";
function FileManagementPage() {
const [files, setFiles] = useState<FileMetadata[]>([]);
const [loading, setLoading] = useState(true);
const loadData = async () => {
setLoading(true);
const fileList = await fetchFiles();
setFiles(fileList);
setLoading(false);
};
useEffect(() => {
loadData();
}, []);
const [inputFile, setInputFile] = useState<File | null>(null);
const [inputExpiresAt, setInputExpiresAt] = useState<string | undefined>(
undefined,
);
const handleFileUpload = async () => {
if (inputFile) {
const success = await uploadFile(inputFile, inputExpiresAt);
if (success) {
// Reload list after successful upload
await loadData();
}
}
};
const handleDelete = async (fileID: string) => {
const confirmDelete = window.confirm("Are you sure?");
if (confirmDelete) {
const success = await deleteFile(fileID);
if (success) {
await loadData();
}
}
};
return (
<div className="flex flex-col gap-4 p-4">
<div className="flex items-center gap-4">
<h1 className="font-bold text-2xl">File Storage</h1>
<Button
onClick={async () => {
await loadData();
}}
>
Refresh
</Button>
</div>
<Input
className=""
onChange={(e) => {
setInputFile(e.currentTarget.files?.[0] || null);
}}
type="file"
/>
<Input
className=""
onChange={(e) => {
setInputExpiresAt(new Date(e.currentTarget.value).toISOString());
}}
type="datetime-local"
/>
<Button onClick={handleFileUpload}>Upload</Button>
{loading ? (
<LoadingPage />
) : (
<ul className="space-y-2">
{files.map((file) => (
<li
className="flex items-center justify-between gap-3 rounded-lg border p-3"
key={file.id}
>
{file.mimeType.includes("image") && (
<img
alt={file.originalName}
className="size-20"
2025-10-23 15:16:42 +02:00
src={`/storage-api/file/${file.id}`}
2025-10-22 15:41:53 +02:00
/>
)}
<div className="flex-1">
<p className="font-semibold">{file.originalName}</p>
<p className="text-gray-500 text-sm">
{file.mimeType} | Size: {(file.size / 1024 / 1024).toFixed(2)}{" "}
MB
</p>
<p>Uploaded At: {new Date(file.uploadedAt).toLocaleString()}</p>
{file.expiresAt && (
<p>Expires At: {new Date(file.expiresAt).toLocaleString()}</p>
)}
</div>
<div className="flex space-x-2">
<a
className="rounded bg-blue-500 px-3 py-1 text-sm text-white"
2025-10-23 15:16:42 +02:00
href={`/storage-api/file/${file.id}`}
2025-10-22 15:41:53 +02:00
>
Download
</a>
<button
className="rounded bg-red-500 px-3 py-1 text-sm text-white"
onClick={() => handleDelete(file.id)}
type="button"
>
Delete
</button>
</div>
</li>
))}
</ul>
)}
</div>
);
}