Compare commits

...

5 Commits

Author SHA1 Message Date
Sascha Kühl 8618533283 progress on player view 2025-05-28 16:09:07 +02:00
Sascha Kühl 7891c1e229 added CrudField.vue component 2025-05-28 16:08:53 +02:00
Sascha Kühl 55c62b8a6b fixed dates for development data 2025-05-28 16:08:39 +02:00
Sascha Kühl 068c1e9f63 ensure that user and auth.email are transmitted 2025-05-28 16:08:19 +02:00
Sascha Kühl 8481740ea5 removed obsolete "both" enum for throws attribute 2025-05-28 16:07:44 +02:00
9 changed files with 115 additions and 36 deletions

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import { IonInput, IonItem } from '@ionic/vue';
import { Field, useField } from 'vee-validate';
interface Props {
name: string; // The name of the field for validation
label: string; // The label text
labelPlacement?: string; // Placement of the label (optional)
type?: string; // Input type, e.g., "text", "number", etc.
}
const props = defineProps<Props>(); // Define props for the component
const { value, errorMessage, ...fieldAttrs } = useField(props.name); // Setup field logic using vee-validate
</script>
<template>
<ion-item>
<Field name="name">
<IonInput
:label="label"
:label-placement="labelPlacement || 'stacked'"
v-model="value"
v-bind="fieldAttrs"
:type="type || 'text'"
>
<small v-if="errorMessage" class="error-message">{{ errorMessage }}</small>
</IonInput>
</Field>
</ion-item>
</template>
<style scoped>
.error-message {
color: red;
font-size: 12px;
}
</style>

View File

@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from '@ionic/vue-router';
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { RouteRecordRaw, RouteLocationNormalizedGeneric } from 'vue-router'; import { RouteRecordRaw, RouteLocationNormalizedGeneric } from 'vue-router';
import PlayerList from '../views/PlayerList.vue' import PlayerList from '../views/PlayerList.vue'
// import PlayerView from '../views/PlayerView.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";
@ -24,12 +24,10 @@ const routes: Array<RouteRecordRaw> = [
{ path: '/stats', component: BullpenListView }, { path: '/stats', component: BullpenListView },
{ path: '/summary', component: BullpenSummaryView }, { path: '/summary', component: BullpenSummaryView },
{ {
path: '/player/:id', path: '/player',
name: 'EditPlayer', component: PlayerView,
component: () => import('@/views/PlayerView.vue'),
props: (route: RouteLocationNormalizedGeneric) => ({ props: (route: RouteLocationNormalizedGeneric) => ({
player: route.state?.player || null, // Pass the player from the state if available id: route.query.id
mode: route.query.mode || 'create', // Default to "create" if no query exists
}), }),
}, },

View File

@ -7,4 +7,5 @@ export default interface User {
roles: string[], roles: string[],
createdAt: Date, createdAt: Date,
updatedAt: Date, updatedAt: Date,
auth: { email: string }
} }

View File

