progress on player view
This commit is contained in:
parent
3fa9ff42b5
commit
8d829975a6
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Ionic App</title>
|
||||
<title>Bullpen App</title>
|
||||
|
||||
<base href="/" />
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from '@ionic/vue-router';
|
|||
import { useStore } from 'vuex'
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import PlayerList from '../views/PlayerList.vue'
|
||||
import PlayerView from '../views/PlayerView.vue'
|
||||
import LoginView from '../views/LoginView.vue'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
import BullpenView from "@/views/BullpenView.vue";
|
||||
|
|
@ -17,7 +18,8 @@ const routes: Array<RouteRecordRaw> = [
|
|||
{ path: '/setup', component: SetupView },
|
||||
{ path: '/home', component: HomeView },
|
||||
{ path: '/profile', component: ProfileView },
|
||||
{ path: '/player', component: PlayerList },
|
||||
{ path: '/players', component: PlayerList },
|
||||
{ path: '/player', component: PlayerView },
|
||||
{ path: '/bullpen', component: BullpenView },
|
||||
{ path: '/stats', component: BullpenListView },
|
||||
{ path: '/summary', component: BullpenSummaryView }
|
||||
|
|
|
|||
|
|
@ -15,12 +15,15 @@ const pitchTypes: Module<PlayerState, RootState> = {
|
|||
namespaced: true,
|
||||
state: {player: null},
|
||||
actions: {
|
||||
determinePlayer({commit}: PlayerActionContext, user: User) {
|
||||
selectPlayer({commit}: PlayerActionContext, user: User) {
|
||||
if (user.roles.includes('ROLE_PLAYER')) {
|
||||
playerService.fetchByUserId(user.id).then(player => {
|
||||
return playerService.fetchByUserId(user.id).then(player => {
|
||||
commit('select', player);
|
||||
|
||||
return Promise.resolve(player);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const store = useStore();
|
|||
const pitch = ref<Pitch>(BullpenSessionService.createPitch());
|
||||
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 pitchTypes = computed(() => store.state.pitchTypes.pitchTypes);
|
||||
const bullpen = computed(() => store.state.bullpen);
|
||||
|
|
@ -82,7 +82,7 @@ const pitchAreaCssClasses = (area: number, name: string) => {
|
|||
<ion-page>
|
||||
<ion-header :translucent="true">
|
||||
<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-toolbar>
|
||||
</ion-header>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ const store = useStore();
|
|||
const userImage = ref(null);
|
||||
|
||||
const user = computed(() => store.state.auth.user);
|
||||
const player = computed(() => store.state.player.player);
|
||||
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
|
||||
|
||||
if (user.value === undefined || user.value === null || user.value === '') {
|
||||
|
|
@ -21,11 +20,13 @@ if (user.value === undefined || user.value === null || user.value === '') {
|
|||
}
|
||||
|
||||
const startBullpen = () => {
|
||||
if (player.value === undefined || player.value === null || player.value === '') {
|
||||
router.push({ path: '/player' });
|
||||
} else {
|
||||
if (TokenService.isPlayer()) {
|
||||
store.dispatch('player/selectPlayer', user).then(() => {
|
||||
store.dispatch("bullpen/start", user.value);
|
||||
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>
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const [password, passwordAttrs] = defineField('password');
|
|||
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
const loginError = ref('');
|
||||
|
||||
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', {
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
}).then((user: User) => {
|
||||
return store.dispatch('player/determinePlayer', user);
|
||||
}, error => {
|
||||
}).then(() => {
|
||||
resetForm();
|
||||
onLogin();
|
||||
}).catch(error => {
|
||||
loading.value = false;
|
||||
console.log(error);
|
||||
}).then(() => {
|
||||
// resetForm();
|
||||
onLogin();
|
||||
loginError.value = 'Invalid login data. Please try again.';
|
||||
});
|
||||
}, ({errors}) => {
|
||||
console.log(errors);
|
||||
|
|
@ -135,6 +137,7 @@ const changeServer = () => {
|
|||
</ion-item>
|
||||
<ion-button expand="block" class="ion-margin-top" type="submit">Login</ion-button>
|
||||
</form>
|
||||
<p v-if="loginError" class="error-text">{{ loginError }}</p>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</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