Skip to content

Commit

Permalink
Add frontend implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Pavel7004 committed Nov 13, 2024
1 parent 90c0d00 commit 7de912c
Show file tree
Hide file tree
Showing 22 changed files with 628 additions and 0 deletions.
11 changes: 11 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# Fresh build directory
_fresh/
# npm dependencies
node_modules/
16 changes: 16 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Fresh project

Your new Fresh project is ready to go. You can follow the Fresh "Getting
Started" guide here: https://fresh.deno.dev/docs/getting-started

### Usage

Make sure to install Deno: https://deno.land/manual/getting_started/installation

Then start the project:

```
deno task start
```

This will watch the project directory and restart as necessary.
12 changes: 12 additions & 0 deletions frontend/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { JSX } from "preact";
import { IS_BROWSER } from "$fresh/runtime.ts";

export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
return (
<button
{...props}
disabled={!IS_BROWSER || props.disabled}
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
/>
);
}
39 changes: 39 additions & 0 deletions frontend/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"lock": false,
"tasks": {
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
"manifest": "deno task cli manifest $(pwd)",
"start": "deno run -A --watch=static/,routes/ dev.ts",
"build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts",
"update": "deno run -A -r https://fresh.deno.dev/update ."
},
"lint": {
"rules": {
"tags": [
"fresh",
"recommended"
]
}
},
"exclude": [
"**/_fresh/*"
],
"imports": {
"$fresh/": "https://deno.land/x/[email protected]/",
"preact": "https://esm.sh/[email protected]",
"preact/": "https://esm.sh/[email protected]/",
"@preact/signals": "https://esm.sh/*@preact/[email protected]",
"@preact/signals-core": "https://esm.sh/*@preact/[email protected]",
"tailwindcss": "npm:[email protected]",
"tailwindcss/": "npm:/[email protected]/",
"tailwindcss/plugin": "npm:/[email protected]/plugin.js",
"$std/": "https://deno.land/[email protected]/"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"nodeModulesDir": true
}
8 changes: 8 additions & 0 deletions frontend/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env -S deno run -A --watch=static/,routes/

import dev from "$fresh/dev.ts";
import config from "./fresh.config.ts";

import "$std/dotenv/load.ts";

await dev(import.meta.url, "./main.ts", config);
8 changes: 8 additions & 0 deletions frontend/fresh.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from "$fresh/server.ts";
import tailwind from "$fresh/plugins/tailwind.ts";

export default defineConfig({
plugins: [
tailwind(),
],
});
33 changes: 33 additions & 0 deletions frontend/fresh.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// DO NOT EDIT. This file is generated by Fresh.
// This file SHOULD be checked into source version control.
// This file is automatically updated during development when running `dev.ts`.

import * as $_404 from "./routes/_404.tsx";
import * as $_app from "./routes/_app.tsx";
import * as $api_joke from "./routes/api/joke.ts";
import * as $greet_name_ from "./routes/greet/[name].tsx";
import * as $index from "./routes/index.tsx";
import * as $DataDisplay from "./islands/DataDisplay.tsx";
import * as $Layout from "./islands/Layout.tsx";
import * as $Navbar from "./islands/Navbar.tsx";
import * as $Sidebar from "./islands/Sidebar.tsx";
import type { Manifest } from "$fresh/server.ts";

const manifest = {
routes: {
"./routes/_404.tsx": $_404,
"./routes/_app.tsx": $_app,
"./routes/api/joke.ts": $api_joke,
"./routes/greet/[name].tsx": $greet_name_,
"./routes/index.tsx": $index,
},
islands: {
"./islands/DataDisplay.tsx": $DataDisplay,
"./islands/Layout.tsx": $Layout,
"./islands/Navbar.tsx": $Navbar,
"./islands/Sidebar.tsx": $Sidebar,
},
baseUrl: import.meta.url,
} satisfies Manifest;

export default manifest;
166 changes: 166 additions & 0 deletions frontend/islands/DataDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { useEffect, useState } from "preact/hooks";

interface GeoJSONPoint {
type: "Point";
coordinates: [number, number];
}

