Add vite react #45
|
@ -16,6 +16,7 @@
|
||||||
"prettier": "^2.8.3",
|
"prettier": "^2.8.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^6.7.0",
|
||||||
"tailwindcss": "^3.2.4"
|
"tailwindcss": "^3.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { useState } from "react";
|
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import Nav from "./components/Nav";
|
import Nav from "./components/Nav";
|
||||||
|
import { Outlet, useLoaderData } from "react-router-dom";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [count, setCount] = useState(0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="dark:bg-gray-800 min-h-screen">
|
<div className="dark:bg-gray-800 min-h-screen">
|
||||||
<Nav />
|
<Nav />
|
||||||
{/* {% block content %}No content here.{% endblock content %} */}
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
{/* {% load version %} */}
|
{/* {% load version %} */}
|
||||||
{/* <span className="fixed left-2 bottom-2 text-xs text-slate-300 dark:text-slate-600">{% version %} ({% version_date %})</span> */}
|
{/* <span className="fixed left-2 bottom-2 text-xs text-slate-300 dark:text-slate-600">{% version %} ({% version_date %})</span> */}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
function Nav() {
|
function Nav() {
|
||||||
return (
|
return (
|
||||||
<nav className="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded">
|
<nav className="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded">
|
||||||
<div className="container flex flex-wrap items-center justify-between mx-auto">
|
<div className="container flex flex-wrap items-center justify-between mx-auto">
|
||||||
<a
|
<Link
|
||||||
href="{% url 'index' %}"
|
to="/"
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
>
|
>
|
||||||
<span className="text-4xl">⌚</span>
|
<span className="text-4xl">⌚</span>
|
||||||
<span className="self-center text-xl font-semibold whitespace-nowrap text-white">
|
<span className="self-center text-xl font-semibold whitespace-nowrap text-white">
|
||||||
Timetracker
|
Timetracker
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</Link>
|
||||||
<div className="w-full md:block md:w-auto">
|
<div className="w-full md:block md:w-auto">
|
||||||
<ul className="flex flex-col md:flex-row p-4 mt-4 dark:text-white">
|
<ul className="flex flex-col md:flex-row p-4 mt-4 dark:text-white">
|
||||||
<li>
|
<li>
|
||||||
|
@ -51,12 +53,12 @@ function Nav() {
|
||||||
{/* {% endif %} */}
|
{/* {% endif %} */}
|
||||||
{/* {% if session_count > 0 %} */}
|
{/* {% if session_count > 0 %} */}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<Link
|
||||||
className="block py-2 pl-3 pr-4 hover:underline"
|
className="block py-2 pl-3 pr-4 hover:underline"
|
||||||
href="{% url 'list_sessions' %}"
|
to="/sessions"
|
||||||
>
|
>
|
||||||
All Sessions
|
All Sessions
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
{/* {% endif %} */}
|
{/* {% endif %} */}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
export default function SessionList() {
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/25/",
|
||||||
|
"timestamp_start": "2020-01-01T00:00:00+01:00",
|
||||||
|
"timestamp_end": null,
|
||||||
|
"duration_manual": "12:00:00",
|
||||||
|
"duration_calculated": "00:00:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/26/",
|
||||||
|
"timestamp_start": "2022-12-31T15:25:00+01:00",
|
||||||
|
"timestamp_end": "2022-12-31T17:25:00+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "02:00:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/2/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/27/",
|
||||||
|
"timestamp_start": "2023-01-01T23:00:00+01:00",
|
||||||
|
"timestamp_end": "2023-01-02T00:28:00+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "01:28:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/28/",
|
||||||
|
"timestamp_start": "2023-01-02T22:08:00+01:00",
|
||||||
|
"timestamp_end": "2023-01-03T01:08:00+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "03:00:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/29/",
|
||||||
|
"timestamp_start": "2023-01-03T22:36:00+01:00",
|
||||||
|
"timestamp_end": "2023-01-04T00:12:00+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "01:36:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/30/",
|
||||||
|
"timestamp_start": "2023-01-04T20:35:00+01:00",
|
||||||
|
"timestamp_end": "2023-01-04T22:36:00+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "02:01:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/31/",
|
||||||
|
"timestamp_start": "2023-01-06T18:48:00+01:00",
|
||||||
|
"timestamp_end": "2023-01-06T23:39:00+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "04:51:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/32/",
|
||||||
|
"timestamp_start": "2023-01-07T23:49:00+01:00",
|
||||||
|
"timestamp_end": "2023-01-08T01:43:00+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "01:54:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/33/",
|
||||||
|
"timestamp_start": "2023-01-08T16:21:00+01:00",
|
||||||
|
"timestamp_end": "2023-01-08T18:27:00+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "02:06:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/34/",
|
||||||
|
"timestamp_start": "2023-01-08T19:04:00+01:00",
|
||||||
|
"timestamp_end": "2023-01-08T22:03:00+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "02:59:00",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/35/",
|
||||||
|
"timestamp_start": "2023-01-09T19:35:48+01:00",
|
||||||
|
"timestamp_end": "2023-01-09T22:13:20.519058+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "02:37:32.519058",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/3/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/api/sessions/36/",
|
||||||
|
"timestamp_start": "2023-01-10T15:50:12+01:00",
|
||||||
|
"timestamp_end": "2023-01-10T17:03:45.424429+01:00",
|
||||||
|
"duration_manual": "00:00:00",
|
||||||
|
"duration_calculated": "01:13:33.424429",
|
||||||
|
"note": "",
|
||||||
|
"purchase": "http://localhost:8000/api/purchases/4/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const header = ["url", "timestamp_start", "timestamp_end", "duration_manual", "duration_calculated", "note", "purchase"]
|
||||||
|
// const header = ["Name", "Platform", "Start", "End", "Duration", "Manage"]
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div id="session-table" className="gap-4 shadow rounded-xl max-w-screen-lg mx-auto dark:bg-slate-700 p-2 justify-center">
|
||||||
|
{header.map(column => {
|
||||||
|
<div className="dark:border-white dark:text-slate-300 text-lg">{column}</div>
|
||||||
|
})}
|
||||||
|
{data.map(session => {
|
||||||
|
<>
|
||||||
|
<div className="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
<a className="hover:underline" href="">
|
||||||
|
{ session.url }
|
||||||
|
</a>
|
||||||
|
<div className="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
<a className="hover:underline" href="">
|
||||||
|
{ session.timestamp_start }
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
<a className="hover:underline" href="">
|
||||||
|
{ session.timestamp_end }
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
<a className="hover:underline" href="">
|
||||||
|
{ session.duration_manual }
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
<a className="hover:underline" href="">
|
||||||
|
{ session.duration_calculated }
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
<a className="hover:underline" href="">
|
||||||
|
{ session.note }
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
<a className="hover:underline" href="">
|
||||||
|
{ session.purchase }
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { useRouteError } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function ErrorPage() {
|
||||||
|
const error = useRouteError()
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container text-center">
|
||||||
|
<h1 className="text-3xl">Oops!</h1>
|
||||||
|
<p>Sorry, an unexpected error has occurred.</p>
|
||||||
|
<p>
|
||||||
|
<i>{error.statusText || error.message}</i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -2,9 +2,33 @@ import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
|
||||||
|
// import { loader as sessionLoader } from './routes/sessions'
|
||||||
|
import ErrorPage from "./error-page"
|
||||||
|
import SessionList from './components/SessionList'
|
||||||
|
// import Session from './routes/sessions'
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <App />,
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
// loader: sessionLoader,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "sessions/",
|
||||||
|
element: <SessionList />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// path: "sessions",
|
||||||
|
// element: <SessionList />
|
||||||
|
// }
|
||||||
|
])
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<RouterProvider router={router} />
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue