Merge branch 'staging' of gitlab.com:PhilippHofer/rot into staging

This commit is contained in:
philipp 2023-10-01 15:53:50 +02:00
commit fb27bc1609
8 changed files with 130 additions and 33 deletions

2
.gitignore vendored
View File

@ -3,4 +3,4 @@ db.sqlite
.history/ .history/
Rocket.toml Rocket.toml
frontend/node_modules/* frontend/node_modules/*
static/ /static/

View File

@ -1,33 +1,85 @@
/*document.addEventListener('DOMContentLoaded', function() { import * as d3 from 'd3';
setDistance('.set-distance-js');
const margin = { top: 20, right: 20, bottom: 50, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
const parseTime = d3.timeParse('%Y-%m-%d');
data.forEach((d: any) => {
d.date = parseTime(d.date);
d.km = +d.km;
}); });
function setDistance(selector: string) { const x = d3.scaleTime()
const fields = document.querySelectorAll(selector); .domain(d3.extent(data, d => d.date))
if(fields) { .range([0, width]);
Array.prototype.forEach.call(fields, (field: HTMLInputElement) => {
if(field.dataset.relation){
const relatedField = <HTMLInputElement>document.getElementById(field.dataset.relation);
if(relatedField) { const y = d3.scaleLinear()
field.addEventListener('input', (e) => { .domain([0, d3.max(data, d => d.km)])
e.preventDefault(); .range([height, 0]);
const dataList = <HTMLDataListElement>document.getElementById('destinations'); const line = d3.line()
if(dataList) { .x(d => x(d.date))
var option = Array.prototype.find.call(dataList.options, function(option) { .y(d => y(d.km));
return option.value === field.value;
});
// Get distance const svg = d3.select('#container')
const distance = option.getAttribute('distance'); .append('svg')
if(distance) relatedField.value = distance; .attr('width', width + margin.left + margin.right)
} .attr('height', height + margin.top + margin.bottom)
}); .call(responsivefy)
} .append('g')
} .attr('transform', `translate(${margin.left},${margin.top})`);
});
svg.append('path')
.data([data])
.attr('class', 'line')
.attr('d', line);
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
svg.append('g')
.call(d3.axisLeft(y));
function responsivefy(svg: any) {
// container will be the DOM element
// that the svg is appended to
// we then measure the container
// and find its aspect ratio
const container = d3.select(svg.node().parentNode),
width = parseInt(svg.style('width'), 10),
height = parseInt(svg.style('height'), 10),
aspect = width / height;
// set viewBox attribute to the initial size
// control scaling with preserveAspectRatio
// resize svg on inital page load
svg.attr('viewBox', `0 0 ${width} ${height}`)
.attr('preserveAspectRatio', 'xMinYMid')
.call(resize);
// add a listener so the chart will be resized
// when the window resizes
// multiple listeners for the same event type
// requires a namespace, i.e., 'click.foo'
// api docs: https://goo.gl/F3ZCFr
d3.select(window).on(
'resize.' + container.attr('id'),
resize
);
// this is the code that resizes the chart
// it will be called on load
// and in response to window resizes
// gets the width of the container
// and resizes the svg to fill it
// while maintaining a consistent aspect ratio
function resize() {
const w = parseInt(container.style('width'));
svg.attr('width', w);
svg.attr('height', Math.round(w / aspect));
} }
} }
*/
export {}

View File

@ -9,15 +9,17 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"@types/d3": "^7.4.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"sass": "^1.60.0", "sass": "^1.60.0",
"tailwindcss": "^3.3.1", "tailwindcss": "^3.3.1",
"typescript": "^4.9.3", "typescript": "^4.9.5",
"vite": "^4.2.0", "vite": "^4.2.0",
"vite-plugin-static-copy": "^0.13.1" "vite-plugin-static-copy": "^0.13.1"
}, },
"dependencies": { "dependencies": {
"choices.js": "^10.2.0" "choices.js": "^10.2.0",
"d3": "^7.8.5"
} }
} }

View File

@ -9,3 +9,4 @@
@import 'components/input'; @import 'components/input';
@import 'components/alert'; @import 'components/alert';
@import 'components/status'; @import 'components/status';
@import 'components/chart';

View File

@ -0,0 +1,3 @@
.line {
@apply fill-none stroke-2 stroke-primary-600;
}

View File

@ -1,3 +1,4 @@
use crate::model::user::User;
use serde::Serialize; use serde::Serialize;
use sqlx::{FromRow, Row, SqlitePool}; use sqlx::{FromRow, Row, SqlitePool};
@ -41,3 +42,26 @@ ORDER BY rowed_km DESC;
.collect() .collect()
} }
} }
#[derive(Debug, Serialize)]
pub struct PersonalStat {
date: String,
km: i32,
}
pub async fn get_personal(db: &SqlitePool, user: &User) -> Vec<PersonalStat> {
vec![
PersonalStat {
date: String::from("2023-01-01"),
km: 5,
},
PersonalStat {
date: String::from("2023-02-01"),
km: 24,
},
PersonalStat {
date: String::from("2023-08-30"),
km: 1340,
},
]
}

View File

@ -2,13 +2,17 @@ use rocket::{get, routes, Route, State};
use rocket_dyn_templates::{context, Template}; use rocket_dyn_templates::{context, Template};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::{stat::Stat, user::User}; use crate::model::{
stat::{self, Stat},
user::User,
};
#[get("/")] #[get("/")]
async fn index(db: &State<SqlitePool>, user: User) -> Template { async fn index(db: &State<SqlitePool>, user: User) -> Template {
let stat = Stat::get_rowed_km(db).await; let stat = Stat::get_rowed_km(db).await;
let personal = stat::get_personal(db, &user).await;
Template::render("stat", context!(loggedin_user: &user, stat)) Template::render("stat", context!(loggedin_user: &user, stat, personal))
} }
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {

View File

@ -34,5 +34,16 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div id="container" class="w-full"></div>
</div> </div>
<script>
const data = [
{%- for p in personal %}
{ date: '{{p.date}}', km: {{p.km}} },
{%- endfor %}
]
</script>
<script src="/public/logbook.js"></script>
{% endblock content%} {% endblock content%}