save bullpen to backend

This commit is contained in:
Sascha Kühl 2025-04-10 16:49:54 +02:00
parent da83b0b123
commit a18559a77a
9 changed files with 96 additions and 56 deletions

View File

@ -2,7 +2,7 @@ import Pitch from "@/types/Pitch";
import User from "@/types/User"; import User from "@/types/User";
import Bullpen from '@/types/Bullpen'; import Bullpen from '@/types/Bullpen';
import PitchTypeService from '@/services/PitchTypeService'; import PitchTypeService from '@/services/PitchTypeService';
// import api from './Api'; import api from '@/services/Api';
export class BullpenSessionService { export class BullpenSessionService {
public create(user: User): Bullpen { public create(user: User): Bullpen {
@ -15,16 +15,26 @@ export class BullpenSessionService {
} }
} }
public finish(): void {} public save(bullpen: Bullpen): Promise<Bullpen> {
console.log(JSON.stringify(bullpen, null, 2));
return api
.post('/bullpen_session', {
bullpen
})
.then(response => {
return response.data;
}, (error) => {
console.log(JSON.stringify(error, null, 2));
});
}
public createPitch(): Pitch { public createPitch(): Pitch {
return { return {
id: 0, id: 0,
date: new Date(), pitchTime: new Date(),
pitchType: PitchTypeService.getLocalPitchTypes()[0], pitchTypeId: PitchTypeService.getLocalPitchTypes()[0].id,
plannedPitchArea: 0, aimedArea: 0,
realPitchArea: 0, hitArea: 0
realPitchSubArea: 0
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { Module } from 'vuex'; import {ActionContext, Module} from 'vuex';
import { RootState } from './index'; import { RootState } from './index';
import Bullpen from '@/types/Bullpen'; import Bullpen from '@/types/Bullpen';
import User from '@/types/User'; import User from '@/types/User';
@ -9,12 +9,24 @@ export interface BullpenState {
bullpen: Bullpen | null; bullpen: Bullpen | null;
} }
type BullpenActionContext = ActionContext<BullpenState, RootState>;
const bullpen: Module<BullpenState, RootState> = { const bullpen: Module<BullpenState, RootState> = {
namespaced: true, namespaced: true,
state: { bullpen: null }, state: { bullpen: null },
actions: {
start({commit}: BullpenActionContext, user: User) {
const bullpen = BullpenSessionService.create(user);
commit('start', bullpen);
},
async finish({commit}: BullpenActionContext, bullpen: Bullpen) {
await BullpenSessionService.save(bullpen);
commit('finish');
}
},
mutations: { mutations: {
start(state, user: User) { start(state, bullpen: Bullpen) {
state.bullpen = BullpenSessionService.create(user); state.bullpen = bullpen;
}, },
addPitch(state, pitch: Pitch) { addPitch(state, pitch: Pitch) {
state.bullpen?.pitches.push({ ...pitch }); state.bullpen?.pitches.push({ ...pitch });

View File

@ -1,10 +1,7 @@
import PitchType from "@/types/PitchType";
export default interface Pitch { export default interface Pitch {
id: number, id: number,
date: Date, pitchTime: Date,
pitchType: PitchType, pitchTypeId: number,
plannedPitchArea: number, aimedArea: number,
realPitchArea: number, hitArea: number
realPitchSubArea: number
} }

View File

@ -17,11 +17,14 @@ import {
IonToolbar IonToolbar
} from '@ionic/vue'; } from '@ionic/vue';
import {useRouter} from 'vue-router'; import {useRouter} from 'vue-router';
import BullpenSessionService from "@/services/BullpenSessionService"; import {computed} from 'vue';
import {ref} from 'vue'; import {useStore} from 'vuex';
const router = useRouter(); const router = useRouter();
const bps = ref(BullpenSessionService); const store = useStore();
const pitcher = computed(() => store.state.auth.user);
const bullpen = computed(() => store.state.bullpen.bullpen);
const gotoHome = () => { const gotoHome = () => {
router.push('/home'); router.push('/home');
@ -32,11 +35,11 @@ const gotoHome = () => {
<ion-page> <ion-page>
<ion-header :translucent="true"> <ion-header :translucent="true">
<ion-toolbar> <ion-toolbar>
<ion-title>Bullpen Stats for {{ bps.getPitcher().firstName }} {{ bps.getPitcher().lastName }}</ion-title> <ion-title>Bullpen Stats for {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<ion-card v-for="(pitch, index) in bps.getBullpenPitches()" :key="index"> <ion-card v-for="(pitch, index) in bullpen.pitches" :key="index">
<ion-card-header> <ion-card-header>
<ion-card-title>Pitch {{index+1}}</ion-card-title> <ion-card-title>Pitch {{index+1}}</ion-card-title>
</ion-card-header> </ion-card-header>
@ -48,11 +51,11 @@ const gotoHome = () => {
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label>Planned Pitch Area</ion-label> <ion-label>Planned Pitch Area</ion-label>
<ion-badge>{{pitch.plannedPitchArea}}</ion-badge> <ion-badge>{{pitch.aimedArea}}</ion-badge>
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label>Real Pitch Area</ion-label> <ion-label>Real Pitch Area</ion-label>
<ion-badge>{{pitch.realPitchArea}}</ion-badge> <ion-badge>{{pitch.hitArea}}</ion-badge>
</ion-item> </ion-item>
</ion-list> </ion-list>
</ion-card-content> </ion-card-content>

View File

@ -12,10 +12,11 @@ const store = useStore();
const userImage = ref(null); const userImage = ref(null);
// const userImage = ref('../assets/groot.jpg'); // const userImage = ref('../assets/groot.jpg');
const user = computed(() => store.state.auth.user); const pitcher = computed(() => store.state.auth.user);
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
console.log('user', user.value); console.log('user', pitcher.value);
if (user.value === undefined || user.value === null || user.value === '') { if (pitcher.value === undefined || pitcher.value === null || pitcher.value === '') {
router.push({ path: '/login' }); router.push({ path: '/login' });
} }
@ -48,7 +49,8 @@ const logout = () => {
<ion-page> <ion-page>
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title>User Home</ion-title> <ion-title v-if="isAuthenticated">Home of {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
<ion-title v-else>Home</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@ -62,7 +64,7 @@ const logout = () => {
<ion-icon :icon="personOutline" class="avatar-placeholder" /> <ion-icon :icon="personOutline" class="avatar-placeholder" />
</template> </template>
</ion-avatar> </ion-avatar>
<div class="user-name">{{ user.firstName }} {{ user.lastName }}</div> <div v-if="isAuthenticated" class="user-name">{{ pitcher.firstName }} {{ pitcher.lastName }}</div>
</div> </div>
<div class="button-grid"> <div class="button-grid">

View File

@ -16,6 +16,9 @@ import {
IonInput, IonInput,
IonIcon, IonIcon,
} from "@ionic/vue"; } from "@ionic/vue";
// Todo: https://github.com/alanmontgomery/ionic-react-login
import { lockClosedOutline, personOutline } from 'ionicons/icons'; 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';

View File

@ -28,14 +28,16 @@ const pitch = ref<Pitch>(BullpenSessionService.createPitch());
const currentStep = ref<BullpenStep>(BullpenStep.Prepare); const currentStep = ref<BullpenStep>(BullpenStep.Prepare);
const pitcher = computed(() => store.state.auth.user); const pitcher = computed(() => store.state.auth.user);
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
const pitchTypes = computed(() => store.state.pitchTypes.pitchTypes); const pitchTypes = computed(() => store.state.pitchTypes.pitchTypes);
const bullpen = computed(() => store.state.bullpen);
onMounted(async () => { onMounted(async () => {
store.commit("bullpen/start", pitcher); await store.dispatch("bullpen/start", pitcher.value);
}); });
const setPitchType = (pitchType: PitchType) => { const setPitchType = (pitchType: PitchType) => {
pitch.value.pitchType = pitchType; pitch.value.pitchTypeId = pitchType.id;
} }
const gotoFinalizePitch = () => { const gotoFinalizePitch = () => {
@ -44,29 +46,35 @@ const gotoFinalizePitch = () => {
const finalizeAndNextPitch = () => { const finalizeAndNextPitch = () => {
store.commit("bullpen/addPitch", pitch.value); store.commit("bullpen/addPitch", pitch.value);
pitch.value = BullpenSessionService.createPitch();
currentStep.value = BullpenStep.Prepare; currentStep.value = BullpenStep.Prepare;
} }
const finalizeAndEndBullpen = () => { const finalizeAndEndBullpen = () => {
store.commit("bullpen/addPitch", pitch.value); store.commit("bullpen/addPitch", pitch.value);
pitch.value = BullpenSessionService.createPitch();
currentStep.value = BullpenStep.Prepare;
const bp = bullpen.value.bullpen;
bp.finishedAt = new Date();
store.dispatch("bullpen/finish", bp);
router.push({ name: 'BullpenStats' }); router.push({ name: 'BullpenStats' });
} }
const setPlannedPitchArea = (hitArea: number) => { const setPlannedPitchArea = (hitArea: number) => {
if (currentStep.value === BullpenStep.Prepare) { if (currentStep.value === BullpenStep.Prepare) {
pitch.value.plannedPitchArea = hitArea; pitch.value.aimedArea = hitArea;
} else { } else {
pitch.value.realPitchArea = hitArea; pitch.value.hitArea = hitArea;
} }
}; };
const pitchAreaCssClasses = (area: number, name: string) => { const pitchAreaCssClasses = (area: number, name: string) => {
const classes = []; const classes = [];
if (pitch.value.plannedPitchArea === area) { if (pitch.value.aimedArea === area) {
classes.push(name + '-aim-selected'); classes.push(name + '-aim-selected');
} }
if (pitch.value.realPitchArea === area) { if (pitch.value.hitArea === area) {
classes.push(name + '-hit-selected'); classes.push(name + '-hit-selected');
} }
@ -78,7 +86,8 @@ const pitchAreaCssClasses = (area: number, name: string) => {
<ion-page> <ion-page>
<ion-header :translucent="true"> <ion-header :translucent="true">
<ion-toolbar> <ion-toolbar>
<ion-title>Prepare Pitch for {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title> <ion-title v-if="isAuthenticated">Pitching {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
<ion-title v-else>Pitching</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
@ -87,7 +96,7 @@ const pitchAreaCssClasses = (area: number, name: string) => {
<ion-card-title>Select Pitch type</ion-card-title> <ion-card-title>Select Pitch type</ion-card-title>
</ion-card-header> </ion-card-header>
<ion-card-content> <ion-card-content>
<ion-button v-for="(pitchType, index) in pitchTypes" :key="index" :color="pitchType.id !== pitch.pitchType.id ? 'primary' : 'warning'" @click="setPitchType(pitchType)"> <ion-button v-for="(pitchType, index) in pitchTypes" :key="index" :color="pitchType.id !== pitch.pitchTypeId ? 'primary' : 'warning'" @click="setPitchType(pitchType)">
<ion-label>{{pitchType.abbreviation}}</ion-label> <ion-label>{{pitchType.abbreviation}}</ion-label>
</ion-button> </ion-button>
</ion-card-content> </ion-card-content>
@ -158,25 +167,31 @@ const pitchAreaCssClasses = (area: number, name: string) => {
</ion-card> </ion-card>
</ion-content> </ion-content>
<ion-footer> <ion-footer>
<ion-button color="warning" expand="full" size="large" :disabled="pitch.pitchType.id === 0 || pitch.plannedPitchArea === 0" @click="gotoFinalizePitch">Pitch</ion-button> <div v-if="currentStep === BullpenStep.Prepare">
<ion-grid>
<ion-row>
<ion-col>
<ion-button color="warning" expand="full" size="large" :disabled="pitch.pitchTypeId === 0 || pitch.aimedArea === 0" @click="gotoFinalizePitch">Pitch</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</div>
<div v-if="currentStep === BullpenStep.Finish">
<ion-grid>
<ion-row>
<ion-col>
<ion-button color="success" expand="full" @click="finalizeAndNextPitch" :disabled="pitch.hitArea === 0">Save Pitch<br/>&<br/>Next Pitch</ion-button>
</ion-col>
<ion-col>
<ion-button color="success" expand="full" @click="finalizeAndEndBullpen" :disabled="pitch.hitArea === 0">Save Pitch<br/>&<br/>End Session</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</div>
</ion-footer> </ion-footer>
</ion-page> </ion-page>
</template> </template>
<!--<ion-grid>-->
<!-- <div v-if="currentStep === BullpenStep.Prepare">-->
<!--<ion-row>-->
<!-- <ion-col><ion-button color="warning" expand="full" size="large" :disabled="pitch.pitchType.id === 0 || pitch.plannedPitchArea === 0" @click="gotoFinalizePitch">Pitch</ion-button></ion-col>-->
<!--</ion-row>-->
<!-- </div>-->
<!-- <div v-if="currentStep === BullpenStep.Finish">-->
<!--<ion-row>-->
<!-- <ion-col><ion-button color="success" expand="full" @click="finalizeAndNextPitch" :disabled="pitch.realPitchArea === 0">Save Pitch<br/>&<br/>Next Pitch</ion-button></ion-col>-->
<!-- <ion-col><ion-button color="success" expand="full" @click="finalizeAndEndBullpen" :disabled="pitch.realPitchArea === 0">Save Pitch<br/>&<br/>End Session</ion-button></ion-col>-->
<!--</ion-row>-->
<!-- </div>-->
<!--</ion-grid>-->
<style scoped> <style scoped>
.wasted { .wasted {
fill: firebrick; fill: firebrick;

View File

@ -108,8 +108,6 @@ exports.refreshToken = async (req, res) => {
try { try {
let refreshToken = await RefreshToken.findOne({ where: { token: requestToken } }); let refreshToken = await RefreshToken.findOne({ where: { token: requestToken } });
console.log(refreshToken)
if (!refreshToken) { if (!refreshToken) {
res.status(403).json({ message: "Refresh token is not in database!" }); res.status(403).json({ message: "Refresh token is not in database!" });
return; return;

View File

@ -3,7 +3,7 @@ const BullpenSession = db.BullpenSession;
const Pitch = db.Pitch; const Pitch = db.Pitch;
exports.insert = (req, res) => { exports.insert = (req, res) => {
BullpenSession.create(req.body, { include: [{ BullpenSession.create(req.body.bullpen, { include: [{
model: Pitch, model: Pitch,
as: 'pitches' as: 'pitches'
}]} }]}