|
|
@@ -8,6 +8,18 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="content">
|
|
|
+ <!-- Universal Search -->
|
|
|
+ <div class="search-box">
|
|
|
+ <input
|
|
|
+ v-model="searchQuery"
|
|
|
+ type="text"
|
|
|
+ :placeholder="$t('devices.searchPlaceholder')"
|
|
|
+ class="search-input"
|
|
|
+ @input="onSearch"
|
|
|
+ />
|
|
|
+ <span v-if="searchQuery" class="search-clear" @click="clearSearch">×</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div v-if="loading" class="loading">{{ $t('common.loading') }}</div>
|
|
|
|
|
|
<div v-else-if="error" class="error">{{ error }}</div>
|
|
|
@@ -125,6 +137,8 @@ const editingDevice = ref(null)
|
|
|
const deviceToDelete = ref(null)
|
|
|
const saving = ref(false)
|
|
|
const deleting = ref(false)
|
|
|
+const searchQuery = ref('')
|
|
|
+let searchDebounceTimer = null
|
|
|
|
|
|
const form = ref({
|
|
|
organization_id: null,
|
|
|
@@ -135,7 +149,11 @@ async function loadDevices() {
|
|
|
loading.value = true
|
|
|
error.value = null
|
|
|
try {
|
|
|
- devices.value = await devicesApi.getAllSuperadmin()
|
|
|
+ const params = {}
|
|
|
+ if (searchQuery.value && searchQuery.value.length >= 2) {
|
|
|
+ params.search = searchQuery.value
|
|
|
+ }
|
|
|
+ devices.value = await devicesApi.getAllSuperadmin(params)
|
|
|
} catch (err) {
|
|
|
error.value = err.response?.data?.detail || 'Failed to load devices'
|
|
|
} finally {
|
|
|
@@ -143,6 +161,19 @@ async function loadDevices() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function onSearch() {
|
|
|
+ // Debounce search - wait 300ms after user stops typing
|
|
|
+ clearTimeout(searchDebounceTimer)
|
|
|
+ searchDebounceTimer = setTimeout(() => {
|
|
|
+ loadDevices()
|
|
|
+ }, 300)
|
|
|
+}
|
|
|
+
|
|
|
+function clearSearch() {
|
|
|
+ searchQuery.value = ''
|
|
|
+ loadDevices()
|
|
|
+}
|
|
|
+
|
|
|
async function loadOrganizations() {
|
|
|
try {
|
|
|
organizations.value = await organizationsApi.getAll()
|
|
|
@@ -220,6 +251,14 @@ onMounted(() => {
|
|
|
.page-header h1 { font-size: 32px; font-weight: 700; color: #1a202c; margin-bottom: 8px; }
|
|
|
.page-header p { color: #718096; font-size: 16px; }
|
|
|
.content { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); }
|
|
|
+
|
|
|
+/* Universal Search */
|
|
|
+.search-box { position: relative; margin-bottom: 20px; }
|
|
|
+.search-input { width: 100%; padding: 12px 40px 12px 16px; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 14px; transition: border-color 0.2s; }
|
|
|
+.search-input:focus { outline: none; border-color: #667eea; }
|
|
|
+.search-input::placeholder { color: #a0aec0; }
|
|
|
+.search-clear { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); font-size: 24px; color: #a0aec0; cursor: pointer; line-height: 1; padding: 0 4px; }
|
|
|
+.search-clear:hover { color: #718096; }
|
|
|
.loading, .error, .empty { text-align: center; padding: 40px; color: #718096; }
|
|
|
.error { color: #e53e3e; }
|
|
|
.data-table { width: 100%; border-collapse: collapse; }
|