From bd08dcc2126198c5fd8d997eee32dc6b2e550230 Mon Sep 17 00:00:00 2001 From: tdv Date: Tue, 7 Oct 2025 19:18:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BD=D0=B0=D1=85=D1=83=D0=B5=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=82=D0=B8=D0=BB.=20it=20works=20but=20sucks,=20tryed?= =?UTF-8?q?=20to=20add=20reactivity=20to=20admin=20page=20and=20its=20chil?= =?UTF-8?q?d=20dialogs=20and=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../customcompometns/AdminDeviceDropdown.vue | 24 +- .../src/customcompometns/Admincomponent.vue | 211 +++++++++--------- .../customcompometns/DataTableNoCheckbox.vue | 10 +- .../DeviceCertificateDialog.vue | 2 +- .../customcompometns/DeviceTasksDialog.vue | 2 +- .../src/customcompometns/EditDeviceDialog.vue | 135 +++++++---- 6 files changed, 225 insertions(+), 159 deletions(-) diff --git a/management-ui/src/customcompometns/AdminDeviceDropdown.vue b/management-ui/src/customcompometns/AdminDeviceDropdown.vue index 3136ebe..062c193 100644 --- a/management-ui/src/customcompometns/AdminDeviceDropdown.vue +++ b/management-ui/src/customcompometns/AdminDeviceDropdown.vue @@ -9,22 +9,36 @@ import DeleteDeviceDialog from './DeleteDeviceDialog.vue' import DeviceCertificateDialog from './DeviceCertificateDialog.vue' import DeviceTasksDialog from './DeviceTasksDialog.vue' import { Ellipsis } from 'lucide-vue-next' -import type { Device } from '@/lib/interfaces' +import type { Device, Users } from '@/lib/interfaces' // import { api } from '@/lib/api' -const props = defineProps<{ row: Device }>() // ← accept full row +const props = defineProps<{ + row: Device + allUsers?: Users[] + onRowUpdated?: (row: Device, payload: { name: string; userIds: string[] }) => void + onRowDeleted?: (row: Device) => void +}>() // ← accept full row const isEditOpen = ref(false) const isDeleteOpen = ref(false) const isTasksOpen = ref(false) const itCertsOpen = ref(false) +const emit = defineEmits<{ + (e: 'refresh'): void + (e: 'error', err: unknown): void +}>() + function onDeleteConfirmed() { // await api.delete(`/devices/${encodeURIComponent(props.row.guid)}`) isDeleteOpen.value = false + // emit('refresh') + props.onRowDeleted?.(props.row) } -function onEditConfirm() { +function onEditConfirm(_payload: { name: string; userIds: string[] }) { isEditOpen.value = false + // emit('refresh') + props.onRowUpdated?.(props.row, _payload) } function onTaskConfirm() { @@ -47,11 +61,11 @@ function onCertsConfirm() { Rename Tasks Certificates - Delete + Delete - + diff --git a/management-ui/src/customcompometns/Admincomponent.vue b/management-ui/src/customcompometns/Admincomponent.vue index f9f3e99..adec5d7 100644 --- a/management-ui/src/customcompometns/Admincomponent.vue +++ b/management-ui/src/customcompometns/Admincomponent.vue @@ -1,38 +1,33 @@ \ No newline at end of file + + + Users + Devices + Trackers + + + + + + + + + + + + + + + diff --git a/management-ui/src/customcompometns/DataTableNoCheckbox.vue b/management-ui/src/customcompometns/DataTableNoCheckbox.vue index eb8f3cd..6602884 100644 --- a/management-ui/src/customcompometns/DataTableNoCheckbox.vue +++ b/management-ui/src/customcompometns/DataTableNoCheckbox.vue @@ -33,7 +33,13 @@ const props = defineProps<{ * :dropdownComponent="MyActionsMenu" * /> */ - dropdownComponent?: DefineComponent<{ row: TData }, any, any> + // dropdownComponent?: DefineComponent<{ row: TData }, any, any> + dropdownComponent?: DefineComponent<{ row: TData } & { + onRowUpdated?: (row: TData, payload: any) => void + onRowDeleted?: (row: TData) => void + }, any, any> + onRowUpdated?: (row: TData, payload: any) => void // <-- NEW + onRowDeleted?: (row: TData) => void // <-- NEW }>() // ——— Table setup ——— @@ -97,6 +103,8 @@ const table = useVueTable({ :is="props.dropdownComponent" :row="row.original" :key="row.id" + :on-row-updated="props.onRowUpdated" + :on-row-deleted="props.onRowDeleted" /> diff --git a/management-ui/src/customcompometns/DeviceCertificateDialog.vue b/management-ui/src/customcompometns/DeviceCertificateDialog.vue index 1b69d90..847681c 100644 --- a/management-ui/src/customcompometns/DeviceCertificateDialog.vue +++ b/management-ui/src/customcompometns/DeviceCertificateDialog.vue @@ -37,7 +37,7 @@ function onSave() { List of certificates - List of certificates for device {{ props.device?.guid }} + {{ props.device?.guid }} diff --git a/management-ui/src/customcompometns/DeviceTasksDialog.vue b/management-ui/src/customcompometns/DeviceTasksDialog.vue index 39ec1bf..4f1c8e0 100644 --- a/management-ui/src/customcompometns/DeviceTasksDialog.vue +++ b/management-ui/src/customcompometns/DeviceTasksDialog.vue @@ -35,7 +35,7 @@ function onSave() { Tasks - List of tasks for device {{ props.device?.guid }} + {{ props.device?.guid }} diff --git a/management-ui/src/customcompometns/EditDeviceDialog.vue b/management-ui/src/customcompometns/EditDeviceDialog.vue index 61ece79..ce70dec 100644 --- a/management-ui/src/customcompometns/EditDeviceDialog.vue +++ b/management-ui/src/customcompometns/EditDeviceDialog.vue @@ -10,76 +10,111 @@ import { import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' -import { defineProps, defineEmits, ref, watch } from 'vue' +import { defineProps, defineEmits, ref, watch, computed } from 'vue' import type { PropType } from 'vue' import AssignDevice from './AssignDevice.vue' import type { Device, Users } from '@/lib/interfaces' +import { api } from '@/lib/api' + + +type DeviceWithUsers = + & Partial + & { guid?: string; name?: string; devicename?: string } + & { users?: Array<{ id: number; username: string; role?: string }> } // 1) runtime props so Vue + TS agree const props = defineProps({ - modelValue: { - type: Boolean as PropType, - required: true, - }, - device: { type: Object as PropType, required: false }, - allUsers: { type: Array as PropType, required: false }, - initialUserIds: { type: Array as PropType, required: false }, // <- if you have IDs already + modelValue: { type: Boolean as PropType, required: true }, + device: { type: Object as PropType, required: true }, // must have guid + allUsers: { type: Array as PropType, required: false }, }) // 2) two emits: v-model and confirm const emit = defineEmits<{ (e: 'update:modelValue', v: boolean): void - (e: 'confirm'): void + // (e: 'confirm'): void + (e: 'updated', payload: { name: string; userIds: string[] }): void }>() +// ---------- Local form state ---------- +const guid = computed(() => String(props.device?.guid ?? '')) +const originalName = computed(() => (props.device?.devicename ?? props.device?.name ?? '')) +const originalIds = computed(() => + Array.isArray(props.device?.users) ? props.device!.users.map(u => String(u.id)) : [] +) + const name = ref('') const selectedUserIds = ref([]) -// helper: map usernames → ids when we only have a string of usernames -const usernameToId = (uname: string): string | null => { - const id = props.allUsers?.find(u => u.username === uname)?.id - return typeof id === 'number' ? String(id) : null -} - - -// when device changes or dialog opens, update local value watch( () => props.device, - (dev) => { - console.log(dev?.assigned_users) - name.value = dev?.devicename ?? '' - if (props.initialUserIds && props.initialUserIds.length) { - selectedUserIds.value = [...props.initialUserIds] - return - } - const usernames = (dev?.assigned_users ?? '') - .split(',') - .map(s => s.trim()) - .filter(Boolean) - - if (usernames.length && props.allUsers?.length) { - selectedUserIds.value = usernames - .map(usernameToId) - .filter((x): x is string => !!x) - } else { - selectedUserIds.value = [] - } + () => { + name.value = originalName.value + selectedUserIds.value = [...originalIds.value] }, { immediate: true } ) -function onSave() { - emit('confirm') - // close the dialog - emit('update:modelValue', false) +// ---------- Save (conditional API calls) ---------- +const saving = ref(false) +const errorText = ref(null) + +function changedName() { + return name.value.trim() !== originalName.value.trim() +} +function changedUsers() { + // compare as sets + const a = new Set(originalIds.value) + const b = new Set(selectedUserIds.value) + if (a.size !== b.size) return true + for (const id of a) if (!b.has(id)) return true + return false +} + +async function onSave() { + if (!guid.value) return + errorText.value = null + saving.value = true + try { + const ops: Promise[] = [] + + if (changedName()) { + ops.push( + api.post(`/devices/${encodeURIComponent(guid.value)}/rename`, { + name: name.value.trim(), + } as { name: string }) + ) + } + + if (changedUsers()) { + const userIdsNum = selectedUserIds.value + .map(v => Number(v)) + .filter(n => Number.isFinite(n)) as number[] + + ops.push( + api.post(`/devices/${encodeURIComponent(guid.value)}/set_users`, { + userIds: userIdsNum, + } as { userIds: number[] }) + ) + } + + if (ops.length > 0) { + await Promise.all(ops) + } + + emit('updated', { name: name.value, userIds: [...selectedUserIds.value] }) + emit('update:modelValue', false) + } catch (err: any) { + console.error(err) + errorText.value = err?.response?.data?.message || 'Failed to save changes.' + } finally { + saving.value = false + } }