progress on player view
This commit is contained in:
parent
3fa9ff42b5
commit
8d829975a6
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Ionic App</title>
|
<title>Bullpen App</title>
|
||||||
|
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from '@ionic/vue-router';
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
import PlayerList from '../views/PlayerList.vue'
|
import PlayerList from '../views/PlayerList.vue'
|
||||||
|
import PlayerView from '../views/PlayerView.vue'
|
||||||
import LoginView from '../views/LoginView.vue'
|
import LoginView from '../views/LoginView.vue'
|
||||||
import HomeView from '../views/HomeView.vue'
|
import HomeView from '../views/HomeView.vue'
|
||||||
import BullpenView from "@/views/BullpenView.vue";
|
import BullpenView from "@/views/BullpenView.vue";
|
||||||
|
|
@ -17,7 +18,8 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
{ path: '/setup', component: SetupView },
|
{ path: '/setup', component: SetupView },
|
||||||
{ path: '/home', component: HomeView },
|
{ path: '/home', component: HomeView },
|
||||||
{ path: '/profile', component: ProfileView },
|
{ path: '/profile', component: ProfileView },
|
||||||
{ path: '/player', component: PlayerList },
|
{ path: '/players', component: PlayerList },
|
||||||
|
{ path: '/player', component: PlayerView },
|
||||||
{ path: '/bullpen', component: BullpenView },
|
{ path: '/bullpen', component: BullpenView },
|
||||||
{ path: '/stats', component: BullpenListView },
|
{ path: '/stats', component: BullpenListView },
|
||||||
{ path: '/summary', component: BullpenSummaryView }
|
{ path: '/summary', component: BullpenSummaryView }
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,15 @@ const pitchTypes: Module<PlayerState, RootState> = {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {player: null},
|
state: {player: null},
|
||||||
actions: {
|
actions: {
|
||||||
determinePlayer({commit}: PlayerActionContext, user: User) {
|
selectPlayer({commit}: PlayerActionContext, user: User) {
|
||||||
if (user.roles.includes('ROLE_PLAYER')) {
|
if (user.roles.includes('ROLE_PLAYER')) {
|
||||||
playerService.fetchByUserId(user.id).then(player => {
|
return playerService.fetchByUserId(user.id).then(player => {
|
||||||
commit('select', player);
|
commit('select', player);
|
||||||
|
|
||||||
|
return Promise.resolve(player);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const store = useStore();
|
||||||
const pitch = ref<Pitch>(BullpenSessionService.createPitch());
|
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 player = computed(() => store.state.player.player);
|
||||||
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
|
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);
|
const bullpen = computed(() => store.state.bullpen);
|
||||||
|
|
@ -82,7 +82,7 @@ const pitchAreaCssClasses = (area: number, name: string) => {
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-header :translucent="true">
|
<ion-header :translucent="true">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title v-if="isAuthenticated">Pitching {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
|
<ion-title v-if="isAuthenticated">Pitching {{ player.user.firstName }} {{ player.user.lastName }}</ion-title>
|
||||||
<ion-title v-else>Pitching</ion-title>
|
<ion-title v-else>Pitching</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ const store = useStore();
|
||||||
const userImage = ref(null);
|
const userImage = ref(null);
|
||||||
|
|
||||||
const user = computed(() => store.state.auth.user);
|
const user = computed(() => store.state.auth.user);
|
||||||
const player = computed(() => store.state.player.player);
|
|
||||||
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
|
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
|
||||||
|
|
||||||
if (user.value === undefined || user.value === null || user.value === '') {
|
if (user.value === undefined || user.value === null || user.value === '') {
|
||||||
|
|
@ -21,11 +20,13 @@ if (user.value === undefined || user.value === null || user.value === '') {
|
||||||
}
|
}
|
||||||
|
|
||||||
const startBullpen = () => {
|
const startBullpen = () => {
|
||||||
if (player.value === undefined || player.value === null || player.value === '') {
|
if (TokenService.isPlayer()) {
|
||||||
router.push({ path: '/player' });
|
store.dispatch('player/selectPlayer', user).then(() => {
|
||||||
} else {
|
store.dispatch("bullpen/start", user.value);
|
||||||
store.dispatch("bullpen/start", user.value);
|
router.push({ path: '/bullpen' });
|
||||||
router.push({ path: '/bullpen' });
|
});
|
||||||
|
} else if (TokenService.isCoach()) {
|
||||||
|
router.push({ path: '/players' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -48,7 +49,9 @@ const logout = () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const addPlayer = () => {}
|
const addPlayer = () => {
|
||||||
|
router.push({ path: '/player' });
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ const [password, passwordAttrs] = defineField('password');
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
const loginError = ref('');
|
||||||
|
|
||||||
const loggedIn = computed(() => store.state.auth.isAuthenticated);
|
const loggedIn = computed(() => store.state.auth.isAuthenticated);
|
||||||
|
|
||||||
|
|
@ -50,18 +51,19 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const submit = handleSubmit((values, { /*resetForm*/ }) => {
|
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((user: User) => {
|
}).then((user: User) => {
|
||||||
return store.dispatch('player/determinePlayer', user);
|
return store.dispatch('player/determinePlayer', user);
|
||||||
}, error => {
|
}).then(() => {
|
||||||
|
resetForm();
|
||||||
|
onLogin();
|
||||||
|
}).catch(error => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}).then(() => {
|
loginError.value = 'Invalid login data. Please try again.';
|
||||||
// resetForm();
|
|
||||||
onLogin();
|
|
||||||
});
|
});
|
||||||
}, ({errors}) => {
|
}, ({errors}) => {
|
||||||
console.log(errors);
|
console.log(errors);
|
||||||
|
|
@ -135,6 +137,7 @@ const changeServer = () => {
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-button expand="block" class="ion-margin-top" type="submit">Login</ion-button>
|
<ion-button expand="block" class="ion-margin-top" type="submit">Login</ion-button>
|
||||||
</form>
|
</form>
|
||||||
|
<p v-if="loginError" class="error-text">{{ loginError }}</p>
|
||||||
</ion-card-content>
|
</ion-card-content>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,297 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
IonPage,
|
||||||
|
IonHeader,
|
||||||
|
IonToolbar,
|
||||||
|
IonTitle,
|
||||||
|
IonContent,
|
||||||
|
IonItem,
|
||||||
|
IonInput,
|
||||||
|
IonButton,
|
||||||
|
IonCard,
|
||||||
|
IonCardHeader,
|
||||||
|
IonCardTitle,
|
||||||
|
IonCardContent,
|
||||||
|
IonSelect,
|
||||||
|
IonSelectOption
|
||||||
|
} from '@ionic/vue';
|
||||||
|
import { Field, useForm } from 'vee-validate';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import Player from '@/types/Player';
|
||||||
|
|
||||||
|
const props = defineProps<{ player?: Player }>();
|
||||||
|
|
||||||
|
// Todo: create default player
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Computed reactive player object: Either use the provided player or initialize with default
|
||||||
|
const player = ref<Player>(props.player ? { ...props.player } : { ...defaultPlayer });
|
||||||
|
|
||||||
|
const schema = yup.object({
|
||||||
|
email: yup
|
||||||
|
.string()
|
||||||
|
.email('Invalid email address')
|
||||||
|
.required('E-Mail is required'),
|
||||||
|
firstName: yup.string().required('First name is required'),
|
||||||
|
lastName: yup.string().required('Last name is required'),
|
||||||
|
dateOfBirth: yup
|
||||||
|
.date()
|
||||||
|
.max(dayjs().subtract(5, 'year').toDate(), 'Player must be at least 5 years old')
|
||||||
|
.required('Geburtsdatum ist erforderlich'),
|
||||||
|
gender: yup.string().required('Gender is required'),
|
||||||
|
bats: yup
|
||||||
|
.string()
|
||||||
|
.oneOf(['R', 'L', 'S'], 'Invalid selection')
|
||||||
|
.required('Batting hand is required'),
|
||||||
|
throws: yup
|
||||||
|
.string()
|
||||||
|
.oneOf(['R', 'L'], 'Invalid selection')
|
||||||
|
.required('Throwing hand is required'),
|
||||||
|
jerseyNumber: yup
|
||||||
|
.number()
|
||||||
|
.integer('Must be an integer')
|
||||||
|
.min(0, 'Must be zero or positive')
|
||||||
|
.max(99, 'Maximum is 99')
|
||||||
|
.required('Jersey number is required'),
|
||||||
|
weight: yup
|
||||||
|
.number()
|
||||||
|
.min(30, 'Minimum weight is 30kg')
|
||||||
|
.max(200, 'Maximum weight is 200kg')
|
||||||
|
.required('Weight is required'),
|
||||||
|
height: yup
|
||||||
|
.number()
|
||||||
|
.min(100, 'Minimum height is 100cm')
|
||||||
|
.max(250, 'Maximum height is 250cm')
|
||||||
|
.required('Height is required'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { errors, handleSubmit, defineField } = useForm({
|
||||||
|
validationSchema: schema,
|
||||||
|
initialValues: player.value
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for changes in props.player to reset form when editing
|
||||||
|
watch(
|
||||||
|
() => props.player,
|
||||||
|
(newPlayer) => {
|
||||||
|
if (newPlayer) {
|
||||||
|
resetForm({ values: { ...newPlayer } });
|
||||||
|
} else {
|
||||||
|
resetForm({ values: { ...defaultPlayer } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Submit logic: Differentiate between create and update
|
||||||
|
const submit = handleSubmit((values: Player) => {
|
||||||
|
if (player.value.id) {
|
||||||
|
console.log('Updating player:', values); // Replace with update logic
|
||||||
|
} else {
|
||||||
|
console.log('Creating player:', values); // Replace with create logic
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEdit = computed(() => !!player.value.id);
|
||||||
|
|
||||||
|
const [email, emailAttrs] = defineField('email');
|
||||||
|
const [firstName, firstNameAttrs] = defineField('firstName');
|
||||||
|
const [lastName, lastNameAttrs] = defineField('lastName');
|
||||||
|
const [dateOfBirth, dateOfBirthAttrs] = defineField('dateOfBirth');
|
||||||
|
const [gender, genderAttrs] = defineField('gender');
|
||||||
|
const [bats, batsAttrs] = defineField('bats');
|
||||||
|
const [throws, throwsAttrs] = defineField('throws');
|
||||||
|
const [jerseyNumber, jerseyNumberAttrs] = defineField('jerseyNumber');
|
||||||
|
const [weight, weightAttrs] = defineField('weight');
|
||||||
|
const [height, heightAttrs] = defineField('height');
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ion-page>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>{{ isEdit ? 'Update Player' : 'Create Player' }}</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<form @submit="submit">
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-header>
|
||||||
|
<ion-card-title>Personal Data</ion-card-title>
|
||||||
|
</ion-card-header>
|
||||||
|
<ion-card-content>
|
||||||
|
<ion-item>
|
||||||
|
<Field name="email">
|
||||||
|
<ion-input
|
||||||
|
label="E-Mail"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="email"
|
||||||
|
v-bind="emailAttrs"
|
||||||
|
type="email"
|
||||||
|
>
|
||||||
|
<small v-if="errors.email" class="error-message">{{ errors.email }}</small>
|
||||||
|
</ion-input>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<Field name="firstName">
|
||||||
|
<ion-input
|
||||||
|
label="First Name"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="firstName"
|
||||||
|
v-bind="firstNameAttrs"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<small v-if="errors.firstName" class="error-message">{{ errors.firstName }}</small>
|
||||||
|
</ion-input>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<Field name="lastName">
|
||||||
|
<ion-input
|
||||||
|
label="Last Name"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="lastName"
|
||||||
|
v-bind="lastNameAttrs"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<small v-if="errors.lastName" class="error-message">{{ errors.lastName }}</small>
|
||||||
|
</ion-input>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<Field name="dateOfBirth">
|
||||||
|
<ion-input
|
||||||
|
label="Date of Birth"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="dateOfBirth"
|
||||||
|
v-bind="dateOfBirthAttrs"
|
||||||
|
type="date"
|
||||||
|
>
|
||||||
|
<small v-if="errors.dateOfBirth" class="error-message">{{ errors.dateOfBirth }}</small>
|
||||||
|
</ion-input>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-header>
|
||||||
|
<ion-card-title>Player Data</ion-card-title>
|
||||||
|
</ion-card-header>
|
||||||
|
<ion-card-content>
|
||||||
|
<ion-item>
|
||||||
|
<Field name="gender">
|
||||||
|
<ion-select
|
||||||
|
label="Gender"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="gender"
|
||||||
|
v-bind="genderAttrs"
|
||||||
|
interface="action-sheet"
|
||||||
|
>
|
||||||
|
<ion-select-option value="male">Male</ion-select-option>
|
||||||
|
<ion-select-option value="female">Female</ion-select-option>
|
||||||
|
<ion-select-option value="other">Diverse</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<Field name="bats">
|
||||||
|
<ion-select
|
||||||
|
label="Bats"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="bats"
|
||||||
|
v-bind="batsAttrs"
|
||||||
|
interface="action-sheet"
|
||||||
|
>
|
||||||
|
<ion-select-option value="R">Right</ion-select-option>
|
||||||
|
<ion-select-option value="L">Left</ion-select-option>
|
||||||
|
<ion-select-option value="S">Switch</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<Field name="throws">
|
||||||
|
<ion-select
|
||||||
|
label="Throws"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="throws"
|
||||||
|
v-bind="throwsAttrs"
|
||||||
|
>
|
||||||
|
<ion-select-option value="R">Right</ion-select-option>
|
||||||
|
<ion-select-option value="L">Left</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<Field name="jerseyNumber">
|
||||||
|
<ion-input
|
||||||
|
label="Jersey Number"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="jerseyNumber"
|
||||||
|
v-bind="jerseyNumberAttrs"
|
||||||
|
type="number"
|
||||||
|
>
|
||||||
|
<small v-if="errors.jerseyNumber" class="error-message">{{ errors.jerseyNumber }}</small>
|
||||||
|
</ion-input>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<Field name="weight">
|
||||||
|
<ion-input
|
||||||
|
label="Weight (kg)"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="weight"
|
||||||
|
v-bind="weightAttrs"
|
||||||
|
type="number"
|
||||||
|
>
|
||||||
|
<small v-if="errors.weight" class="error-message">{{ errors.weight }}</small>
|
||||||
|
</ion-input>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<Field name="height">
|
||||||
|
<ion-input
|
||||||
|
label="Height (cm)"
|
||||||
|
label-placement="stacked"
|
||||||
|
v-model="height"
|
||||||
|
v-bind="heightAttrs"
|
||||||
|
type="number"
|
||||||
|
>
|
||||||
|
<small v-if="errors.height" class="error-message">{{ errors.height }}</small>
|
||||||
|
</ion-input>
|
||||||
|
</Field>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<div class="ion-padding-vertical">
|
||||||
|
<ion-button type="submit" expand="block">Save</ion-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ion-content>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error-message {
|
||||||
|
color: var(--ion-color-danger);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-card {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue