mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
261 lines
8.6 KiB
Vue
261 lines
8.6 KiB
Vue
<template>
|
|
<open-creations-amount
|
|
:minimal-date="minimalDate"
|
|
:max-gdd-last-month="maxGddLastMonth"
|
|
:max-gdd-this-month="maxGddThisMonth"
|
|
/>
|
|
<div class="mb-3"></div>
|
|
<div class="contribution-form">
|
|
<BForm
|
|
class="form-style p-3 bg-white app-box-shadow gradido-border-radius"
|
|
@submit.prevent="submit"
|
|
>
|
|
<ValidatedInput
|
|
id="contribution-date"
|
|
:model-value="form.contributionDate"
|
|
name="contributionDate"
|
|
:label="$t('contribution.selectDate')"
|
|
:no-flip="true"
|
|
class="mb-4 bg-248"
|
|
type="date"
|
|
:rules="validationSchema.fields.contributionDate"
|
|
:disable-smart-valid-state="disableSmartValidState"
|
|
@update:model-value="updateField"
|
|
/>
|
|
<div v-if="noOpenCreation" class="p-3" data-test="contribution-message">
|
|
{{ noOpenCreation }}
|
|
</div>
|
|
<div v-else>
|
|
<ValidatedInput
|
|
id="contribution-memo"
|
|
:model-value="form.memo"
|
|
name="memo"
|
|
:label="$t('contribution.activity')"
|
|
:placeholder="$t('contribution.yourActivity')"
|
|
:rules="validationSchema.fields.memo"
|
|
textarea="true"
|
|
:disable-smart-valid-state="disableSmartValidState"
|
|
@update:model-value="updateField"
|
|
/>
|
|
<ValidatedInput
|
|
name="hours"
|
|
:model-value="form.hours"
|
|
:label="$t('form.hours')"
|
|
placeholder="0.01"
|
|
step="0.01"
|
|
type="text"
|
|
:rules="validationSchema.fields.hours"
|
|
:disable-smart-valid-state="disableSmartValidState"
|
|
@update:model-value="updateField"
|
|
/>
|
|
<LabeledInput
|
|
id="contribution-amount"
|
|
:model-value="form.amount"
|
|
class="mt-3"
|
|
name="amount"
|
|
:label="$t('form.amount')"
|
|
:placeholder="GDD_PER_HOUR"
|
|
readonly
|
|
type="text"
|
|
trim
|
|
/>
|
|
<BRow class="mt-5">
|
|
<BCol>
|
|
<BButton
|
|
block
|
|
type="reset"
|
|
variant="secondary"
|
|
data-test="button-cancel"
|
|
@click="emit('abort')"
|
|
>
|
|
{{ $t('form.cancel') }}
|
|
</BButton>
|
|
</BCol>
|
|
<BCol class="text-end mt-lg-0" @mouseover="disableSmartValidState = true">
|
|
<BButton
|
|
block
|
|
type="submit"
|
|
variant="gradido"
|
|
:disabled="disabled"
|
|
data-test="button-submit"
|
|
>
|
|
{{ form.id ? $t('form.change') : $t('contribution.submit') }}
|
|
</BButton>
|
|
</BCol>
|
|
</BRow>
|
|
</div>
|
|
</BForm>
|
|
</div>
|
|
</template>
|
|
<script setup>
|
|
import { reactive, computed, ref, onMounted, onUnmounted, toRaw } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import ValidatedInput from '@/components/Inputs/ValidatedInput'
|
|
import LabeledInput from '@/components/Inputs/LabeledInput'
|
|
import OpenCreationsAmount from './OpenCreationsAmount.vue'
|
|
import { object, date as dateSchema, number, string } from 'yup'
|
|
import { GDD_PER_HOUR } from '../../constants'
|
|
|
|
const amountToHours = (amount) => parseFloat(amount / GDD_PER_HOUR).toFixed(2)
|
|
const hoursToAmount = (hours) => parseFloat(hours * GDD_PER_HOUR).toFixed(2)
|
|
|
|
const props = defineProps({
|
|
modelValue: { type: Object, required: true },
|
|
maxGddLastMonth: { type: Number, required: true },
|
|
maxGddThisMonth: { type: Number, required: true },
|
|
})
|
|
|
|
const emit = defineEmits(['upsert-contribution', 'abort'])
|
|
|
|
const { t, d } = useI18n()
|
|
|
|
const entityDataToForm = computed(() => ({
|
|
...props.modelValue,
|
|
hours:
|
|
props.modelValue.hours !== undefined
|
|
? props.modelValue.hours
|
|
: amountToHours(props.modelValue.amount),
|
|
contributionDate: props.modelValue.contributionDate
|
|
? new Date(props.modelValue.contributionDate).toISOString().slice(0, 10)
|
|
: undefined,
|
|
}))
|
|
|
|
const form = reactive({ ...entityDataToForm.value })
|
|
|
|
const now = ref(new Date()) // checked every minute, updated if day, month or year changed
|
|
const disableSmartValidState = ref(false)
|
|
|
|
const isThisMonth = computed(() => {
|
|
const formContributionDate = new Date(form.contributionDate)
|
|
return (
|
|
formContributionDate.getMonth() === now.value.getMonth() &&
|
|
formContributionDate.getFullYear() === now.value.getFullYear()
|
|
)
|
|
})
|
|
|
|
const minimalDate = computed(() => {
|
|
const minimalDate = new Date(now.value)
|
|
minimalDate.setMonth(now.value.getMonth() - 1, 1)
|
|
return minimalDate
|
|
})
|
|
|
|
// reactive validation schema, because some boundaries depend on form input and existing data
|
|
const validationSchema = computed(() => {
|
|
const maxAmounts = Number(
|
|
isThisMonth.value ? parseFloat(props.maxGddThisMonth) : parseFloat(props.maxGddLastMonth),
|
|
)
|
|
const maxHours = parseFloat(Number(maxAmounts / GDD_PER_HOUR).toFixed(2))
|
|
|
|
return object({
|
|
// The date field is required and needs to be a valid date
|
|
// contribution date
|
|
contributionDate: dateSchema()
|
|
.required('form.validation.contributionDate.required')
|
|
.min(minimalDate.value.toISOString().slice(0, 10), ({ min }) => ({
|
|
key: 'form.validation.contributionDate.min',
|
|
values: { min: d(min) },
|
|
})) // min date is first day of last month
|
|
.max(now.value.toISOString().slice(0, 10), ({ max }) => ({
|
|
key: 'form.validation.contributionDate.max',
|
|
values: { max: d(max) },
|
|
})), // date cannot be in the future
|
|
memo: string()
|
|
.min(5, ({ min }) => ({ key: 'form.validation.contributionMemo.min', values: { min } }))
|
|
.max(512, ({ max }) => ({ key: 'form.validation.contributionMemo.max', values: { max } }))
|
|
.required('form.validation.contributionMemo.required'),
|
|
hours: string()
|
|
.typeError({ key: 'form.validation.hours.typeError', values: { min: 0.01, max: maxHours } })
|
|
.transform((currentValue) =>
|
|
!currentValue || typeof currentValue !== 'string'
|
|
? currentValue
|
|
: currentValue.replace(',', '.'),
|
|
)
|
|
// min and max are needed for html min max which validatedInput will take from this scheme
|
|
.min(0.01, ({ min }) => ({ key: 'form.validation.hours.min', values: { min } }))
|
|
.max(maxHours, ({ max }) => ({ key: 'form.validation.hours.max', values: { max } }))
|
|
.test('decimal-places', 'form.validation.hours.decimal-places', (value) => {
|
|
if (value === undefined || value === null) return true
|
|
return /^\d+(\.\d{0,2})?$/.test(value.toString())
|
|
})
|
|
// min and max are not working with string, so we need to do it manually
|
|
.test('min-hours', { key: 'form.validation.hours.min', values: { min: 0.01 } }, (value) => {
|
|
if (value === undefined || value === null || Number.isNaN(parseFloat(value))) {
|
|
return false
|
|
}
|
|
return parseFloat(value) >= 0.01
|
|
})
|
|
.test(
|
|
'max-hours',
|
|
{ key: 'form.validation.hours.max', values: { max: maxHours } },
|
|
(value) => {
|
|
if (value === undefined || value === null || Number.isNaN(parseFloat(value))) {
|
|
return false
|
|
}
|
|
return parseFloat(value) <= maxHours
|
|
},
|
|
),
|
|
amount: number().min(0.01).max(maxAmounts),
|
|
})
|
|
})
|
|
|
|
const disabled = computed(() => !validationSchema.value.isValidSync(form))
|
|
|
|
// decide message if no open creation exists
|
|
const noOpenCreation = computed(() => {
|
|
if (props.maxGddThisMonth <= 0 && props.maxGddLastMonth <= 0) {
|
|
return t('contribution.noOpenCreation.allMonth')
|
|
}
|
|
if (form.contributionDate) {
|
|
if (isThisMonth.value && props.maxGddThisMonth <= 0) {
|
|
return t('contribution.noOpenCreation.thisMonth')
|
|
}
|
|
if (!isThisMonth.value && props.maxGddLastMonth <= 0) {
|
|
return t('contribution.noOpenCreation.lastMonth')
|
|
}
|
|
}
|
|
return undefined
|
|
})
|
|
|
|
// make sure, that base date for min and max date is up to date, even if user work at midnight
|
|
onMounted(() => {
|
|
const interval = setInterval(() => {
|
|
const localNow = new Date()
|
|
if (
|
|
localNow.getDate() !== now.value.getDate() ||
|
|
localNow.getMonth() !== now.value.getMonth() ||
|
|
localNow.getFullYear() !== now.value.getFullYear()
|
|
) {
|
|
now.value = localNow
|
|
}
|
|
}, 60 * 1000) // check every minute
|
|
|
|
onUnmounted(() => {
|
|
clearInterval(interval)
|
|
})
|
|
})
|
|
|
|
const updateField = (newValue, name) => {
|
|
if (typeof name === 'string' && name.length) {
|
|
form[name] = newValue
|
|
if (name === 'hours') {
|
|
const hoursTransformed = validationSchema.value.fields.hours.cast(newValue)
|
|
const amount = hoursTransformed ? hoursToAmount(hoursTransformed) : GDD_PER_HOUR
|
|
form.amount = amount.toString()
|
|
}
|
|
}
|
|
}
|
|
|
|
function submit() {
|
|
emit('upsert-contribution', toRaw(form))
|
|
}
|
|
</script>
|
|
<style>
|
|
.form-style {
|
|
min-height: 410px;
|
|
}
|
|
|
|
span.errors {
|
|
color: red;
|
|
}
|
|
</style>
|