bullpen page progress

This commit is contained in:
Sascha Kühl 2025-04-07 22:41:44 +02:00
parent a350b02731
commit ea885ae56f
14 changed files with 161 additions and 70 deletions

View File

@ -10,7 +10,7 @@ import BullpenStats from "@/views/BullpenStats.vue";
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
{ {
path: '/', path: '/',
redirect: '/home' redirect: '/login'
}, { }, {
path: '/login', path: '/login',
name: 'Login', name: 'Login',

View File

@ -17,8 +17,10 @@ class AuthService {
}); });
} }
public logout() { public logout(): Promise<void> {
TokenService.removeUser(); TokenService.removeUser();
return Promise.resolve();
} }
public register(email: string, password: string) { public register(email: string, password: string) {

View File

@ -1,15 +1,17 @@
import BullpenPitch from "@/types/BullpenPitch"; import Pitch from "@/types/Pitch";
import User from "@/types/User"; import User from "@/types/User";
import PitchType from "@/types/PitchType"; import PitchType from "@/types/PitchType";
// import api from './Api';
export class BullpenSessionService { export class BullpenSessionService {
private static instance: BullpenSessionService;
private static nullPitcher: User = { private static nullPitcher: User = {
id: 0, id: 0,
firstName: "", firstName: "",
lastName: "", lastName: "",
email: "", gender: "male",
password: "", handedness: "right",
height: 0.0,
weight: 0.0,
dateOfBirth: new Date(0), dateOfBirth: new Date(0),
createdAt: new Date(0), createdAt: new Date(0),
updatedAt: new Date(0), updatedAt: new Date(0),
@ -20,18 +22,11 @@ export class BullpenSessionService {
abbreviation: "" abbreviation: ""
} }
private constructor() { public constructor() {
this.currentPitch = this.createPitch(); this.currentPitch = this.createPitch();
} }
// Static method to get the instance of the service public saveBullpen(): void {}
public static getInstance(): BullpenSessionService {
if (!BullpenSessionService.instance) {
BullpenSessionService.instance = new BullpenSessionService();
}
return BullpenSessionService.instance;
}
public startSession(pitcher: User): void { public startSession(pitcher: User): void {
this.pitcher = pitcher; this.pitcher = pitcher;
this.pitches = []; this.pitches = [];
@ -51,15 +46,15 @@ export class BullpenSessionService {
this.pitcher = BullpenSessionService.nullPitcher; this.pitcher = BullpenSessionService.nullPitcher;
} }
public addBullpenPitch( bullpenPitch: BullpenPitch ): void { public addBullpenPitch( bullpenPitch: Pitch ): void {
this.pitches.push( bullpenPitch ); this.pitches.push( bullpenPitch );
} }
public getBullpenPitches(): BullpenPitch[] { public getBullpenPitches(): Pitch[] {
return this.pitches; return this.pitches;
} }
public currentBullpenPitch(): BullpenPitch { public currentBullpenPitch(): Pitch {
return this.currentPitch; return this.currentPitch;
} }
@ -76,7 +71,7 @@ export class BullpenSessionService {
return this.pitcher.id === 0; return this.pitcher.id === 0;
} }
private createPitch(): BullpenPitch { private createPitch(): Pitch {
return { return {
id: 0, id: 0,
date: new Date(), date: new Date(),
@ -86,9 +81,9 @@ export class BullpenSessionService {
realPitchSubArea: 0 realPitchSubArea: 0
} }
} }
private pitches: BullpenPitch[] = []; private pitches: Pitch[] = [];
private pitcher: User = BullpenSessionService.nullPitcher; private pitcher: User = BullpenSessionService.nullPitcher;
private currentPitch: BullpenPitch; private currentPitch: Pitch;
} }
export const bullpenSessionService = BullpenSessionService.getInstance(); export default new BullpenSessionService();

View File

@ -26,7 +26,9 @@ const auth: Module<AuthState, RootState> = {
actions: { actions: {
login({ commit }: AuthActionContext, user) { login({ commit }: AuthActionContext, user) {
return AuthService.login(user.email, user.password).then( return AuthService.login(user.email, user.password).then(
user => { auth => {
return UserService.getUser(auth.id).then(
(user: User) => {
commit('loginSuccess', user); commit('loginSuccess', user);
return Promise.resolve(user); return Promise.resolve(user);
}, },
@ -35,10 +37,19 @@ const auth: Module<AuthState, RootState> = {
return Promise.reject(error); return Promise.reject(error);
} }
); );
},
error => {
commit('loginFailure');
return Promise.reject(error);
}
);
}, },
logout({ commit }: AuthActionContext) { logout({ commit }: AuthActionContext) {
AuthService.logout(); return AuthService.logout().then(() => {
commit('logout'); commit('logout');
return Promise.resolve();
})
}, },
register({ commit }: AuthActionContext, user) { register({ commit }: AuthActionContext, user) {
return AuthService.register(user.email, user.password).then( return AuthService.register(user.email, user.password).then(
@ -56,9 +67,7 @@ const auth: Module<AuthState, RootState> = {
mutations: { mutations: {
loginSuccess(state, user) { loginSuccess(state, user) {
state.isAuthenticated = true; state.isAuthenticated = true;
UserService.getUser(user.id).then((user: User) => {
state.user = user; state.user = user;
});
}, },
loginFailure(state) { loginFailure(state) {
state.isAuthenticated = false; state.isAuthenticated = false;

9
app/src/types/Bullpen.ts Normal file
View File

@ -0,0 +1,9 @@
import Pitch from "@/types/Pitch";
export default interface Bullpen {
id: number,
startedAt: Date,
finishedAt: Date,
pitcherId: number,
pitches: Pitch[]
}

View File

@ -1,6 +1,6 @@
import PitchType from "@/types/PitchType"; import PitchType from "@/types/PitchType";
export default interface BullpenPitch { export default interface Pitch {
id: number, id: number,
date: Date, date: Date,
pitchType: PitchType, pitchType: PitchType,

View File

@ -16,12 +16,12 @@ import {
IonTitle, IonTitle,
IonToolbar IonToolbar
} from '@ionic/vue'; } from '@ionic/vue';
import {ref} from 'vue'
import {useRouter} from 'vue-router'; import {useRouter} from 'vue-router';
import {BullpenSessionService, bullpenSessionService} from "@/services/BullpenSessionService"; import BullpenSessionService from "@/services/BullpenSessionService";
import {ref} from 'vue';
const router = useRouter(); const router = useRouter();
const bps = ref<BullpenSessionService>(bullpenSessionService); const bps = ref(BullpenSessionService);
const gotoHome = () => { const gotoHome = () => {
router.push('/home'); router.push('/home');

View File

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import {defineComponent, ref} from "vue";
import { import {
IonContent, IonContent,
IonHeader, IonHeader,
@ -13,20 +12,21 @@ import {
IonCardTitle, IonButton, IonCardTitle, IonButton,
IonGrid, IonRow, IonCol IonGrid, IonRow, IonCol
} from "@ionic/vue"; } from "@ionic/vue";
import {BullpenSessionService, bullpenSessionService} from "@/services/BullpenSessionService"; import BullpenSessionService from "@/services/BullpenSessionService";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import {ref} from 'vue';
const router = useRouter(); const router = useRouter();
const bps = ref<BullpenSessionService>(bullpenSessionService); const bps = ref(BullpenSessionService);
const finalizeAndNextPitch = () => { const finalizeAndNextPitch = () => {
bullpenSessionService.nextPitch(); bps.value.nextPitch();
router.push({ name: 'PreparePitch' }); router.push({ name: 'PreparePitch' });
} }
const finalizeAndEndBullpen = () => { const finalizeAndEndBullpen = () => {
bullpenSessionService.nextPitch(); bps.value.nextPitch();
bullpenSessionService.finishSession(); bps.value.finishSession();
router.push({ name: 'BullpenStats' }); router.push({ name: 'BullpenStats' });
} }

View File

@ -36,8 +36,11 @@ const showProfile = () => {
const logout = () => { const logout = () => {
console.log('Logout'); console.log('Logout');
store.dispatch('auth/logout'); store.dispatch('auth/logout').then(() => {
router.push({ path: '/login' }); router.push({ path: '/' });
}, error => {
console.log(error);
});
} }
</script> </script>
@ -62,20 +65,20 @@ const logout = () => {
<div class="user-name">{{ user.firstName }} {{ user.lastName }}</div> <div class="user-name">{{ user.firstName }} {{ user.lastName }}</div>
</div> </div>
<div class="button-container"> <div class="button-grid">
<ion-button expand="full" @click="startBullpen" class="full-width"> <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 expand="full" @click="showStats" class="full-width"> <ion-button @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 expand="full" @click="showProfile" class="full-width"> <ion-button @click="showProfile" class="grid-button">
<ion-icon :icon="personOutline" slot="start" /> <ion-icon :icon="personOutline" slot="start" />
Show Profile Show Profile
</ion-button> </ion-button>
<ion-button expand="full" @click="logout" class="full-width"> <ion-button @click="logout" class="grid-button">
<ion-icon :icon="logOutOutline" slot="start" /> <ion-icon :icon="logOutOutline" slot="start" />
Logout Logout
</ion-button> </ion-button>
@ -87,10 +90,12 @@ const logout = () => {
<style scoped> <style scoped>
.user-container { .user-container {
display: flex; display: flex;
flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 30vh; height: 30vh;
background-color: #f0f0f0; /* Change background color of top area */ background-color: #f0f0f0;
padding: 5px;
} }
.user-avatar { .user-avatar {
@ -99,6 +104,9 @@ const logout = () => {
border-radius: 50%; border-radius: 50%;
border: 4px solid #ccc; border: 4px solid #ccc;
overflow: hidden; overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
} }
.avatar-frame { .avatar-frame {
@ -108,19 +116,33 @@ const logout = () => {
border-radius: 50%; border-radius: 50%;
} }
.button-container { .avatar-placeholder {
display: flex; font-size: 60px;
flex-direction: column; color: #888;
gap: 10px;
height: 60vh;
justify-content: flex-start;
align-items: center;
width: 100%;
padding-top: 20px;
} }
.full-width { .user-name {
margin-top: 10px;
font-size: 18px;
font-weight: bold;
color: #333;
}
.button-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
padding-top: 10px;
width: 100%; width: 100%;
}
.grid-button {
height: 60px;
width: 100%;
display: flex;
justify-content: center; justify-content: center;
align-items: center;
text-align: center;
margin-inline: 0 !important;
} }
</style> </style>

View File

@ -20,7 +20,6 @@ import { lockClosedOutline, personOutline } from 'ionicons/icons';
import { useForm } from 'vee-validate'; import { useForm } from 'vee-validate';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { AppStore } from '@/store';
import * as yup from 'yup'; import * as yup from 'yup';
const loading = ref(false); const loading = ref(false);
@ -36,7 +35,7 @@ const [email, emailAttrs] = defineField('email');
const [password, passwordAttrs] = defineField('password'); const [password, passwordAttrs] = defineField('password');
const router = useRouter(); const router = useRouter();
const store = useStore<AppStore>(); const store = useStore();
const loggedIn = computed(() => store.state.auth.isAuthenticated); const loggedIn = computed(() => store.state.auth.isAuthenticated);
@ -46,12 +45,13 @@ onMounted(() => {
} }
}); });
const submit = handleSubmit(values => { const submit = handleSubmit((values, { resetForm }) => {
store.dispatch('auth/login', { store.dispatch('auth/login', {
email: values.email, email: values.email,
password: values.password, password: values.password,
}).then( }).then(
() => { () => {
resetForm();
onLogin(); onLogin();
}, },
error => { error => {
@ -64,6 +64,7 @@ const submit = handleSubmit(values => {
} }
); );
}, ({errors}) => { }, ({errors}) => {
console.log(errors);
loading.value = false; loading.value = false;
}); });

View File

@ -4,19 +4,20 @@ import { useRouter } from "vue-router";
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonList, IonItem, IonLabel } from '@ionic/vue'; import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonList, IonItem, IonLabel } from '@ionic/vue';
import UserService from "@/services/UserService"; import UserService from "@/services/UserService";
import User from "@/types/User"; import User from "@/types/User";
import {bullpenSessionService} from "@/services/BullpenSessionService"; import BullpenSessionService from "@/services/BullpenSessionService";
const pitchers = ref<User[]>([]); const pitchers = ref<User[]>([]);
const router = useRouter(); const router = useRouter();
const bps = ref(BullpenSessionService);
bullpenSessionService.clear(); bps.value.clear();
onMounted(async () => { onMounted(async () => {
pitchers.value = await UserService.getAllUsers(); pitchers.value = await UserService.getAllUsers();
}); });
const goToPreparePitch = (pitcher: User) => { const goToPreparePitch = (pitcher: User) => {
bullpenSessionService.startSession( pitcher ); bps.value.startSession( pitcher );
router.push({ name: 'PreparePitch' }); router.push({ name: 'PreparePitch' });
}; };

View File

@ -16,12 +16,12 @@ import {
IonCardTitle, IonCardTitle,
} from "@ionic/vue"; } from "@ionic/vue";
import PitchType from "@/types/PitchType"; import PitchType from "@/types/PitchType";
import {BullpenSessionService, bullpenSessionService} from "@/services/BullpenSessionService"; import BullpenSessionService from "@/services/BullpenSessionService";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
const router = useRouter(); const router = useRouter();
const pitchTypes = ref<PitchType[]>([]); const pitchTypes = ref<PitchType[]>([]);
const bps = ref<BullpenSessionService>(bullpenSessionService); const bps = ref(BullpenSessionService);
onMounted(async () => { onMounted(async () => {
pitchTypes.value = PitchTypeService.getLocalPitchTypes(); pitchTypes.value = PitchTypeService.getLocalPitchTypes();

View File

@ -17,7 +17,13 @@ exports.insert = (req, res) => {
}; };
exports.findAll = (req, res) => { exports.findAll = (req, res) => {
const { user } = req.query;
const filter = {};
if (user) {
filter.pitcherId = parseInt(user, 10);
}
BullpenSession.findAll({ BullpenSession.findAll({
where: filter,
include: { include: {
model: Pitch, model: Pitch,
as: 'pitches' as: 'pitches'

View File

@ -32,12 +32,58 @@ describe("Test bullpen session", () => {
expect(bullpenSessionData.pitches).toBeDefined(); expect(bullpenSessionData.pitches).toBeDefined();
expect(Array.isArray(bullpenSessionData.pitches)).toBe(true); expect(Array.isArray(bullpenSessionData.pitches)).toBe(true);
expect(bullpenSessionData.pitches.length).toBe(2); expect(bullpenSessionData.pitches.length).toBe(2);
});
test.skip("should get all bullpen sessions", async () => {
let response = await request(app)
.post("/api/auth/login")
.send({
email: 'player@example.com',
password: 'hash1234'
});
const user = response.body;
response = await request(app) response = await request(app)
.get(`/api/bullpen_session/${bullpenSessionData.id}`) .get("/api/bullpen_session")
.set('x-access-token', user.accessToken); .set('x-access-token', user.accessToken);
expect(response.statusCode).toBe(200);
// console.log(JSON.stringify(response.body, null, 2)); expect(response.statusCode).toBe(200);
const bullpenSessionDataArray = await response.body;
expect(bullpenSessionDataArray).toBeDefined();
expect(Array.isArray(bullpenSessionDataArray)).toBe(true);
expect(bullpenSessionDataArray.length).toBe(1);
const bullpenSessionData = bullpenSessionDataArray[0];
expect(bullpenSessionData.id).toBeDefined();
expect(bullpenSessionData.id).toBeGreaterThan(0);
expect(bullpenSessionData.pitches).toBeDefined();
expect(Array.isArray(bullpenSessionData.pitches)).toBe(true);
expect(bullpenSessionData.pitches.length).toBe(2);
})
test("should get all bullpen sessions for user", async () => {
let response = await request(app)
.post("/api/auth/login")
.send({
email: 'player@example.com',
password: 'hash1234'
}); });
const user = response.body;
response = await request(app)
.get("/api/bullpen_session")
.set('x-access-token', user.accessToken)
.query({ user: user.id });
expect(response.statusCode).toBe(200);
const bullpenSessionDataArray = await response.body;
expect(bullpenSessionDataArray).toBeDefined();
expect(Array.isArray(bullpenSessionDataArray)).toBe(true);
expect(bullpenSessionDataArray.length).toBe(1);
const bullpenSessionData = bullpenSessionDataArray[0];
expect(bullpenSessionData.id).toBeDefined();
expect(bullpenSessionData.id).toBeGreaterThan(0);
expect(bullpenSessionData.pitches).toBeDefined();
expect(Array.isArray(bullpenSessionData.pitches)).toBe(true);
expect(bullpenSessionData.pitches.length).toBe(2);
})
}) })