All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 25s
- Implemented PendingRewardDialog for handling pending reward requests. - Created RewardConfirmDialog for confirming reward redemption. - Developed TaskConfirmDialog for task confirmation with child name display. test: add unit tests for ChildView and ParentView components - Added comprehensive tests for ChildView including task triggering and SSE event handling. - Implemented tests for ParentView focusing on override modal and SSE event management. test: add ScrollingList component tests - Created tests for ScrollingList to verify item fetching, loading states, and custom item classes. - Included tests for two-step click interactions and edit button display logic. - Moved toward hashed passwords.
206 lines
4.3 KiB
Vue
206 lines
4.3 KiB
Vue
<template>
|
|
<ModalDialog v-if="isOpen" @backdrop-click="$emit('close')">
|
|
<div class="override-edit-modal">
|
|
<h3>Edit {{ entityName }}</h3>
|
|
<div class="modal-body">
|
|
<label :for="`override-input-${entityId}`">
|
|
{{ entityType === 'task' ? 'New Points' : 'New Cost' }}:
|
|
</label>
|
|
<input
|
|
:id="`override-input-${entityId}`"
|
|
v-model.number="inputValue"
|
|
type="number"
|
|
min="0"
|
|
max="10000"
|
|
:disabled="loading"
|
|
@input="validateInput"
|
|
/>
|
|
<div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
|
|
<div class="default-hint">Default: {{ defaultValue }}</div>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button @click="$emit('close')" :disabled="loading" class="btn-secondary">Cancel</button>
|
|
<button @click="save" :disabled="!isValid || loading" class="btn-primary">Save</button>
|
|
</div>
|
|
</div>
|
|
</ModalDialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch } from 'vue'
|
|
import ModalDialog from './shared/ModalDialog.vue'
|
|
import { setChildOverride, parseErrorResponse } from '@/common/api'
|
|
|
|
const props = defineProps<{
|
|
isOpen: boolean
|
|
childId: string
|
|
entityId: string
|
|
entityType: 'task' | 'reward'
|
|
entityName: string
|
|
defaultValue: number
|
|
currentOverride?: number
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
close: []
|
|
saved: []
|
|
}>()
|
|
|
|
const inputValue = ref<number>(0)
|
|
const errorMessage = ref<string>('')
|
|
const isValid = ref<boolean>(true)
|
|
const loading = ref<boolean>(false)
|
|
|
|
// Initialize input value when modal opens
|
|
watch(
|
|
() => props.isOpen,
|
|
(newVal) => {
|
|
if (newVal) {
|
|
inputValue.value = props.currentOverride ?? props.defaultValue
|
|
validateInput()
|
|
}
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
function validateInput() {
|
|
const value = inputValue.value
|
|
|
|
if (value === null || value === undefined || isNaN(value)) {
|
|
errorMessage.value = 'Please enter a valid number'
|
|
isValid.value = false
|
|
return
|
|
}
|
|
|
|
if (value < 0 || value > 10000) {
|
|
errorMessage.value = 'Value must be between 0 and 10000'
|
|
isValid.value = false
|
|
return
|
|
}
|
|
|
|
errorMessage.value = ''
|
|
isValid.value = true
|
|
}
|
|
|
|
async function save() {
|
|
if (!isValid.value) {
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
|
|
try {
|
|
const response = await setChildOverride(
|
|
props.childId,
|
|
props.entityId,
|
|
props.entityType,
|
|
inputValue.value,
|
|
)
|
|
|
|
if (!response.ok) {
|
|
const { msg } = parseErrorResponse(response)
|
|
alert(`Error: ${msg}`)
|
|
loading.value = false
|
|
return
|
|
}
|
|
|
|
emit('saved')
|
|
emit('close')
|
|
} catch (error) {
|
|
alert(`Error: ${error}`)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.override-edit-modal {
|
|
background: var(--modal-bg);
|
|
padding: var(--spacing-md);
|
|
border-radius: var(--border-radius-md);
|
|
min-width: 300px;
|
|
}
|
|
|
|
.override-edit-modal h3 {
|
|
margin: 0 0 var(--spacing-md) 0;
|
|
color: var(--text-primary);
|
|
font-size: var(--font-size-lg);
|
|
}
|
|
|
|
.modal-body {
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
|
|
.modal-body label {
|
|
display: block;
|
|
margin-bottom: var(--spacing-xs);
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.modal-body input[type='number'] {
|
|
width: 100%;
|
|
padding: var(--spacing-sm);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--border-radius-sm);
|
|
font-size: var(--font-size-md);
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.modal-body input[type='number']:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.error-message {
|
|
color: var(--error-color);
|
|
font-size: var(--font-size-sm);
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
.default-hint {
|
|
color: var(--text-muted);
|
|
font-size: var(--font-size-sm);
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
.modal-actions {
|
|
display: flex;
|
|
gap: var(--spacing-sm);
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.modal-actions button {
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
border: none;
|
|
border-radius: var(--border-radius-sm);
|
|
font-size: var(--font-size-md);
|
|
cursor: pointer;
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
.modal-actions button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--btn-primary);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover:not(:disabled) {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: var(--btn-secondary);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.btn-secondary:hover:not(:disabled) {
|
|
opacity: 0.8;
|
|
}
|
|
</style>
|