forked from Ruderverein-Donau-Linz/rowt
		
	Merge branch 'staging' of gitlab.com:PhilippHofer/rot into staging
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -3,4 +3,4 @@ db.sqlite
 | 
			
		||||
.history/
 | 
			
		||||
Rocket.toml
 | 
			
		||||
frontend/node_modules/*
 | 
			
		||||
static/
 | 
			
		||||
/static/
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,85 @@
 | 
			
		||||
/*document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
  setDistance('.set-distance-js');
 | 
			
		||||
import * as d3 from 'd3';
 | 
			
		||||
 | 
			
		||||
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 fields = document.querySelectorAll(selector);
 | 
			
		||||
  if(fields) {
 | 
			
		||||
    Array.prototype.forEach.call(fields, (field: HTMLInputElement) => {
 | 
			
		||||
      if(field.dataset.relation){
 | 
			
		||||
        const relatedField = <HTMLInputElement>document.getElementById(field.dataset.relation);
 | 
			
		||||
const x = d3.scaleTime()
 | 
			
		||||
.domain(d3.extent(data, d => d.date))
 | 
			
		||||
.range([0, width]);
 | 
			
		||||
 | 
			
		||||
        if(relatedField) {
 | 
			
		||||
          field.addEventListener('input', (e) => {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            
 | 
			
		||||
            const dataList = <HTMLDataListElement>document.getElementById('destinations');
 | 
			
		||||
              if(dataList) {
 | 
			
		||||
                var option = Array.prototype.find.call(dataList.options, function(option) {
 | 
			
		||||
                  return option.value === field.value;
 | 
			
		||||
                });
 | 
			
		||||
    
 | 
			
		||||
                // Get distance
 | 
			
		||||
                const distance = option.getAttribute('distance');
 | 
			
		||||
                if(distance) relatedField.value = distance;
 | 
			
		||||
              }
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
const y = d3.scaleLinear()
 | 
			
		||||
.domain([0, d3.max(data, d => d.km)])
 | 
			
		||||
.range([height, 0]);
 | 
			
		||||
 | 
			
		||||
const line = d3.line()
 | 
			
		||||
.x(d => x(d.date))
 | 
			
		||||
.y(d => y(d.km));
 | 
			
		||||
 | 
			
		||||
const svg = d3.select('#container')
 | 
			
		||||
.append('svg')
 | 
			
		||||
  .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 {}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,17 @@
 | 
			
		||||
    "preview": "vite preview"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/d3": "^7.4.1",
 | 
			
		||||
    "autoprefixer": "^10.4.14",
 | 
			
		||||
    "postcss": "^8.4.21",
 | 
			
		||||
    "sass": "^1.60.0",
 | 
			
		||||
    "tailwindcss": "^3.3.1",
 | 
			
		||||
    "typescript": "^4.9.3",
 | 
			
		||||
    "typescript": "^4.9.5",
 | 
			
		||||
    "vite": "^4.2.0",
 | 
			
		||||
    "vite-plugin-static-copy": "^0.13.1"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "choices.js": "^10.2.0"
 | 
			
		||||
    "choices.js": "^10.2.0",
 | 
			
		||||
    "d3": "^7.8.5"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,3 +9,4 @@
 | 
			
		||||
@import 'components/input';
 | 
			
		||||
@import 'components/alert';
 | 
			
		||||
@import 'components/status';
 | 
			
		||||
@import 'components/chart';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								frontend/scss/components/_chart.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								frontend/scss/components/_chart.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
.line {
 | 
			
		||||
  @apply fill-none stroke-2 stroke-primary-600;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
use crate::model::user::User;
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use sqlx::{FromRow, Row, SqlitePool};
 | 
			
		||||
 | 
			
		||||
@@ -41,3 +42,26 @@ ORDER BY rowed_km DESC;
 | 
			
		||||
        .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,
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,17 @@ use rocket::{get, routes, Route, State};
 | 
			
		||||
use rocket_dyn_templates::{context, Template};
 | 
			
		||||
use sqlx::SqlitePool;
 | 
			
		||||
 | 
			
		||||
use crate::model::{stat::Stat, user::User};
 | 
			
		||||
use crate::model::{
 | 
			
		||||
    stat::{self, Stat},
 | 
			
		||||
    user::User,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[get("/")]
 | 
			
		||||
async fn index(db: &State<SqlitePool>, user: User) -> Template {
 | 
			
		||||
    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> {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,5 +34,16 @@
 | 
			
		||||
        </div>
 | 
			
		||||
			{% endfor %}
 | 
			
		||||
		</div>
 | 
			
		||||
    <div id="container" class="w-full"></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%}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user