mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
finally a working solution
This commit is contained in:
parent
4f1ea1f5ba
commit
772901e073
@ -7,40 +7,42 @@
|
||||
>
|
||||
<ValidatedInput
|
||||
id="contribution-date"
|
||||
:model-value="formValues.date"
|
||||
:model-value="date"
|
||||
name="date"
|
||||
:label="$t('contribution.selectDate')"
|
||||
:no-flip="true"
|
||||
class="mb-4 bg-248"
|
||||
type="date"
|
||||
:rules="validationSchema"
|
||||
:rules="validationSchema.fields.date"
|
||||
@update:model-value="updateField"
|
||||
/>
|
||||
<div v-if="showMessage" class="p-3" data-test="contribtion-message">
|
||||
<div v-if="noOpenCreation" class="p-3" data-test="contribtion-message">
|
||||
{{ noOpenCreation }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<input-textarea
|
||||
<ValidatedInput
|
||||
id="contribution-memo"
|
||||
:model-value="formValues.memo"
|
||||
:model-value="memo"
|
||||
name="memo"
|
||||
:label="$t('contribution.activity')"
|
||||
:placeholder="$t('contribution.yourActivity')"
|
||||
:rules="validationSchema"
|
||||
:rules="validationSchema.fields.memo"
|
||||
@update:model-value="updateField"
|
||||
textarea="true"
|
||||
/>
|
||||
<ValidatedInput
|
||||
name="hours"
|
||||
:model-value="formValues.hours"
|
||||
:model-value="hours"
|
||||
:label="$t('form.hours')"
|
||||
placeholder="0.01"
|
||||
step="0.01"
|
||||
type="number"
|
||||
:rules="validationSchema"
|
||||
:rules="validationSchema.fields.hours"
|
||||
@update:model-value="updateField"
|
||||
/>
|
||||
<LabeledInput
|
||||
id="contribution-amount"
|
||||
:model-value="amount"
|
||||
class="mt-3"
|
||||
name="amount"
|
||||
:label="$t('form.amount')"
|
||||
@ -79,48 +81,63 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { reactive, computed, watchEffect, watch, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import InputTextarea from '@/components/Inputs/InputTextarea'
|
||||
import ValidatedInput from '@/components/Inputs/ValidatedInput'
|
||||
import LabeledInput from '@/components/Inputs/LabeledInput'
|
||||
import { memo } from '@/validationSchemas'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { object, date, number } from 'yup'
|
||||
import { memo as memoSchema } from '@/validationSchemas'
|
||||
import { object, date as dateSchema, number } from 'yup'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, required: true },
|
||||
isThisMonth: { type: Boolean, required: true },
|
||||
minimalDate: { type: Date, required: true },
|
||||
maxGddLastMonth: { type: Number, required: true },
|
||||
maxGddThisMonth: { type: Number, required: true },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update-contribution', 'set-contribution'])
|
||||
const emit = defineEmits(['update-contribution', 'set-contribution', 'update:modelValue'])
|
||||
|
||||
const { t } = useI18n()
|
||||
let allFieldsValid = ref(false)
|
||||
|
||||
const form = ref({ ...props.modelValue })
|
||||
const form = reactive({ ...props.modelValue })
|
||||
|
||||
const maxHours = computed(() => Number(props.isThisMonth ? props.maxGddThisMonth : props.maxGddLastMonth / 20))
|
||||
// update local form if in parent form changed, it is necessary because the community page will reuse this form also for editing existing
|
||||
// contributions, and it will reusing a existing instance of this component
|
||||
watch(() => props.modelValue, (newValue) => Object.assign(form, newValue))
|
||||
|
||||
// use computed to make sure child input update if props from parent from this component change
|
||||
const amount = computed(() => (form.hours * 20).toFixed(2).toString())
|
||||
const date = computed(() => form.date)
|
||||
const hours = computed(() => form.hours)
|
||||
const memo = computed(() => form.memo)
|
||||
|
||||
const isThisMonth = computed(() => {
|
||||
const formDate = new Date(form.date)
|
||||
const now = new Date()
|
||||
return formDate.getMonth() === now.getMonth() && formDate.getFullYear() === now.getFullYear()
|
||||
});
|
||||
|
||||
// const maxHours = computed(() => Number((isThisMonth.value ? props.maxGddThisMonth : props.maxGddLastMonth) / 20))
|
||||
// const maxAmounts = computed(() => Number(isThisMonth.value ? parseFloat(props.maxGddThisMonth): parseFloat(props.maxGddLastMonth)))
|
||||
|
||||
const validationSchema = computed(() => {
|
||||
// const maxGDD = Number(props.isThisMonth ? props.maxGddThisMonth : props.maxGddLastMonth)
|
||||
// const maxHours = Number(maxGDD / 20)
|
||||
console.log('compute validationSchema', { maxHours })
|
||||
const maxHours = Number((isThisMonth.value ? props.maxGddThisMonth : props.maxGddLastMonth) / 20)
|
||||
const maxAmounts = Number(isThisMonth.value ? parseFloat(props.maxGddThisMonth): parseFloat(props.maxGddLastMonth))
|
||||
|
||||
return object({
|
||||
// The date field is required and needs to be a valid date
|
||||
// contribution date
|
||||
date: date()
|
||||
date: dateSchema()
|
||||
.required('contribution.noDateSelected')
|
||||
.min(new Date(new Date().setMonth(new Date().getMonth() - 1, 1)).toISOString().slice(0, 10)) // min date is first day of last month
|
||||
.max(new Date().toISOString().slice(0, 10))
|
||||
.default(''), // date cannot be in the future
|
||||
memo,
|
||||
hours: number().required('contribution.noHours')
|
||||
memo: memoSchema,
|
||||
hours: number().transform((value, originalValue) =>
|
||||
originalValue === "" ? undefined : value
|
||||
)
|
||||
.required('contribution.noHours')
|
||||
.min(0.01, ({min}) => ({ key: 'form.validation.gddCreationTime.min', values: { min } }))
|
||||
.max(maxHours.value, ({max}) => ({ key: 'form.validation.gddCreationTime.max', values: { max } }))
|
||||
.max(maxHours, ({max}) => ({ key: 'form.validation.gddCreationTime.max', values: { max } }))
|
||||
.test(
|
||||
'decimal-places',
|
||||
'form.validation.gddCreationTime.decimal-places',
|
||||
@ -129,78 +146,40 @@ const validationSchema = computed(() => {
|
||||
return /^\d+(\.\d{0,2})?$/.test(value.toString())
|
||||
}
|
||||
),
|
||||
})
|
||||
})
|
||||
const {
|
||||
values: formValues,
|
||||
meta: formMeta,
|
||||
resetForm,
|
||||
setFieldValue,
|
||||
} = useForm({
|
||||
initialValues: {
|
||||
date: props.modelValue.date,
|
||||
memo: props.modelValue.memo,
|
||||
hours: props.modelValue.hours,
|
||||
amount: props.modelValue.amount,
|
||||
},
|
||||
validationSchema: validationSchema.value,
|
||||
amount: number().max(maxAmounts)
|
||||
})
|
||||
})
|
||||
|
||||
const updateField = (newValue, name) => {
|
||||
if (typeof name === 'string' && name.length) {
|
||||
setFieldValue(name, newValue)
|
||||
form[name] = newValue
|
||||
if (name === 'hours') {
|
||||
form.amount = (newValue * 20).toFixed(2).toString()
|
||||
}
|
||||
}
|
||||
if (name === 'hours') {
|
||||
setFieldValue('amount', (newValue * 20).toFixed(2).toString())
|
||||
}
|
||||
/*
|
||||
validationSchema.value.validateAt(name, formValues)
|
||||
.then(() => {
|
||||
allFieldsValid = true
|
||||
})
|
||||
.catch((e) => {
|
||||
allFieldsValid = false
|
||||
console.log('validation error')
|
||||
console.log(JSON.stringify(e, null, 2))
|
||||
//errorMessage = e.message
|
||||
})*/
|
||||
console.log('update field', { newValue, name, form, amount: amount.value })
|
||||
emit('update:modelValue', form)
|
||||
}
|
||||
|
||||
const showMessage = computed(() => {
|
||||
if (props.maxGddThisMonth <= 0 && props.maxGddLastMonth <= 0) return true
|
||||
if (props.modelValue.date)
|
||||
return (
|
||||
(props.isThisMonth && props.maxGddThisMonth <= 0) ||
|
||||
(!props.isThisMonth && props.maxGddLastMonth <= 0)
|
||||
)
|
||||
return false
|
||||
})
|
||||
|
||||
const disabled = computed(() => {
|
||||
return !allFieldsValid
|
||||
/*
|
||||
return (
|
||||
!formMeta.value.valid ||
|
||||
(props.isThisMonth && parseFloat(form.value.amount) > parseFloat(props.maxGddThisMonth)) ||
|
||||
(!props.isThisMonth && parseFloat(form.value.amount) > parseFloat(props.maxGddLastMonth))
|
||||
)*/
|
||||
})
|
||||
const disabled = computed(() => !validationSchema.value.isValidSync(form))
|
||||
|
||||
const noOpenCreation = computed(() => {
|
||||
if (props.maxGddThisMonth <= 0 && props.maxGddLastMonth <= 0) {
|
||||
return t('contribution.noOpenCreation.allMonth')
|
||||
}
|
||||
if (props.isThisMonth && props.maxGddThisMonth <= 0) {
|
||||
return t('contribution.noOpenCreation.thisMonth')
|
||||
if (form.date) {
|
||||
if (props.isThisMonth && props.maxGddThisMonth <= 0) {
|
||||
return t('contribution.noOpenCreation.thisMonth')
|
||||
}
|
||||
if (!props.isThisMonth && props.maxGddLastMonth <= 0) {
|
||||
return t('contribution.noOpenCreation.lastMonth')
|
||||
}
|
||||
}
|
||||
if (!props.isThisMonth && props.maxGddLastMonth <= 0) {
|
||||
return t('contribution.noOpenCreation.lastMonth')
|
||||
}
|
||||
return ''
|
||||
return undefined
|
||||
})
|
||||
|
||||
function submit() {
|
||||
const dataToSave = { ...formValues }
|
||||
const dataToSave = { ...form }
|
||||
let emitOption = 'set-contribution'
|
||||
if (props.modelValue.id) {
|
||||
dataToSave.id = props.modelValue.id
|
||||
@ -211,14 +190,12 @@ function submit() {
|
||||
}
|
||||
|
||||
function fullFormReset() {
|
||||
resetForm({
|
||||
values: {
|
||||
date: '',
|
||||
memo: '',
|
||||
hours: 0.0,
|
||||
amount: '',
|
||||
}
|
||||
})
|
||||
form = {
|
||||
date: '',
|
||||
memo: '',
|
||||
hours: 0.0,
|
||||
amount: 0.0,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
@ -1,17 +1,25 @@
|
||||
<template>
|
||||
<div :class="wrapperClassName">
|
||||
<BFormGroup :label="label" :label-for="labelFor">
|
||||
<BFormInput
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<BFormTextarea v-if="textarea"
|
||||
v-bind="{ ...$attrs, id: labelFor, name }"
|
||||
v-model="model"
|
||||
trim
|
||||
:rows="4"
|
||||
:max-rows="4"
|
||||
no-resize
|
||||
/>
|
||||
<BFormInput v-else
|
||||
v-bind="{ ...$attrs, id: labelFor, name }"
|
||||
v-model="model"
|
||||
/>
|
||||
<slot></slot>
|
||||
</BFormGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineOptions } from 'vue'
|
||||
|
||||
import { computed, defineOptions, watchEffect, ref, defineModel } from 'vue'
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
@ -25,8 +33,27 @@ const props = defineProps({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
// modelValue: [String, Number, Date],
|
||||
textarea: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
})
|
||||
|
||||
const wrapperClassName = computed(() => `input-${props.name}`)
|
||||
const model = defineModel()
|
||||
// const model = ref(props.modelValue)
|
||||
/*const model = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})*/
|
||||
const wrapperClassName = computed(() => props.name ? `input-${props.name}` : 'input')
|
||||
const labelFor = computed(() => `${props.name}-input-field`)
|
||||
|
||||
// const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
watchEffect(() => {
|
||||
console.log(`${props.name}: ${props.modelValue}`)
|
||||
// model.value = props.modelValue
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -3,96 +3,82 @@
|
||||
v-bind="$attrs"
|
||||
:min="minValue"
|
||||
:max="maxValue"
|
||||
:model-value="currentValue"
|
||||
:model-value="model"
|
||||
:reset-value="resetValue"
|
||||
:locale="$i18n.locale"
|
||||
:required="!isOptional"
|
||||
:label="props.label"
|
||||
:name="props.name"
|
||||
:label="label"
|
||||
:name="name"
|
||||
:state="valid"
|
||||
@input="updateValue($event.target.value)">
|
||||
@update:modelValue="updateValue"
|
||||
>
|
||||
<BFormInvalidFeedback v-if="errorMessage">
|
||||
{{ $t(translatedErrorString) }}
|
||||
{{ errorMessage }}
|
||||
</BFormInvalidFeedback>
|
||||
</LabeledInput>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import LabeledInput from './LabeledInput'
|
||||
import { computed, ref, watchEffect, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { isLanguageKey } from '@/validationSchemas'
|
||||
import { useField } from 'vee-validate'
|
||||
import LabeledInput from './LabeledInput'
|
||||
import { translateYupErrorString } from '@/validationSchemas'
|
||||
|
||||
const props = defineProps({
|
||||
errorMessage: [String, Object],
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
modelValue: [String, Number],
|
||||
modelValue: [String, Number, Date],
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rules: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const model = ref(props.modelValue)
|
||||
let wasUpdated = false
|
||||
|
||||
let valid = ref(false)
|
||||
let errorMessage = ref('')
|
||||
let currentValue = ref(props.modelValue)
|
||||
function updateErrorMessage() {
|
||||
try {
|
||||
props.rules.validateSync(model.value)
|
||||
errorMessage.value = undefined
|
||||
} catch(e) {
|
||||
errorMessage.value = translateYupErrorString(e.message, t)
|
||||
}
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const translatedErrorString = computed(() => {
|
||||
const error = errorMessage.value
|
||||
const type = typeof error
|
||||
// console.log(error)
|
||||
if (type === 'object') {
|
||||
return t(error.key, error.values)
|
||||
} else if (type === 'string' && error.length > 0 && isLanguageKey(error)) {
|
||||
return t(error)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
})
|
||||
const errorMessage = ref()
|
||||
const valid = computed(() => props.rules.isValidSync(model.value))
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const updateValue = ((newValue) => {
|
||||
// emit('update:modelValue', newValue, props.name)
|
||||
wasUpdated = true
|
||||
model.value = newValue
|
||||
updateErrorMessage()
|
||||
emit('update:modelValue', newValue, props.name)
|
||||
})
|
||||
|
||||
const data = new Object()
|
||||
data[props.name] = newValue
|
||||
|
||||
const result = props.rules.validateSyncAt(props.name, data)
|
||||
console.log('result: ', JSON.stringify(result, null, 2))
|
||||
/*
|
||||
.then(() => {
|
||||
valid = true
|
||||
console.log("%s is valid, emit event", props.name)
|
||||
emit('update:modelValue', newValue, props.name)
|
||||
})
|
||||
.catch((e) => {
|
||||
valid = false
|
||||
console.log(t(e.message))
|
||||
errorMessage = e.message
|
||||
console.log('max value: %o for name: %s, rules: %o', maxValue.value, props.name, props.rules)
|
||||
})
|
||||
*/
|
||||
// validate on rule change, but only if updateValue was called at least one
|
||||
watch(() => props.rules, () => {
|
||||
if(wasUpdated) {
|
||||
updateErrorMessage()
|
||||
}
|
||||
})
|
||||
// update model if parent change
|
||||
watch(() => props.modelValue, () => {
|
||||
model.value = props.modelValue
|
||||
})
|
||||
|
||||
// extract additional parameter like min and max from schema
|
||||
const getDateOnly = (schemaDescription, name) => schemaDescription.fields[props.name].tests.find((test) => test.name === name)?.params[name]
|
||||
|
||||
const schemaDescription = computed(() => props.rules.describe())
|
||||
console.log(schemaDescription.value)
|
||||
const minValue = computed(() => getDateOnly(schemaDescription.value, 'min'))
|
||||
const maxValue = computed(() => getDateOnly(schemaDescription.value, 'max'))
|
||||
const resetValue = computed(() => schemaDescription.value.fields[props.name].default)
|
||||
const isOptional = computed(() => schemaDescription.value.fields[props.name].optional)
|
||||
|
||||
// const { value: currentValue, errorMessage, meta } = useField(props.name, props.rules)
|
||||
|
||||
const getTestParameter = (name) => schemaDescription.value?.tests?.find(t => t.name === name)?.params[name]
|
||||
const minValue = computed(() => getTestParameter('min'))
|
||||
const maxValue = computed(() => getTestParameter('max'))
|
||||
const resetValue = computed(() => schemaDescription.default)
|
||||
const isOptional = computed(() => schemaDescription.optional)
|
||||
</script>
|
||||
|
||||
@ -10,9 +10,7 @@
|
||||
/>
|
||||
<div class="mb-3"></div>
|
||||
<contribution-form
|
||||
:key="computedKeyFromForm"
|
||||
v-model="form"
|
||||
:is-this-month="isThisMonth"
|
||||
:minimal-date="minimalDate"
|
||||
:max-gdd-last-month="maxForMonths[0]"
|
||||
:max-gdd-this-month="maxForMonths[1]"
|
||||
@ -95,7 +93,7 @@ const form = ref({
|
||||
date: '',
|
||||
memo: '',
|
||||
hours: 0,
|
||||
amount: '',
|
||||
amount: 20,
|
||||
})
|
||||
const originalContributionDate = ref('')
|
||||
const updateAmount = ref('')
|
||||
@ -107,14 +105,6 @@ const minimalDate = computed(() => {
|
||||
return new Date(date.setMonth(date.getMonth() - 1, 1))
|
||||
})
|
||||
|
||||
const isThisMonth = computed(() => {
|
||||
const formDate = new Date(form.value.date)
|
||||
return (
|
||||
formDate.getFullYear() === maximalDate.value.getFullYear() &&
|
||||
formDate.getMonth() === maximalDate.value.getMonth()
|
||||
)
|
||||
})
|
||||
|
||||
const amountToAdd = computed(() => (form.value.id ? parseInt(updateAmount.value) : 0))
|
||||
|
||||
const maxForMonths = computed(() => {
|
||||
@ -273,13 +263,20 @@ const handleUpdateListContributions = (pagination) => {
|
||||
}
|
||||
|
||||
const handleUpdateContributionForm = (item) => {
|
||||
/*Object.assign(form.value, {
|
||||
id: item.id,
|
||||
date: new Date(item.contributionDate).toISOString().slice(0, 10),
|
||||
memo: item.memo,
|
||||
amount: item.amount,
|
||||
hours: item.amount / 20,
|
||||
})*/
|
||||
form.value = {
|
||||
id: item.id,
|
||||
date: new Date(item.contributionDate).toISOString().slice(0, 10),
|
||||
memo: item.memo,
|
||||
amount: item.amount,
|
||||
hours: item.amount / 20,
|
||||
}
|
||||
}//*/
|
||||
originalContributionDate.value = item.contributionDate
|
||||
updateAmount.value = item.amount
|
||||
tabIndex.value = 0
|
||||
|
||||
@ -1,7 +1,18 @@
|
||||
import { string } from 'yup'
|
||||
|
||||
// TODO: only needed for grace period, before all inputs updated for using veeValidate + yup
|
||||
export const isLanguageKey = (str) => str.match(/^(?!\.)[a-z][a-zA-Z0-9]*([.][a-z][a-zA-Z0-9]*)*(?<!\.)$/)
|
||||
export const isLanguageKey = (str) => str.match(/^(?!\.)[a-z][a-zA-Z0-9-]*([.][a-z][a-zA-Z0-9-]*)*(?<!\.)$/)
|
||||
|
||||
export const translateYupErrorString = (error, t) => {
|
||||
const type = typeof error
|
||||
if (type === 'object') {
|
||||
return t(error.key, error.values)
|
||||
} else if (type === 'string' && error.length > 0 && isLanguageKey(error)) {
|
||||
return t(error)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
export const memo = string()
|
||||
.required('contribution.yourActivity')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user