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
+14
View File
@@ -0,0 +1,14 @@
<script setup>
defineProps({
class: {
type: String,
default: ''
}
});
</script>
<template>
<div class="bg-white border border-gray-200 rounded-2xl p-4" :class="class">
<slot />
</div>
</template>
@@ -0,0 +1,154 @@
<script setup>
import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
const props = defineProps({
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
maximizable: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'medium',
validator: (value) => ['small', 'medium', 'large', 'xlarge'].includes(value)
},
closable: {
type: Boolean,
default: true
},
dismissableMask: {
type: Boolean,
default: true
},
modal: {
type: Boolean,
default: true
},
class: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:visible', 'hide', 'show', 'fullscreen-change'])
const isVisible = ref(props.visible)
const isFullscreen = ref(false)
watch(() => props.visible, (newValue) => {
isVisible.value = newValue
})
watch(isVisible, (newValue) => {
emit('update:visible', newValue)
if (newValue) {
emit('show')
nextTick(() => {
checkFullscreenState()
})
} else {
emit('hide')
}
})
const checkFullscreenState = () => {
const dialog = document.querySelector('.p-dialog:not(.p-dialog-hidden)')
if (dialog) {
const rect = dialog.getBoundingClientRect()
const isMaximized = rect.width >= window.innerWidth * 0.95 && rect.height >= window.innerHeight * 0.95
if (isMaximized !== isFullscreen.value) {
isFullscreen.value = isMaximized
emit('fullscreen-change', isMaximized)
}
}
}
const handleResize = () => {
if (isVisible.value) {
checkFullscreenState()
}
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
const hide = () => {
isVisible.value = false
}
const show = () => {
isVisible.value = true
}
const handleMaximize = () => {
isFullscreen.value = true
emit('fullscreen-change', true)
}
const handleUnmaximize = () => {
isFullscreen.value = false
emit('fullscreen-change', false)
}
const sizeClasses = {
small: 'max-w-md',
medium: 'max-w-lg',
large: 'max-w-2xl',
xlarge: 'max-w-3xl'
}
defineExpose({
show,
hide
})
</script>
<template>
<Dialog
v-model:visible="isVisible"
:modal="modal"
:closable="closable"
:dismissableMask="dismissableMask"
:maximizable="maximizable"
@hide="hide"
@show="show"
@maximize="handleMaximize"
@unmaximize="handleUnmaximize"
:class="[!maximizable ? sizeClasses[size] : '', props.class]"
:pt="{
root: 'backdrop-blur-sm',
header: 'border-b border-gray-200',
content: 'p-0',
footer: 'border-t border-gray-200',
}"
>
<template #header v-if="title">
<div class="flex items-center justify-between w-full">
<h3 class="text-lg font-semibold text-gray-900">{{ title }}</h3>
</div>
</template>
<div class="p-6">
<slot :isFullscreen="isFullscreen" />
</div>
<template #footer v-if="$slots.footer">
<div class="flex justify-end gap-3">
<slot name="footer" />
</div>
</template>
</Dialog>
</template>
@@ -0,0 +1,165 @@
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const props = defineProps({
modelValue: {
type: [String, Number, Array, Object],
default: null
},
options: {
type: Array,
default: () => []
},
optionValue: {
type: String,
default: 'value'
},
optionLabel: {
type: String,
default: 'label'
},
placeholder: {
type: String,
default: 'انتخاب کنید'
},
multiple: {
type: Boolean,
default: false
},
filter: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false
},
searchUrl: {
type: String,
default: null
},
searchParam: {
type: String,
default: 'search'
},
class: {
type: String,
default: '!text-xs !h-full'
},
pt: {
type: Object,
default: () => ({
option: '!text-sm',
placeholder: '!text-sm',
label: '!pt-3'
})
}
})
const emit = defineEmits(['update:modelValue', 'filter', 'change', 'update:options'])
const isLoading = ref(false)
const searchTimeout = ref(null)
const handleFilter = (event) => {
if (searchTimeout.value) {
clearTimeout(searchTimeout.value)
}
const searchValue = event.value ? event.value.trim() : ''
// If search is empty, make request immediately (no debounce)
if (!searchValue) {
isLoading.value = true
if (props.searchUrl) {
axios.get(props.searchUrl, {
params: {}
}).then(response => {
if (response.data.success) {
emit('update:options', response.data.data)
}
isLoading.value = false
}).catch(error => {
console.error('Search error:', error)
isLoading.value = false
})
} else {
emit('filter', event)
isLoading.value = false
}
return
}
// If search has value, use debounce
isLoading.value = true
searchTimeout.value = setTimeout(() => {
if (props.searchUrl) {
const params = {}
params[props.searchParam] = searchValue
axios.get(props.searchUrl, {
params: params
}).then(response => {
if (response.data.success) {
const newOptions = response.data.data
const currentValue = props.modelValue
const currentOption = props.options.find(opt => {
const value = typeof opt === 'object' ? opt[props.optionValue] : opt
return value == currentValue
})
let finalOptions = newOptions
if (currentOption && !newOptions.find(opt => {
const value = typeof opt === 'object' ? opt[props.optionValue] : opt
return value == currentValue
})) {
finalOptions = [currentOption, ...newOptions]
}
emit('update:options', finalOptions)
}
isLoading.value = false
}).catch(error => {
console.error('Search error:', error)
isLoading.value = false
})
} else {
emit('filter', event)
isLoading.value = false
}
}, 1000)
}
const handleChange = (value) => {
emit('update:modelValue', value)
emit('change', value)
}
onMounted(() => {
return () => {
if (searchTimeout.value) {
clearTimeout(searchTimeout.value)
}
}
})
</script>
<template>
<Select
:class="class"
:placeholder="placeholder"
:modelValue="modelValue"
:options="options"
:optionValue="optionValue"
:optionLabel="optionLabel"
:multiple="multiple"
:filter="filter"
:loading="loading || isLoading"
:pt="pt"
@update:modelValue="handleChange"
@filter="handleFilter"
/>
</template>