interface GeoJSONPolygon {
type: "Polygon";
coordinates: Array<Array<[number, number]>>;
}

interface User {
id: string;
email: string;
registration_date: string;
profile_update_date: string;
avatar_url: string;
}

interface Point {
id: string;
coordinates: GeoJSONPoint;
description: string;
availability: number;
}

interface Route {
id: string;
points: string[];
author: string;
popularity_score: number;
}

interface Lake {
id: string;
name: string;
description: string;
coordinates_boundary: GeoJSONPolygon;
availability_score: number;
max_depth: number;
inflowing_rivers: string[];
outflowing_rivers: string[];
salinity: number;
}

interface SupportRequestReview {
text: string;
date: string;
photo: string;
}

interface SupportRequest {
id: string;
author: string;
route_reference?: string;
lake_reference?: string;
subject: string;
reviews: SupportRequestReview[];
}

type DataItem = User | Point | Route | Lake | SupportRequest;

type DataCategory =
| "users"
| "points"
| "routes"
| "lakes"
| "support_requests";

export default function DataDisplay() {
const [category, setCategory] = useState<DataCategory>("users");
const [data, setData] = useState<DataItem[]>([]);

useEffect(() => {
fetchData().catch(console.error);
}, [category]);

const fetchData = async () => {
const response = await fetch(`http://localhost:34567/${category}`);
if (!response.ok) {
console.error("Failed to fetch data");
return;
}
const jsonData = await response.json();
setData(jsonData);
};

const renderTableHeaders = () => {
if (data.length === 0) return null;
return (
<tr>
{Object.keys(data[0]).map((key) => (
<th
key={key}
class="px-4 py-2 border-b-2 border-nord4 text-left uppercase tracking-wider"
>
{key.replace(/_/g, " ")}
</th>
))}
</tr>
);
};

const renderTableRows = () => {
return data.map((item, index) => (
<tr
key={index}
class={`${index % 2 === 0 ? "bg-nord0" : "bg-nord1"} hover:bg-nord2`}
>
{Object.values(item).map((value, i) => (
<td key={i} class="px-4 py-2 border-b border-nord4">
{renderCellValue(value)}
</td>
))}
</tr>
));
};

const renderCellValue = (value: unknown) => {
if (value === null || value === undefined) {
return "";
} else if (typeof value === "string" || typeof value === "number") {
return value;
} else if (Array.isArray(value)) {
return value.join(", ");
} else if (typeof value === "object") {
return JSON.stringify(value);
} else {
return String(value);
}
};

return (
<div class="p-4">
<div class="mb-6">
<label class="text-nord6 font-semibold mr-2">
Select Data Category:
</label>
<select
class="bg-nord0 text-nord6 border border-nord4 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-nord8"
value={category}
onChange={(e) =>
setCategory(
(e.target as HTMLSelectElement).value as DataCategory,
)}
>
<option value="users">Пользователи</option>
<option value="points">Точки</option>
<option value="routes">Маршруты</option>
<option value="lakes">Озера</option>
<option value="support_requests">Обращения в поддержку</option>
</select>
</div>
<div class="overflow-auto rounded-lg shadow">
<table class="min-w-full table-auto bg-nord0 text-nord6">
<thead class="bg-nord3">
{renderTableHeaders()}
</thead>
<tbody>{renderTableRows()}</tbody>
</table>
</div>
</div>
);
}

27 changes: 27 additions & 0 deletions frontend/islands/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { FunctionalComponent } from "preact";
import { useState } from "preact/hooks";
import Navbar from "./Navbar.tsx";
import Sidebar from "./Sidebar.tsx";
import DataDisplay from "./DataDisplay.tsx";

const Layout: FunctionalComponent = () => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);

const handleToggleSidebar = () => {
setIsSidebarOpen((prev) => !prev);
};

return (
<>
<Navbar onToggleSidebar={handleToggleSidebar} />
<Sidebar
isSidebarOpen={isSidebarOpen}
setIsSidebarOpen={setIsSidebarOpen}
>
<DataDisplay />
</Sidebar>
</>
);
};

export default Layout;
Loading

0 comments on commit 7de912c

Please sign in to comment.