@ -38,7 +38,7 @@ const showStats = () => {
}; };
const showProfile = () => { const showProfile = () => {
router.push({ path: '/profile' }); router.push({ path: '/player', query: { id: user.value.id }});
}; };
const logout = () => { const logout = () => {

View File

@ -56,7 +56,7 @@ const submit = handleSubmit((values, { resetForm }) => {
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/selectPlayer', user);
}).then(() => { }).then(() => {
resetForm(); resetForm();
onLogin(); onLogin();

View File

@ -18,17 +18,22 @@ import {
import { Field, useForm } from 'vee-validate'; import { Field, useForm } from 'vee-validate';
import * as yup from 'yup'; import * as yup from 'yup';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { ref, computed, watch } from 'vue'; import { ref, onMounted, watch } from 'vue';
import Player from '@/types/Player'; import Player from '@/types/Player';
// import CrudField from '@/components/CrudField.vue'
import PlayerService from '@/services/PlayerService';
import {useRoute} from 'vue-router';
const props = defineProps<{ player?: Player }>(); const props = defineProps<{ player?: Player }>();
// Todo: create default player
const defaultPlayer: Player = { const defaultPlayer: Player = {
id: 0, id: 0,
gender: 'male', gender: 'male',
bats: 'R', bats: 'R',
throws: 'R', throws: 'R',
height: 0,
weight: 0,
state: 'active',
jerseyNumber: 0, jerseyNumber: 0,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
@ -41,12 +46,17 @@ const defaultPlayer: Player = {
roles: [], roles: [],
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
auth: {
email: ''
}
} }
} }
// ...
// Computed reactive player object: Either use the provided player or initialize with default // Computed reactive player object: Either use the provided player or initialize with default
const player = ref<Player>(props.player ? { ...props.player } : { ...defaultPlayer }); const player = ref<Player>(props.player ? { ...props.player } : { ...defaultPlayer });
const isEdit = ref(false);
const route = useRoute();
const formattedDate = ref(player.value.user.dateOfBirth.toISOString().split('T')[0]);
const schema = yup.object({ const schema = yup.object({
email: yup email: yup
@ -86,23 +96,40 @@ const schema = yup.object({
.required('Height is required'), .required('Height is required'),
}); });
const { errors, handleSubmit, defineField } = useForm({ const { errors, resetForm, handleSubmit, defineField } = useForm({
validationSchema: schema, validationSchema: schema,
initialValues: player.value initialValues: player.value
}); });
// Watch for changes in props.player to reset form when editing // Watch for changes in props.player to reset form when editing
watch( watch(
() => props.player, () => route.params.id || route.query.id,
(newPlayer) => { (newId) => {
if (newPlayer) { if (newId) {
resetForm({ values: { ...newPlayer } }); fetchPlayerById(Number(newId)); // Fetch player when the ID changes
} else {
resetForm({ values: { ...defaultPlayer } });
} }
} }
); );
onMounted(() => {
const id = route.params.id || route.query.id; // Get ID from params or query
if (id) {
fetchPlayerById(Number(id));
}
});
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 // Submit logic: Differentiate between create and update
const submit = handleSubmit((values: Player) => { const submit = handleSubmit((values: Player) => {
if (player.value.id) { if (player.value.id) {
@ -112,12 +139,15 @@ const submit = handleSubmit((values: Player) => {
} }
}); });
const isEdit = computed(() => !!player.value.id); 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.email'); const [email, emailAttrs] = defineField('user.auth.email');
const [firstName, firstNameAttrs] = defineField('user.firstName'); const [firstName, firstNameAttrs] = defineField('user.firstName');
const [lastName, lastNameAttrs] = defineField('user.lastName'); const [lastName, lastNameAttrs] = defineField('user.lastName');
const [dateOfBirth, dateOfBirthAttrs] = defineField('user.dateOfBirth'); // const [dateOfBirth, dateOfBirthAttrs] = defineField('formattedDate');
const [gender, genderAttrs] = defineField('gender'); const [gender, genderAttrs] = defineField('gender');
const [bats, batsAttrs] = defineField('bats'); const [bats, batsAttrs] = defineField('bats');
const [throws, throwsAttrs] = defineField('throws'); const [throws, throwsAttrs] = defineField('throws');
@ -142,9 +172,11 @@ const [height, heightAttrs] = defineField('height');
<ion-card-title>Personal Data</ion-card-title> <ion-card-title>Personal Data</ion-card-title>
</ion-card-header> </ion-card-header>
<ion-card-content> <ion-card-content>
<!-- <CrudField name="user.email" label="Email" labelPlacement="floating"/>-->
<ion-item> <ion-item>
<Field name="email"> <Field name="email">
<ion-input <ion-input
readonly: true
label="E-Mail" label="E-Mail"
label-placement="stacked" label-placement="stacked"
v-model="email" v-model="email"
@ -159,6 +191,7 @@ const [height, heightAttrs] = defineField('height');
<ion-item> <ion-item>
<Field name="firstName"> <Field name="firstName">
<ion-input <ion-input
readonly: true
label="First Name" label="First Name"
label-placement="stacked" label-placement="stacked"
v-model="firstName" v-model="firstName"
@ -189,8 +222,8 @@ const [height, heightAttrs] = defineField('height');
<ion-input <ion-input
label="Date of Birth" label="Date of Birth"
label-placement="stacked" label-placement="stacked"
v-model="dateOfBirth" v-model="formattedDate"
v-bind="dateOfBirthAttrs" @input="onDateChange($event.target.value)"
type="date" type="date"
> >
<small v-if="errors['user.dateOfBirth']" class="error-message">{{ errors['user.dateOfBirth'] }}</small> <small v-if="errors['user.dateOfBirth']" class="error-message">{{ errors['user.dateOfBirth'] }}</small>
@ -230,9 +263,9 @@ const [height, heightAttrs] = defineField('height');
v-bind="batsAttrs" v-bind="batsAttrs"
interface="action-sheet" interface="action-sheet"
> >
<ion-select-option value="R">Right</ion-select-option> <ion-select-option value="right">Right</ion-select-option>
<ion-select-option value="L">Left</ion-select-option> <ion-select-option value="left">Left</ion-select-option>
<ion-select-option value="S">Switch</ion-select-option> <ion-select-option value="both">Switch</ion-select-option>
</ion-select> </ion-select>
</Field> </Field>
</ion-item> </ion-item>
@ -244,9 +277,10 @@ const [height, heightAttrs] = defineField('height');
label-placement="stacked" label-placement="stacked"
v-model="throws" v-model="throws"
v-bind="throwsAttrs" v-bind="throwsAttrs"
interface="action-sheet"
> >
<ion-select-option value="R">Right</ion-select-option> <ion-select-option value="right">Right</ion-select-option>
<ion-select-option value="L">Left</ion-select-option> <ion-select-option value="left">Left</ion-select-option>
</ion-select> </ion-select>
</Field> </Field>
</ion-item> </ion-item>

View File

@ -2,6 +2,7 @@ const db = require("../models/index");
const {registerUser} = require("../helper/user.helper"); const {registerUser} = require("../helper/user.helper");
const Player = db.Player; const Player = db.Player;
const User = db.User; const User = db.User;
const Auth = db.Auth;
const Op = db.Sequelize.Op; const Op = db.Sequelize.Op;
exports.insert = (req, res) => { exports.insert = (req, res) => {
@ -63,7 +64,15 @@ exports.findOneByUserId = (req, res) => {
Player.findOne({ Player.findOne({
where: { userId: userId }, where: { userId: userId },
include: { model: User } include: {
model: User,
as: 'user',
include: {
model: Auth,
as: 'auth',
attributes: ['email']
}
}
}).then(data => { }).then(data => {
if (data) { if (data) {
res.send(data); res.send(data);

View File

@ -29,7 +29,7 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.ENUM('left', 'right', 'both'), type: DataTypes.ENUM('left', 'right', 'both'),
}, },
throws: { throws: {
type: DataTypes.ENUM('left', 'right', 'both'), type: DataTypes.ENUM('left', 'right'),
}, },
jerseyNumber: { jerseyNumber: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,

View File

@ -8,16 +8,16 @@ module.exports = {
const roles = await queryInterface.select(null, 'Roles'); const roles = await queryInterface.select(null, 'Roles');
const players = [ const players = [
{ email: 'ryan.nolan@bullpen.com', password: 'ryan$123', firstName: 'Nolan', lastName: 'Ryan', dateOfBirth: new Date(1947, 1, 31), jerseyNumber: 17, gender: 'male', bats: 'right', throws: 'right', state: 'active' }, { email: 'ryan.nolan@bullpen.com', password: 'ryan$123', firstName: 'Nolan', lastName: 'Ryan', dateOfBirth: new Date(1947, 0, 31), jerseyNumber: 17, gender: 'male', bats: 'right', throws: 'right', state: 'active' },
{ email: 'sandy.koufax@bullpen.com', password: 'sandy$123', firstName: 'Sandy', lastName: 'Koufax', dateOfBirth: new Date(1935, 12, 30), jerseyNumber: 18, gender: 'male', bats: 'right', throws: 'right', state: 'active' }, { email: 'sandy.koufax@bullpen.com', password: 'sandy$123', firstName: 'Sandy', lastName: 'Koufax', dateOfBirth: new Date(1935, 11, 30), jerseyNumber: 18, gender: 'male', bats: 'right', throws: 'right', state: 'active' },
{ email: 'pedro.martinez@bullpen.com', password: 'pedro$123', firstName: 'Pedro', lastName: 'Martinez', dateOfBirth: new Date(1971, 10, 25), jerseyNumber: 19, gender: 'male', bats: 'right', throws: 'right', state: 'active' }, { email: 'pedro.martinez@bullpen.com', password: 'pedro$123', firstName: 'Pedro', lastName: 'Martinez', dateOfBirth: new Date(1971, 9, 25), jerseyNumber: 19, gender: 'male', bats: 'right', throws: 'right', state: 'active' },
{ email: 'randy.johnson@bullpen.com', password: 'randy$123', firstName: 'Randy', lastName: 'Johnson', dateOfBirth: new Date(1963, 9, 10), jerseyNumber: 20, gender: 'male', bats: 'right', throws: 'right', state: 'active' }, { email: 'randy.johnson@bullpen.com', password: 'randy$123', firstName: 'Randy', lastName: 'Johnson', dateOfBirth: new Date(1963, 8, 10), jerseyNumber: 20, gender: 'male', bats: 'right', throws: 'right', state: 'active' },
]; ];
await createPlayer(queryInterface, roles, players); await createPlayer(queryInterface, roles, players);
await createUsers(queryInterface, roles, [ await createUsers(queryInterface, roles, [
{ email: 'sparky.anderson@bullpen.com', password: 'sparky$123', firstName: 'Sparky', lastName: 'Anderson', dateOfBirth: new Date(1934, 22, 2), roles: ['coach', 'admin'] }, { email: 'sparky.anderson@bullpen.com', password: 'sparky$123', firstName: 'Sparky', lastName: 'Anderson', dateOfBirth: new Date(1934, 1, 22), roles: ['coach', 'admin'] },
]); ]);
}, },