complete Complete User Request Submission section RAD-1

This commit is contained in:
2026-01-03 19:14:30 +03:30
commit 7137ec5792
393 changed files with 152602 additions and 0 deletions
@@ -0,0 +1,515 @@
<template>
<div class="ui-input-wrapper" :class="directionClass">
<label v-if="label" :for="id" class="ui-input-label">
{{ label }}
<span v-if="required" class="ui-input-required-indicator">*</span>
</label>
<div class="ui-input">
<div v-if="hasPrefixes" :class="['.ui-input-wrapper-border', prefixClasses]">
<i v-if="actualIconEnd" :class="[actualIconEnd, iconColorClass]" class="ui-input-icon-end !mt-2"></i>
<div
v-if="isSvgIconEnd && svgIconEndContent"
class="ui-input-icon-end has-svg"
:class="iconColorClass"
v-html="svgIconEndContent"
/>
<span v-if="prefixEnd" class="ui-input-prefix-end" :class="prefixColorClass">{{ prefixEnd }}</span>
<InputText
:id="id"
:model-value="displayValue"
:class="inputClassesNoBorder"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:type="inputType"
:maxlength="maxlength"
:minlength="minlength"
:autocomplete="autocomplete"
:autofocus="autofocus"
:spellcheck="spellcheck"
:tabindex="tabindex"
@input="handleInput"
@change="handleChange"
@focus="handleFocus"
@blur="handleBlur"
@keyup="handleKeyup"
@keydown="handleKeydown"
/>
<span v-if="prefixStart" class="ui-input-prefix-start" :class="prefixColorClass">{{ prefixStart }}</span>
<i v-if="actualIconStart" :class="[actualIconStart, iconColorClass]" class="ui-input-icon-start !mt-2"></i>
<div
v-if="isSvgIconStart && svgIconStartContent"
class="ui-input-icon-start has-svg"
:class="iconColorClass"
v-html="svgIconStartContent"
/>
<i
v-if="showPasswordToggle"
:class="[passwordToggleIcon, iconColorClass]"
class="ui-input-password-toggle !mt-2 cursor-pointer"
@click="togglePasswordVisibility"
></i>
</div>
<div v-else-if="prefix" :class="['.ui-input-wrapper-border', props.wrapperBorderClass, directionClass]">
<span class="ui-input-prefix">{{ prefix }}</span>
<InputText
:id="id"
:model-value="displayValue"
:class="inputClassesNoBorder"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:type="inputType"
:maxlength="maxlength"
:minlength="minlength"
:autocomplete="autocomplete"
:autofocus="autofocus"
:spellcheck="spellcheck"
:tabindex="tabindex"
@input="handleInput"
@change="handleChange"
@focus="handleFocus"
@blur="handleBlur"
@keyup="handleKeyup"
@keydown="handleKeydown"
/>
<i
v-if="showPasswordToggle"
:class="[passwordToggleIcon, iconColorClass]"
class="ui-input-password-toggle !mt-2 cursor-pointer"
@click="togglePasswordVisibility"
></i>
</div>
<div v-else class="relative">
<InputText
:id="id"
:model-value="displayValue"
:class="inputClasses"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:type="inputType"
:maxlength="maxlength"
:minlength="minlength"
:autocomplete="autocomplete"
:autofocus="autofocus"
:spellcheck="spellcheck"
:tabindex="tabindex"
@input="handleInput"
@change="handleChange"
@focus="handleFocus"
@blur="handleBlur"
@keyup="handleKeyup"
@keydown="handleKeydown"
/>
<i
v-if="showPasswordToggle"
:class="[passwordToggleIcon, iconColorClass]"
class="ui-input-password-toggle absolute top-1/2 left-3 transform -translate-y-1/2 cursor-pointer"
@click="togglePasswordVisibility"
></i>
</div>
</div>
<small v-if="error" class="ui-input-error-message">
{{ error }}
</small>
<small v-if="hint" class="ui-input-hint-message">
{{ hint }}
</small>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import InputText from 'primevue/inputtext'
import { convertPersianToEnglish } from '../../utils/numberUtils.js'
const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
},
label: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
type: {
type: String,
default: 'text',
validator: (value) => ['text', 'password', 'email', 'number', 'tel', 'url', 'search'].includes(value)
},
id: {
type: String,
default: () => `input-${Math.random().toString(36).substr(2, 9)}`
},
required: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
error: {
type: String,
default: ''
},
hint: {
type: String,
default: ''
},
maxlength: {
type: Number,
default: null
},
minlength: {
type: Number,
default: null
},
autocomplete: {
type: String,
default: 'off'
},
autofocus: {
type: Boolean,
default: false
},
spellcheck: {
type: Boolean,
default: false
},
tabindex: {
type: Number,
default: null
},
size: {
type: String,
default: 'default',
validator: (value) => ['small', 'default', 'large'].includes(value)
},
variant: {
type: String,
default: 'default',
validator: (value) => ['default', 'outlined', 'filled'].includes(value)
},
customClass: {
type: String,
default: ''
},
prefix: {
type: String,
default: ''
},
prefixStart: {
type: String,
default: ''
},
prefixEnd: {
type: String,
default: ''
},
iconStart: {
type: String,
default: ''
},
iconEnd: {
type: String,
default: ''
},
direction: {
type: String,
default: 'rtl',
validator: (value) => ['rtl', 'ltr'].includes(value)
},
prefixColor: {
type: String,
default: 'dominant-700'
},
iconColor: {
type: String,
default: 'dominant-700'
},
passwordDisplayType: {
type: String,
default: 'dots',
validator: (value) => ['default', 'dots', 'asterisks'].includes(value)
},
convertPersianNumbers: {
type: Boolean,
default: false
},
wrapperBorderClass: {
type: String,
default: 'ui-input-wrapper-border'
}
})
const emit = defineEmits([
'update:modelValue',
'input',
'change',
'focus',
'blur',
'keyup',
'keydown'
])
const inputValue = ref(props.modelValue)
const passwordVisible = ref(false)
watch(() => props.modelValue, (newValue) => {
inputValue.value = newValue
})
watch(inputValue, (newValue) => {
emit('update:modelValue', newValue)
})
const directionClass = computed(() => {
return props.direction === 'ltr' ? 'ui-input--ltr' : 'ui-input--rtl'
})
const hasPrefixes = computed(() => {
return props.prefixStart || props.prefixEnd || props.iconStart || props.iconEnd
})
const isSvgIconStart = computed(() => {
return props.iconStart && props.iconStart.startsWith('svg:')
})
const isSvgIconEnd = computed(() => {
return props.iconEnd && props.iconEnd.startsWith('svg:')
})
const svgIconStartName = computed(() => {
return isSvgIconStart.value ? props.iconStart.replace('svg:', '') : ''
})
const svgIconEndName = computed(() => {
return isSvgIconEnd.value ? props.iconEnd.replace('svg:', '') : ''
})
const actualIconStart = computed(() => {
return isSvgIconStart.value ? '' : props.iconStart
})
const actualIconEnd = computed(() => {
return isSvgIconEnd.value ? '' : props.iconEnd
})
const showPasswordToggle = computed(() => {
return props.type === 'password'
})
const inputType = computed(() => {
if (props.type === 'password') {
if (passwordVisible.value || props.passwordDisplayType !== 'default') {
return 'text'
}
return 'password'
}
return props.type
})
const displayValue = computed(() => {
if (props.type === 'password' && !passwordVisible.value && inputValue.value) {
if (props.passwordDisplayType === 'dots') {
return '•'.repeat(inputValue.value.length)
} else if (props.passwordDisplayType === 'asterisks') {
return '*'.repeat(inputValue.value.length)
}
}
return inputValue.value
})
const passwordToggleIcon = computed(() => {
return passwordVisible.value ? 'fi-rr-eye-crossed' : 'fi-rr-eye'
})
const prefixClasses = computed(() => {
const classes = [directionClass.value]
if (props.prefixStart) classes.push('ui-input-with-prefix-start')
if (props.prefixEnd) classes.push('ui-input-with-prefix-end')
if (props.iconStart || isSvgIconStart.value) classes.push('ui-input-with-icon-start')
if (props.iconEnd || isSvgIconEnd.value) classes.push('ui-input-with-icon-end')
if (showPasswordToggle.value) classes.push('ui-input-with-password-toggle')
if (props.wrapperBorderClass) {
classes.push(props.wrapperBorderClass + ' ui-input-wrapper-border')
}
return classes.join(' ')
})
const prefixColorClass = computed(() => {
return `text-${props.prefixColor}`
})
const iconColorClass = computed(() => {
return `text-${props.iconColor}`
})
const loadSvgContent = async (svgName) => {
try {
const svgModule = await import(`../../components/svg/${svgName}.svg?raw`)
return svgModule.default
} catch (error) {
return null
}
}
const svgIconStartContent = ref('')
const svgIconEndContent = ref('')
watch(() => svgIconStartName.value, async (newName) => {
if (newName) {
const content = await loadSvgContent(newName)
svgIconStartContent.value = content
} else {
svgIconStartContent.value = ''
}
}, { immediate: true })
watch(() => svgIconEndName.value, async (newName) => {
if (newName) {
const content = await loadSvgContent(newName)
svgIconEndContent.value = content
} else {
svgIconEndContent.value = ''
}
}, { immediate: true })
const togglePasswordVisibility = () => {
passwordVisible.value = !passwordVisible.value
}
const inputClassesNoBorder = computed(() => {
const classes = []
switch (props.size) {
case 'small':
classes.push('p-2', 'text-xs')
break
case 'large':
classes.push('p-4', 'text-base')
break
default:
classes.push('p-3', 'text-sm')
}
if (props.variant === 'filled') {
classes.push('bg-gray-100')
}
if (props.error) {
classes.push('border-red-500')
}
if (props.disabled) {
classes.push('opacity-50', 'cursor-not-allowed')
}
if (props.customClass) {
classes.push(props.customClass)
}
return classes.join(' ')
})
const inputClasses = computed(() => {
const classes = []
switch (props.size) {
case 'small':
classes.push('p-2', 'text-xs')
break
case 'large':
classes.push('p-4', 'text-base')
break
default:
classes.push('p-3', 'text-sm')
}
switch (props.variant) {
case 'outlined':
classes.push('border-2')
break
case 'filled':
classes.push('bg-gray-100')
break
default:
classes.push('border')
}
if (props.error) {
classes.push('border-red-500')
}
if (props.disabled) {
classes.push('opacity-50', 'cursor-not-allowed')
}
if (props.customClass) {
classes.push(props.customClass)
}
return classes.join(' ')
})
const handleInput = (event) => {
let newValue = event.target.value
if (props.convertPersianNumbers) {
newValue = convertPersianToEnglish(newValue)
event.target.value = newValue
}
if (props.type === 'password' && !passwordVisible.value && props.passwordDisplayType !== 'default') {
const currentLength = inputValue.value.length
const newLength = newValue.length
if (newLength > currentLength) {
inputValue.value = inputValue.value + newValue.slice(currentLength)
} else if (newLength < currentLength) {
inputValue.value = inputValue.value.slice(0, newLength)
}
} else {
inputValue.value = newValue
}
emit('input', event)
}
const handleChange = (event) => {
emit('change', event)
}
const handleFocus = (event) => {
emit('focus', event)
}
const handleBlur = (event) => {
emit('blur', event)
}
const handleKeyup = (event) => {
emit('keyup', event)
}
const handleKeydown = (event) => {
emit('keydown', event)
}
</script>