Compare commits

..

No commits in common. "98cd757666b339499c44b8c4170af61b27836833" and "ccc3ffe7713cd9bc83614f665ffc5d44cd6b81f9" have entirely different histories.

11 changed files with 265 additions and 388 deletions

View File

@ -19,7 +19,7 @@ const routes: Array<RouteRecordRaw> = [
{ path: '/home', component: HomeView },
{ path: '/profile', component: ProfileView },
{ path: '/players', component: PlayerList },
{ path: '/profile/:id', component: ProfileView },
// { path: '/player', component: PlayerView },
{ path: '/bullpen', component: BullpenView },
{ path: '/stats', component: BullpenListView },
{ path: '/summary', component: BullpenSummaryView },

View File

@ -9,33 +9,6 @@ class PlayerService extends ApiService<Player> {
super('players');
}
public emptyPlayer(): Player {
return {
id: 0,
gender: 'male',
bats: 'right',
throws: 'right',
height: 187,
weight: 84,
state: 'active',
jerseyNumber: 23,
createdAt: new Date(),
updatedAt: new Date(),
user: {
id: 0,
auth: {
email: 'test@de.de',
password: 'test$123'
},
firstName: 'Demo',
lastName: 'Player',
dateOfBirth: new Date(),
roles: ['player'],
createdAt: new Date(),
updatedAt: new Date()
}
};
}
public fetchByUserId(userId: number): Promise<Player> {
return api
.get(`/${this.name}/user/${userId}`, { headers: authHeader() })

View File

@ -1,13 +1,11 @@
export default interface User {
id: number,
email: string,
firstName: string,
lastName: string,
dateOfBirth: Date,
roles: string[],
createdAt: Date,
updatedAt: Date,
auth: {
email: string,
password: string
}
auth: { email: string }
}

View File

@ -2,21 +2,14 @@
import {
IonButton,
IonCard,
IonCardHeader,
IonCardTitle,
IonCardSubtitle,
IonCardContent,
IonContent,
IonFooter,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonLabel,
IonList,
IonItem,
IonIcon,
IonBadge
IonToolbar, IonLabel, IonList, IonItem, IonIcon, IonBadge
} from "@ionic/vue";
import {
baseballOutline

View File

@ -26,7 +26,7 @@ const router = useRouter();
const store = useStore();
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
const player = computed(() => store.state.player.player);
const pitcher = computed(() => store.state.auth.user);
const pitchTypes = computed(() => store.state.pitchTypes.pitchTypes);
const bullpen = computed(() => store.state.bullpen);
@ -52,7 +52,7 @@ const gotoHome = () => {
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-title v-if="isAuthenticated">Bullpen Summary {{ player.firstName }} {{ player.lastName }}</ion-title>
<ion-title v-if="isAuthenticated">Bullpen Summary {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
<ion-title v-else>Bullpen Summary</ion-title>
</ion-toolbar>
</ion-header>

View File

@ -1,14 +1,7 @@
<script setup lang="ts">
import {computed, ref} from 'vue';
import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonAvatar, IonButton, IonIcon } from '@ionic/vue';
import {
playOutline,
statsChartOutline,
personOutline,
logOutOutline,
personAddOutline,
ellipsisHorizontal, ellipsisVertical
} from 'ionicons/icons';
import { playOutline, statsChartOutline, personOutline, logOutOutline, personAddOutline } from 'ionicons/icons';
import {useStore} from "vuex";
import {useRouter} from "vue-router";
import BullpenSessionService from "@/services/BullpenSessionService";
@ -28,7 +21,7 @@ if (user.value === undefined || user.value === null || user.value === '') {
const startBullpen = () => {
if (TokenService.isPlayer()) {
store.dispatch('player/selectPlayer', user.value).then(() => {
store.dispatch('player/selectPlayer', user).then(() => {
store.dispatch("bullpen/start", user.value);
router.push({ path: '/bullpen' });
});
@ -45,7 +38,7 @@ const showStats = () => {
};
const showProfile = () => {
router.push({ path: `/profile/${user.value.id}`});
router.push({ path: `/player/view/${user.value.id}`});
};
const logout = () => {
@ -68,19 +61,6 @@ const addPlayer = () => {
<ion-toolbar>
<ion-title v-if="isAuthenticated">Home of {{ user.firstName }} {{ user.lastName }}</ion-title>
<ion-title v-else>Home</ion-title>
<!-- <ion-buttons slot="primary">-->
<!-- <ion-button id="user-menu-trigger">-->
<!-- <ion-icon slot="icon-only" :ios="ellipsisHorizontal" :md="ellipsisVertical"></ion-icon>-->
<!-- </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-header>
@ -102,7 +82,7 @@ const addPlayer = () => {
<ion-icon :icon="playOutline" slot="start" />
Start Bullpen Session
</ion-button>
<ion-button v-if="TokenService.isPlayer()" @click="showStats" class="grid-button">
<ion-button @click="showStats" class="grid-button">
<ion-icon :icon="statsChartOutline" slot="start" />
Show Bullpen Stats
</ion-button>

View File

@ -9,7 +9,6 @@ import {
IonCardHeader,
IonCardTitle,
IonCardContent,
IonInputPasswordToggle,
} from "@ionic/vue";
import {Field, useForm} from 'vee-validate';
@ -133,7 +132,6 @@ const changeServer = () => {
id="password"
name="password"
type="password">
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
</ion-input>
</Field>
</ion-item>

View File

@ -2,8 +2,7 @@
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useStore } from 'vuex'
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonList, IonItem, IonLabel, IonIcon, IonText } from '@ionic/vue';
import {personCircle} from "ionicons/icons";
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonList, IonItem, IonLabel } from '@ionic/vue';
import PlayerService from "@/services/PlayerService";
import Player from "@/types/Player";
@ -28,24 +27,20 @@ const selectPlayer = (player: Player) => {
<ion-page>
<ion-header :translucent="true">
<ion-toolbar>
<ion-title>Players</ion-title>
<ion-title>Pitchers</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Player List</ion-title>
<ion-title size="large">Pitchers</ion-title>
</ion-toolbar>
</ion-header>
<ion-list>
<ion-item v-for="(player, index) in players" :key="index" button @click="selectPlayer(player)">
<ion-icon aria-hidden="true" :icon="personCircle" slot="start"></ion-icon>
<ion-label>
<strong>{{ player.user.firstName }} {{ player.user.lastName }} <ion-badge slot="start">{{ player.jerseyNumber}}</ion-badge></strong>
<ion-text>Bats: {{player.bats}} / Throws: {{player.throws}}</ion-text>
</ion-label>
<ion-label>{{ player.user.firstName }} {{ player.user.lastName }}</ion-label>
</ion-item>
</ion-list>
</ion-content>
@ -53,16 +48,6 @@ const selectPlayer = (player: Player) => {
</template>
<style scoped>
ion-label strong {
display: block;
max-width: calc(100% - 60px);
overflow: hidden;
text-overflow: ellipsis;
}
#container {
text-align: center;

View File

@ -5,16 +5,15 @@ import {
IonToolbar,
IonTitle,
IonContent,
IonGrid,
IonRow,
IonCol,
IonItem,
IonInput,
IonIcon,
IonButton,
IonCard,
IonCardHeader,
IonCardTitle,
IonCardContent,
IonSelect,
IonSelectOption,
IonInputPasswordToggle,
IonSelectOption
} from '@ionic/vue';
import { Field, useForm } from 'vee-validate';
import * as yup from 'yup';
@ -24,10 +23,37 @@ import Player from '@/types/Player';
// import CrudField from '@/components/CrudField.vue'
import PlayerService from '@/services/PlayerService';
import {useRoute, useRouter} from 'vue-router';
import {save, stop} from "ionicons/icons";
const props = defineProps<{ player?: Player }>();
const defaultPlayer: Player = {
id: 0,
gender: 'male',
bats: 'right',
throws: 'right',
height: 0,
weight: 0,
state: 'active',
jerseyNumber: 0,
createdAt: new Date(),
updatedAt: new Date(),
user: {
id: 0,
email: '',
firstName: '',
lastName: '',
dateOfBirth: new Date(),
roles: [],
createdAt: new Date(),
updatedAt: new Date(),
auth: {
email: ''
}
}
}
// Computed reactive player object: Either use the provided player or initialize with default
const player = ref<Player>( PlayerService.emptyPlayer() );
const player = ref<Player>(props.player ? { ...props.player } : { ...defaultPlayer });
const route = useRoute();
const router = useRouter();
@ -42,32 +68,24 @@ const mode = computed(() => {
const formattedDate = ref(player.value.user.dateOfBirth.toISOString().split('T')[0]);
const schema = yup.object({
user: yup.object({
auth: yup.object({
email: yup.string().email('Invalid email address').required('E-Mail is required'),
password: yup.string()
.min(8, 'Minimum 8 characters')
.matches(/\d/, 'Must include a number')
.matches(/[^A-Za-z\d]/, 'Must include a special character')
.required('Password is required')
}),
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('Date of birth is required'),
}),
.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(['right', 'left', 'both'], 'Invalid selection')
.oneOf(['R', 'L', 'S'], 'Invalid selection')
.required('Batting hand is required'),
throws: yup
.string()
.oneOf(['right', 'left'], 'Invalid selection')
.oneOf(['R', 'L'], 'Invalid selection')
.required('Throwing hand is required'),
jerseyNumber: yup
.number()
@ -87,47 +105,67 @@ const schema = yup.object({
.required('Height is required'),
});
const { errors, resetForm, handleSubmit, defineField, setFieldValue } = useForm({
const { errors, resetForm, handleSubmit, defineField } = useForm({
validationSchema: schema,
initialValues: player.value
});
// Watch for changes in props.player to reset form when editing
// watch(
// () => route.params.id || route.query.id,
// (newId) => {
// if (newId) {
// fetchPlayerById(Number(newId)); // Fetch player when the ID changes
// }
// }
// );
onMounted(() => {
if (mode.value === 'view' || mode.value === 'edit') {
PlayerService
.fetchById(Number(id))
PlayerService.fetchById(Number(id))
.then((response) => {
player.value = response;
resetForm({ values: { ...player.value } }); // Reset form with fetched data
formattedDate.value = dayjs(player.value.user.dateOfBirth).format('YYYY-MM-DD');
})
.catch((error) => {
console.error('Failed to fetch player:', error);
});
} else {
console.log('Creating new player');
resetForm({values: {...player.value}}); // Reset form with fetched data
formattedDate.value = dayjs(player.value.user.dateOfBirth).format('YYYY-MM-DD');
}
resetForm({ values: { ...player.value } }); // Reset form with fetched data
formattedDate.value = dayjs(player.value.user.dateOfBirth).format('YYYY-MM-DD');
});
const submit = handleSubmit((values) => {
// const fetchPlayerById = async (id: number) => {
// try {
// const data = await PlayerService.fetchByUserId(id); // Fetch the player from the API
// player.value = { ...data }; // Load the API response into the form
// resetForm({ values: { ...player.value } }); // Reset form with fetched data
// formattedDate.value = dayjs(player.value.user.dateOfBirth).format('YYYY-MM-DD');
// isEdit.value = true; // Set to edit mode
// } catch (error) {
// console.error('Error fetching player data:', error);
// }
// };
// Submit logic: Differentiate between create and update
const submit = handleSubmit((values: Player) => {
if (mode.value === 'edit') {
console.log('Updating player:', values);
console.log('Updating player:', values); // Replace with update logic
// PlayerService.(player.value.id, player.value).then(() => router.push('/players'));
} else if (mode.value === 'create') {
console.log('Creating player:', values);
PlayerService.save(values).then(() => router.push('/home'));
console.log('Creating player:', values); // Replace with create logic
PlayerService.save(player.value).then(() => router.push('/home'));
}
}, ({errors}) => {
console.log(errors);
});
const onDateChange = (value: string) => {
formattedDate.value = value; // Update formatted string
player.value.user.dateOfBirth = new Date(value); // Convert to Date object and update
};
const [email, emailAttrs] = defineField('user.auth.email');
const [password, passwordAttrs] = defineField('user.auth.password');
const [firstName, firstNameAttrs] = defineField('user.firstName');
const [lastName, lastNameAttrs] = defineField('user.lastName');
const [dateOfBirth/*, dateOfBirthAttrs*/] = defineField('user.dateOfBirth');
// const [dateOfBirth, dateOfBirthAttrs] = defineField('formattedDate');
const [gender, genderAttrs] = defineField('gender');
const [bats, batsAttrs] = defineField('bats');
const [throws, throwsAttrs] = defineField('throws');
@ -135,35 +173,6 @@ const [jerseyNumber, jerseyNumberAttrs] = defineField('jerseyNumber');
const [weight, weightAttrs] = defineField('weight');
const [height, heightAttrs] = defineField('height');
const onDateChange = (value: string) => {
const input = value as string;
if (input) {
const parsedDate = dayjs(input, 'YYYY-MM-DD', true);
if (parsedDate.isValid()) {
setFieldValue('user.dateOfBirth', parsedDate.toDate());
} else {
setFieldValue('user.dateOfBirth', new Date());
}
} else {
setFieldValue('user.dateOfBirth', new Date());
}
formattedDate.value = value; // Update formatted string
player.value.user.dateOfBirth = new Date(value); // Convert to Date object and update
};
// Convert current value to yyyy-MM-dd for input
const dateValueString = computed(() => {
return dayjs(dateOfBirth.value).isValid()
? dayjs(dateOfBirth.value).format('YYYY-MM-DD')
: '';
});
const cancel = () => {
router.push('/home');
}
</script>
<template>
@ -180,62 +189,44 @@ const cancel = () => {
<ion-content class="ion-padding">
<form @submit="submit">
<ion-grid>
<ion-row>
<ion-col>
<ion-card>
<ion-card-header>
<ion-card-title>Personal Data</ion-card-title>
</ion-card-header>
<ion-card-content>
<!-- <CrudField name="user.email" label="Email" labelPlacement="floating"/>-->
<ion-item>
<Field name="user.auth.email">
<ion-input required
<Field name="email">
<ion-input
readonly: true
label="E-Mail"
label-placement="stacked"
v-model="email"
v-bind="emailAttrs"
type="email"
>
<small v-if="errors['user.auth.email']" class="error-message">{{ errors['user.auth.email'] }}</small>
<small v-if="errors['user.email']" class="error-message">{{ errors['user.email'] }}</small>
</ion-input>
</Field>
</ion-item>
</ion-col>
</ion-row>
<ion-row v-if="mode === 'create'">
<ion-col>
<ion-item>
<Field name="user.auth.password">
<ion-input required
label="Password"
label-placement="stacked"
v-model="password"
v-bind="passwordAttrs"
type="password"
>
<ion-input-password-toggle slot="end"></ion-input-password-toggle>
<small v-if="errors['user.auth.password']" class="error-message">{{ errors['user.auth.password'] }}</small>
</ion-input>
</Field>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-item>
<Field name="user.firstName">
<Field name="firstName">
<ion-input
readonly: true
label="First Name"
label-placement="stacked"
v-model="firstName"
v-bind="firstNameAttrs"
type="text">
type="text"
>
<small v-if="errors['user.firstName']" class="error-message">{{ errors['user.firstName'] }}</small>
</ion-input>
</Field>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-item>
<Field name="user.lastName">
<Field name="lastName">
<ion-input
label="Last Name"
label-placement="stacked"
@ -247,16 +238,13 @@ const cancel = () => {
</ion-input>
</Field>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-item>
<Field name="user.dateOfBirth">
<Field name="dateOfBirth">
<ion-input
label="Date of Birth"
label-placement="stacked"
:value="dateValueString"
v-model="formattedDate"
@input="onDateChange($event.target.value)"
type="date"
>
@ -264,10 +252,14 @@ const cancel = () => {
</ion-input>
</Field>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
</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
@ -283,10 +275,7 @@ const cancel = () => {
</ion-select>
</Field>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-item>
<Field name="bats">
<ion-select
@ -302,8 +291,7 @@ const cancel = () => {
</ion-select>
</Field>
</ion-item>
</ion-col>
<ion-col>
<ion-item>
<Field name="throws">
<ion-select
@ -318,10 +306,7 @@ const cancel = () => {
</ion-select>
</Field>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-item>
<Field name="jerseyNumber">
<ion-input
@ -335,10 +320,7 @@ const cancel = () => {
</ion-input>
</Field>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-item>
<Field name="weight">
<ion-input
@ -352,8 +334,7 @@ const cancel = () => {
</ion-input>
</Field>
</ion-item>
</ion-col>
<ion-col>
<ion-item>
<Field name="height">
<ion-input
@ -367,23 +348,12 @@ const cancel = () => {
</ion-input>
</Field>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-button expand="block" color="danger" @click="cancel">
Cancel
<ion-icon slot="end" :icon="stop"></ion-icon>
</ion-button>
</ion-col>
<ion-col>
<ion-button type="submit" expand="block" color="success">
Save
<ion-icon slot="end" :icon="save"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</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>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import {personOutline} from "ionicons/icons";
import {logOutOutline, personOutline, playOutline, statsChartOutline} from "ionicons/icons";
import {
IonAvatar,
IonButton,
@ -11,38 +11,19 @@ import {
IonTitle,
IonToolbar
} from "@ionic/vue";
import {computed, onMounted, ref} from "vue";
import {computed, ref} from "vue";
import {useStore} from "vuex";
import {useRoute, useRouter} from "vue-router";
import PlayerService from "@/services/PlayerService";
// import dayjs from "dayjs";
import {useRouter} from "vue-router";
const userImage = ref(null);
const router = useRouter();
const route = useRoute();
const store = useStore();
const id = route.params.id as string | undefined; // `id` parameter from route
const pitcher = computed(() => store.state.auth.user);
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
onMounted(() => {
PlayerService
.fetchByUserId(Number(id))
.then((response) => {
console.log('Fetched player:', response);
// player.value = response;
// resetForm({ values: { ...player.value } }); // Reset form with fetched data
// formattedDate.value = dayjs(player.value.user.dateOfBirth).format('YYYY-MM-DD');
})
.catch((error) => {
console.error('Failed to fetch player:', error);
});
});
const gotoHome = () => {
router.push({path: '/home'});
}

View File

@ -18,7 +18,6 @@ module.exports = {
await createUsers(queryInterface, roles, [
{ email: 'sparky.anderson@bullpen.com', password: 'sparky$123', firstName: 'Sparky', lastName: 'Anderson', dateOfBirth: new Date(1934, 1, 22), roles: ['coach', 'admin'] },
{ email: 'c@d', password: 'demo$123', firstName: 'Demo', lastName: 'Coach', dateOfBirth: new Date(1970, 1, 22), roles: ['coach', 'admin'] },
]);
},