Compare commits

..

No commits in common. "1d96c9c54b067abb87f5a5d08e1b7e2b242fc697" and "c330e2312a96958de06198db4d5f56e312833190" have entirely different histories.

11 changed files with 111 additions and 288 deletions

View File

@ -1,31 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { import { IonApp, IonRouterOutlet } from '@ionic/vue';
IonApp,
IonMenu,
IonHeader,
IonToolbar,
IonContent,
IonIcon,
IonList,
IonTitle,
IonButton,
IonButtons,
IonLabel,
IonItem,
IonRouterOutlet,
IonAvatar
} from '@ionic/vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { AppStore } from '@/store'; import { AppStore } from '@/store';
import {onMounted, onBeforeUnmount, ref, computed} from "vue"; import { onMounted, onBeforeUnmount, ref } from "vue";
import EventBus from "./common/EventBus"; import EventBus from "./common/EventBus";
import backendService from '@/services/BackendService' import backendService from '@/services/BackendService'
import {
chevronBack,
personCircleOutline,
logOutOutline
} from 'ionicons/icons';
const router = useRouter(); const router = useRouter();
const store = useStore<AppStore>(); const store = useStore<AppStore>();
@ -36,8 +16,6 @@ const logout = () => {
} }
const isOnline = ref(navigator.onLine); const isOnline = ref(navigator.onLine);
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
const user = computed(() => store.state.auth.user);
onMounted(() => { onMounted(() => {
EventBus.on("logout", logout); EventBus.on("logout", logout);
@ -47,7 +25,7 @@ onMounted(() => {
window.addEventListener("online", () => isOnline.value = true); window.addEventListener("online", () => isOnline.value = true);
window.addEventListener("offline", () => isOnline.value = false); window.addEventListener("offline", () => isOnline.value = false);
if (isOnline.value && !isAuthenticated.value) { if (isOnline.value) {
router.push({path: '/login'}); router.push({path: '/login'});
} else { } else {
// checkStoredUserData(); // checkStoredUserData();
@ -57,96 +35,10 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
EventBus.remove("logout", logout); EventBus.remove("logout", logout);
}) })
const closeMenu = async () => {
try {
const elem = document.querySelector('ion-menu');
if (elem && await elem.isOpen()) {
await elem.close();
}
} catch (e) {
console.log(e);
}
}
const showProfile = () => {
router.push({path: '/profile/' + user.value.id});
closeMenu();
}
const logoutUser = () => {
store.dispatch('auth/logout').then(() => {
router.push({ path: '/' });
}, error => {
console.log(error);
});
closeMenu();
}
</script> </script>
<template> <template>
<ion-app> <ion-app>
<ion-menu content-id="user-menu">
<ion-header>
<ion-toolbar>
<ion-buttons slot="secondary">
<ion-button @click="closeMenu">
<ion-icon slot="icon-only" :icon="chevronBack"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>Menu Content</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="user-container">
<ion-avatar class="user-avatar">
<img alt="Silhouette of a person's head" src="https://ionicframework.com/docs/img/demos/avatar.svg" />
</ion-avatar>
<div v-if="isAuthenticated" class="user-name">{{ user.firstName }} {{ user.lastName }}</div>
</div>
<ion-list lines="full">
<ion-item button @click="showProfile">
<ion-icon slot="start" :icon="personCircleOutline"></ion-icon>
<ion-label>Profile</ion-label>
</ion-item>
</ion-list>
<ion-list lines="full">
<ion-item button @click="logoutUser">
<ion-icon slot="start" :icon="logOutOutline"></ion-icon>
<ion-label>Logout</ion-label>
</ion-item>
</ion-list>
</ion-content>
</ion-menu>
<ion-router-outlet /> <ion-router-outlet />
</ion-app> </ion-app>
</template> </template>
<style scoped>
.user-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
padding: 5px;
}
.user-avatar {
width: 120px;
height: 120px;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.user-name {
margin-top: 10px;
font-size: 18px;
font-weight: bold;
color: #333;
}
</style>

View File

@ -1,4 +1,4 @@
import PitchTypeService from '@/services/PitchTypeService' import pitchTypeService from '@/services/PitchTypeService'
import PitchType from "@/types/PitchType"; import PitchType from "@/types/PitchType";
import { Module } from 'vuex'; import { Module } from 'vuex';
@ -11,18 +11,9 @@ export interface PitchTypeState {
const pitchTypes: Module<PitchTypeState, RootState> = { const pitchTypes: Module<PitchTypeState, RootState> = {
namespaced: true, namespaced: true,
state: {pitchTypes: []}, state: {pitchTypes: []},
actions: {
fetch({commit}: any) {
PitchTypeService.fetchAll().then((pitchTypes: PitchType[]) => {
commit('initialize', pitchTypes);
}, (error) => {
console.log(error);
});
}
},
mutations: { mutations: {
initialize(state, pitchTypeList: PitchType[]) { initialize(state, pitchTypeList: PitchType[]) {
// pitchTypeService.updateLocalPitchTypes(pitchTypeList); pitchTypeService.updateLocalPitchTypes(pitchTypeList);
state.pitchTypes = pitchTypeList; state.pitchTypes = pitchTypeList;
} }
} }

View File

@ -25,7 +25,7 @@ import {
import PitchType from "@/types/PitchType"; import PitchType from "@/types/PitchType";
import {useStore} from 'vuex'; import {useStore} from 'vuex';
import {useRouter} from 'vue-router'; import {useRouter} from 'vue-router';
import {computed, onMounted} from "vue"; import {computed} from "vue";
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const router = useRouter(); const router = useRouter();
@ -36,15 +36,10 @@ const pitcher = computed(() => store.state.auth.user);
const bullpens = computed(() => store.state.bullpen.bullpens); const bullpens = computed(() => store.state.bullpen.bullpens);
const pitchTypes = computed(() => store.state.pitchTypes.pitchTypes); const pitchTypes = computed(() => store.state.pitchTypes.pitchTypes);
onMounted(() => {
store.dispatch('pitchTypes/fetch');
});
const formatDate = (date: Date) => { const formatDate = (date: Date) => {
return dayjs(date).format('YYYY.MM.DD HH:mm'); return dayjs(date).format('YYYY.MM.DD HH:mm');
} }
const determinePitchTypeName = (id: number): string => { const determinePitchTypeName = (id: number): string => {
const pitchType = pitchTypes.value.find((pitchType: PitchType) => pitchType.id === id); const pitchType = pitchTypes.value.find((pitchType: PitchType) => pitchType.id === id);
@ -92,4 +87,4 @@ const gotoHome = () => {
<style scoped> <style scoped>
</style> </style>

View File

@ -1,25 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, ref} from 'vue'; import {computed, ref} from 'vue';
import { import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonAvatar, IonButton, IonIcon } from '@ionic/vue';
IonPage,
IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonAvatar,
IonButton,
IonButtons,
IonIcon,
IonMenuButton,
IonFab,
IonFabButton
} from '@ionic/vue';
import { import {
playOutline, playOutline,
statsChartOutline, statsChartOutline,
personOutline, personOutline,
add, logOutOutline,
personAddOutline personAddOutline,
ellipsisHorizontal, ellipsisVertical
} from 'ionicons/icons'; } from 'ionicons/icons';
import {useStore} from "vuex"; import {useStore} from "vuex";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
@ -56,6 +44,18 @@ const showStats = () => {
}) })
}; };
const showProfile = () => {
router.push({ path: `/profile/${user.value.id}`});
};
const logout = () => {
store.dispatch('auth/logout').then(() => {
router.push({ path: '/' });
}, error => {
console.log(error);
});
}
const addPlayer = () => { const addPlayer = () => {
router.push({ path: '/player/create' }); router.push({ path: '/player/create' });
} }
@ -63,50 +63,62 @@ const addPlayer = () => {
</script> </script>
<template> <template>
<ion-page id="user-menu"> <ion-page>
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start"> <ion-title v-if="isAuthenticated">Home of {{ user.firstName }} {{ user.lastName }}</ion-title>
<ion-menu-button></ion-menu-button> <ion-title v-else>Home</ion-title>
</ion-buttons> <!-- <ion-buttons slot="primary">-->
<ion-title>Bullpens</ion-title> <!-- <ion-button id="user-menu-trigger">-->
<!-- <ion-title v-if="isAuthenticated">Home of {{ user.firstName }} {{ user.lastName }}</ion-title>--> <!-- <ion-icon slot="icon-only" :ios="ellipsisHorizontal" :md="ellipsisVertical"></ion-icon>-->
<!-- <ion-title v-else>Home</ion-title>--> <!-- </ion-button>-->
<!-- <ion-popover trigger="user-menu-trigger" trigger-action="click">-->
<!-- <ion-content class="ion-padding">-->
<!-- <ion-list>-->
<!-- <ion-item button @click="showProfile">Profile</ion-item>-->
<!-- <ion-item button @click="logout">Logout</ion-item>-->
<!-- </ion-list>-->
<!-- </ion-content>-->
<!-- </ion-popover>-->
<!-- </ion-buttons>-->
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content class="ion-padding"> <ion-content class="ion-padding">
<!-- <div class="user-container">--> <div class="user-container">
<!-- <ion-avatar class="user-avatar">--> <ion-avatar class="user-avatar">
<!-- <template v-if="userImage">--> <template v-if="userImage">
<!-- <img :src="userImage" alt="User Profile" class="avatar-frame" />--> <img :src="userImage" alt="User Profile" class="avatar-frame" />
<!-- </template>--> </template>
<!-- <template v-else>--> <template v-else>
<!-- <ion-icon :icon="personOutline" class="avatar-placeholder" />--> <ion-icon :icon="personOutline" class="avatar-placeholder" />
<!-- </template>--> </template>
<!-- </ion-avatar>--> </ion-avatar>
<!-- <div v-if="isAuthenticated" class="user-name">{{ user.firstName }} {{ user.lastName }}</div>--> <div v-if="isAuthenticated" class="user-name">{{ user.firstName }} {{ user.lastName }}</div>
<!-- </div>--> </div>
<!-- <div class="button-grid">--> <div class="button-grid">
<!-- <ion-button @click="startBullpen" class="grid-button">--> <ion-button @click="startBullpen" class="grid-button">
<!-- <ion-icon :icon="playOutline" slot="start" />--> <ion-icon :icon="playOutline" slot="start" />
<!-- Start Bullpen Session--> Start Bullpen Session
<!-- </ion-button>--> </ion-button>
<!-- <ion-button v-if="TokenService.isPlayer()" @click="showStats" class="grid-button">--> <ion-button v-if="TokenService.isPlayer()" @click="showStats" class="grid-button">
<!-- <ion-icon :icon="statsChartOutline" slot="start" />--> <ion-icon :icon="statsChartOutline" slot="start" />
<!-- Show Bullpen Stats--> Show Bullpen Stats
<!-- </ion-button>--> </ion-button>
<!-- <ion-button v-if="TokenService.isCoach()" @click="addPlayer" class="grid-button">--> <ion-button @click="showProfile" class="grid-button">
<!-- <ion-icon :icon="personAddOutline" slot="start" />--> <ion-icon :icon="personOutline" slot="start" />
<!-- Add Player--> Show Profile
<!-- </ion-button>--> </ion-button>
<!-- </div>--> <ion-button @click="logout" class="grid-button">
<ion-fab slot="fixed" vertical="bottom" horizontal="end"> <ion-icon :icon="logOutOutline" slot="start" />
<ion-fab-button> Logout
<ion-icon :icon="add"></ion-icon> </ion-button>
</ion-fab-button> <ion-button v-if="TokenService.isCoach()" @click="addPlayer" class="grid-button">
</ion-fab> <ion-icon :icon="personAddOutline" slot="start" />
Add Player
</ion-button>
</div>
</ion-content> </ion-content>
</ion-page> </ion-page>
</template> </template>
@ -125,6 +137,8 @@ const addPlayer = () => {
.user-avatar { .user-avatar {
width: 120px; width: 120px;
height: 120px; height: 120px;
border-radius: 50%;
border: 4px solid #ccc;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, computed } from "vue"; import { computed, ref, onMounted } from "vue";
import { import {
IonPage, IonPage,
IonButton, IonButton,
@ -16,6 +16,8 @@ import {Field, useForm} from 'vee-validate';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStore } from 'vuex' import { useStore } from 'vuex'
import * as yup from 'yup'; import * as yup from 'yup';
import PitchTypeService from "@/services/PitchTypeService";
import PitchType from "@/types/PitchType";
import User from "@/types/User"; import User from "@/types/User";
const loading = ref(false); const loading = ref(false);
@ -42,11 +44,11 @@ const router = useRouter();
const store = useStore(); const store = useStore();
const loginError = ref(''); const loginError = ref('');
const isAuthenticated = computed(() => store.state.auth.isAuthenticated); const loggedIn = computed(() => store.state.auth.isAuthenticated);
onMounted(() => { onMounted(() => {
if (isAuthenticated.value) { if (loggedIn.value) {
router.push({path: '/home'}); onLogin();
} }
}); });
@ -59,8 +61,7 @@ const submit = handleSubmit((values, { resetForm }) => {
return store.dispatch('player/selectPlayer', user); return store.dispatch('player/selectPlayer', user);
}).then(() => { }).then(() => {
resetForm(); resetForm();
router.push({path: '/home'}); onLogin();
// onLogin();
}).catch(error => { }).catch(error => {
loading.value = false; loading.value = false;
console.log(error); console.log(error);
@ -71,6 +72,18 @@ const submit = handleSubmit((values, { resetForm }) => {
loading.value = false; loading.value = false;
}); });
const onLogin = () => {
console.log('check if pitch types are available.');
PitchTypeService.fetchAll().then((pitchTypes: PitchType[]) => {
store.commit('pitchTypes/initialize', pitchTypes);
router.push({path: '/home'});
}, (error) => {
console.log(error);
});
}
// const changeServer = () => { // const changeServer = () => {
// router.push({ path: '/setup'}); // router.push({ path: '/setup'});
// } // }

View File

@ -9,19 +9,18 @@ import {
IonIcon, IonIcon,
IonPage, IonPage,
IonTitle, IonTitle,
IonToolbar, IonToolbar
IonButtons,
IonBackButton,
} from "@ionic/vue"; } from "@ionic/vue";
import {computed, onMounted, ref} from "vue"; import {computed, onMounted, ref} from "vue";
import {useStore} from "vuex"; import {useStore} from "vuex";
import {useRoute} from "vue-router"; import {useRoute, useRouter} from "vue-router";
import PlayerService from "@/services/PlayerService"; import PlayerService from "@/services/PlayerService";
// import dayjs from "dayjs"; // import dayjs from "dayjs";
const userImage = ref(null); const userImage = ref(null);
const router = useRouter();
const route = useRoute(); const route = useRoute();
const store = useStore(); const store = useStore();
@ -44,20 +43,22 @@ onMounted(() => {
}); });
}); });
const gotoHome = () => {
router.push({path: '/home'});
}
</script> </script>
<template> <template>
<ion-page> <ion-page>
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start"> <ion-title v-if="isAuthenticated">Home of {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
<ion-back-button></ion-back-button> <ion-title v-else>Home</ion-title>
</ion-buttons>
<ion-title>Profile</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content class="ion-padding">
<div class="user-container"> <div class="user-container">
<ion-avatar class="user-avatar"> <ion-avatar class="user-avatar">
<template v-if="userImage"> <template v-if="userImage">
@ -70,6 +71,10 @@ onMounted(() => {
<div v-if="isAuthenticated" class="user-name">{{ pitcher.firstName }} {{ pitcher.lastName }}</div> <div v-if="isAuthenticated" class="user-name">{{ pitcher.firstName }} {{ pitcher.lastName }}</div>
</div> </div>
</ion-content> </ion-content>
<ion-footer>
<ion-button expand="full" color="success" @click="gotoHome">Home</ion-button>
</ion-footer>
</ion-page> </ion-page>
</template> </template>
@ -113,4 +118,4 @@ onMounted(() => {
font-weight: bold; font-weight: bold;
color: #333; color: #333;
} }
</style> </style>

View File

@ -1,3 +1 @@
const BULLPEN_VERSION = "1.1.0"; export const BULLPEN_VERSION = "1.1.0";
module.exports = BULLPEN_VERSION;

View File

@ -1,5 +1,5 @@
const process = require('process'); const process = require('process');
const BULLPEN_VERSION = require("../config/version"); const {BULLPEN_VERSION} = require("../config/version");
exports.info = (req, res) => { exports.info = (req, res) => {
res.json({ res.json({

View File

@ -75,9 +75,7 @@ const createPlayer = async (queryInterface, roles, players) => {
}; };
})); }));
const createdPlayers = await queryInterface.select(null, 'Players');
return createdPlayers;
} }
module.exports = { module.exports = {

View File

@ -3,7 +3,7 @@
"version": "1.1.0", "version": "1.1.0",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"prebuild": "node -p \"'const BULLPEN_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\"\n\nmodule.exports = BULLPEN_VERSION; > config/version.js", "prebuild": "node -p \"'export const BULLPEN_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > config/version.js",
"test": "cross-env NODE_ENV=test jest", "test": "cross-env NODE_ENV=test jest",
"pretest": "cross-env NODE_ENV=test npm run db:reset", "pretest": "cross-env NODE_ENV=test npm run db:reset",
"db:create:test": "cross-env NODE_ENV=test npx sequelize-cli db:create", "db:create:test": "cross-env NODE_ENV=test npx sequelize-cli db:create",

View File

@ -1,83 +0,0 @@
const process = require('process');
const {createPlayer, createUsers} = require("../helper/seeder.helper");
const createBullpen = async (queryInterface, demoPlayer) => {
const startDate = new Date();
const endDate = new Date(new Date().setTime(startDate.getTime() + 10 * 60000));
const bullpens = [...Array(20).keys()].map(key => {
return {
pitcherId: demoPlayer.user.id,
startedAt: new Date(new Date().setDate(startDate.getDate()-5)),
finishedAt: new Date(new Date().setDate(startDate.getDate()-5)),
createdAt: new Date(),
updatedAt: new Date()
}
})
await queryInterface.bulkInsert('BullpenSessions', bullpens);
const pitchTypeIds = await queryInterface
.select(null, 'PitchTypes')
.map(pitchType => pitchType.id);
// red: 21-28
// orange: 11-18:
// green: 1-9
const bullpenChart = [
[21,22,23,24,25,26,27,28],
[11,12,13,14,15,16,17,18],
[1,2,3,4,5,6,7,8,9]
]
const dbBullpens = await queryInterface.select(null, 'BullpenSessions', {
where: {
pitcherId: demoPlayer.user.id
}
});
[...Array(20).keys()].map(key => {
return {
pitchTypeId: pitchTypeIds[Math.floor(Math.random() * pitchTypeIds.length)],
pitchTime: new Date(2025, 3, 22, 16, 7, 21),
aimedArea: 11,
hitArea: 12
}
});
}
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, /*Sequelize*/) {
if (process.env.NODE_ENV !== 'development') return;
const roles = await queryInterface.select(null, 'Roles');
const players = [{
email: 'demo@bullpen.com',
password: 'demo$123',
firstName: 'Demo',
lastName: 'Player',
dateOfBirth: new Date(1975, 6, 19),
jerseyNumber: 33,
height: 187,
weight: 85,
gender: 'male',
bats: 'right',
throws: 'right',
state: 'active'
}];
const dbPlayers = await createPlayer(queryInterface, roles, players);
const demoPlayer = dbPlayers
.filter(player => player.user.auth.email === 'demo@bullpen.com').shift();
for (let i = 0; i < 10; ++i) {
createBullpen(queryInterface, demoPlayer);
}
},
async down (queryInterface, /*Sequelize*/) {
if (process.env.NODE_ENV !== 'development') return;
return;
}
};