Переглянути джерело

add the delete user on frontend

gdias 1 місяць тому
батько
коміт
7ca64cc31b
2 змінених файлів з 101 додано та 29 видалено
  1. 49 9
      src/lib/components/ui/PopUpDelete.svelte
  2. 52 20
      src/routes/users/+page.svelte

+ 49 - 9
src/lib/components/ui/PopUpDelete.svelte

@@ -1,15 +1,41 @@
 <script>
-  import { createEventDispatcher } from 'svelte';
+  import { createEventDispatcher, onMount, onDestroy } from 'svelte';
 
   export let visible = false;
   export let title = 'Confirmar ação';
   export let confirmText = 'Confirmar';
   export let cancelText = 'Cancelar';
   export let disableBackdropClose = false;
+  export let confirmDisabled = false;
+
+  let host;
+  let movedToBody = false;
 
   const dispatch = createEventDispatcher();
 
+  onMount(() => {
+    if (typeof document === 'undefined') return;
+    if (!host || movedToBody) return;
+    const existing = document.querySelectorAll('[data-popup-delete-host="true"]');
+    existing.forEach((node) => {
+      if (node !== host && node?.parentNode) {
+        node.parentNode.removeChild(node);
+      }
+    });
+    document.body.appendChild(host);
+    movedToBody = true;
+  });
+
+  onDestroy(() => {
+    if (typeof document === 'undefined') return;
+    if (movedToBody && host?.parentNode === document.body) {
+      document.body.removeChild(host);
+    }
+    movedToBody = false;
+  });
+
   function onConfirm() {
+    if (confirmDisabled) return;
     dispatch('confirm');
   }
   function onCancel() {
@@ -23,15 +49,22 @@
   }
 </script>
 
-{#if visible}
   <div
-    class="fixed inset-0 z-50 flex items-center justify-center"
+    bind:this={host}
+    data-popup-delete-host="true"
+    class="fixed inset-0 z-[9999] flex items-center justify-center p-4"
+    style={
+      visible
+        ? 'display:flex;position:fixed;top:0;right:0;bottom:0;left:0;width:100vw;height:100vh;z-index:9999;align-items:center;justify-content:center;padding:16px;'
+        : 'display:none;position:fixed;top:0;right:0;bottom:0;left:0;width:100vw;height:100vh;z-index:9999;'
+    }
     aria-modal="true"
     role="dialog"
     aria-label={title}
   >
     <div
-      class="absolute inset-0 bg-black/50"
+      class="absolute inset-0 bg-black/20 z-0"
+      style="background: rgba(0,0,0,0.10);"
       role="button"
       tabindex="0"
       aria-label="Fechar modal"
@@ -43,10 +76,10 @@
         }
       }}
     ></div>
-    <div class="bg-white dark:bg-gray-800 w-full max-w-sm rounded-lg shadow-lg">
+    <div class="relative z-10 bg-white dark:bg-gray-800 w-full max-w-sm rounded-lg shadow-lg">
       <div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
         <h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">{title}</h3>
-        <button class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" aria-label="Fechar" on:click={onCancel}>✕</button>
+        <button type="button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" aria-label="Fechar" on:click={onCancel}>✕</button>
       </div>
 
       <div class="p-6 text-sm text-gray-700 dark:text-gray-300">
@@ -56,9 +89,16 @@
       </div>
 
       <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end gap-3">
-        <button class="px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700" on:click={onCancel}>{cancelText}</button>
-        <button class="px-4 py-2 rounded-md bg-red-600 hover:bg-red-700 text-white" on:click={onConfirm}>{confirmText}</button>
+        <button type="button" class="px-4 py-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700" on:click={onCancel}>{cancelText}</button>
+        <button
+          type="button"
+          class="px-4 py-2 rounded-md bg-red-600 hover:bg-red-700 text-white disabled:opacity-60 disabled:cursor-not-allowed"
+          disabled={confirmDisabled}
+          on:click={onConfirm}
+        >
+          {confirmText}
+        </button>
       </div>
     </div>
   </div>
-{/if}
+

+ 52 - 20
src/routes/users/+page.svelte

@@ -30,6 +30,8 @@
   // Confirmação de delete
   let showDeleteConfirm = false;
   let rowToDelete = null;
+  let deleteLoading = false;
+  let deleteError = '';
 
   let selectedUser = null;
   let showDetails = false;
@@ -177,6 +179,8 @@
     const { row } = e?.detail || {};
     if (!row) return;
     rowToDelete = row;
+    deleteError = '';
+    deleteLoading = false;
     showDeleteConfirm = true;
   }
 
@@ -229,31 +233,43 @@
   }
 
   async function confirmDelete() {
-    if (!rowToDelete) { showDeleteConfirm = false; return; }
-    const userId =
-      rowToDelete?.__raw?.user_id ??
-      rowToDelete?.__raw?.userId ??
-      rowToDelete?.__raw?.id ??
-      rowToDelete?.user_id ??
-      rowToDelete?.userId ??
-      rowToDelete?.id;
-    if (userId == null) {
-      console.error('ID do usuário não encontrado para exclusão.');
+    if (deleteLoading) return;
+    if (!rowToDelete) {
       showDeleteConfirm = false;
-      rowToDelete = null;
       return;
     }
+
+    deleteError = '';
+    const userIdRaw = getUserIdFromRow(rowToDelete);
+    const userId = Number(userIdRaw);
+
+    if (!Number.isInteger(userId) || userId <= 0) {
+      deleteError = 'user_id inválido para exclusão.';
+      return;
+    }
+    if (!$authToken) {
+      deleteError = 'Token de autenticação não encontrado.';
+      return;
+    }
+
+    deleteLoading = true;
+    let deleted = false;
+
     try {
       const res = await fetch(`${apiUrl}/user/delete`, {
         method: 'POST',
         headers: {
           'content-type': 'application/json',
-          ...( $authToken ? { Authorization: `Bearer ${$authToken}` } : {} )
+          Authorization: `Bearer ${$authToken}`
         },
         body: JSON.stringify({ user_id: userId })
       });
+
+      if (res.status === 204) {
+        throw new Error('Usuário não encontrado.');
+      }
+
       const raw = await res.text();
-      //console.log('delete user raw body:', raw);
       let body = null;
       if (raw) {
         try {
@@ -263,23 +279,34 @@
           throw new Error('Resposta inválida do servidor.');
         }
       }
-      const isSuccess = body?.status === 'success' || body?.status === 'ok' || body?.code === 'S_DELETED';
-      if (!res.ok || !isSuccess) {
-        throw new Error(body?.message ?? body?.msg ?? 'Falha ao excluir usuário.');
+
+      const isSuccess =
+        res.ok && body?.status === 'ok' && body?.code === 'S_OK' && body?.data?.deleted === true;
+
+      if (!isSuccess) {
+        throw new Error(body?.msg ?? body?.message ?? `Falha ao excluir usuário (HTTP ${res.status}).`);
       }
-      successMessage = body?.message ?? body?.msg ?? 'Usuário excluído com sucesso!';
+
+      deleted = true;
+      successMessage = body?.msg ?? body?.message ?? 'Usuário excluído com sucesso!';
       await loadUsers();
     } catch (e) {
       console.error('Erro ao excluir usuário:', e);
+      deleteError = e?.message ?? 'Falha ao excluir usuário.';
     } finally {
-      showDeleteConfirm = false;
-      rowToDelete = null;
+      deleteLoading = false;
+      if (deleted) {
+        showDeleteConfirm = false;
+        rowToDelete = null;
+      }
     }
   }
 
   function cancelDelete() {
     showDeleteConfirm = false;
     rowToDelete = null;
+    deleteError = '';
+    deleteLoading = false;
   }
 </script>
 
@@ -310,12 +337,17 @@
       <ConfirmModal
         visible={showDeleteConfirm}
         title="Confirmar exclusão"
-        confirmText="Excluir"
+        confirmText={deleteLoading ? 'Excluindo...' : 'Excluir'}
         cancelText="Cancelar"
+        disableBackdropClose={deleteLoading}
+        confirmDisabled={deleteLoading}
         on:confirm={confirmDelete}
         on:cancel={cancelDelete}
       >
         <p>Tem certeza que deseja excluir o usuário "{rowToDelete?.name}"?</p>
+        {#if deleteError}
+          <p class="mt-3 text-sm text-red-600 dark:text-red-400">{deleteError}</p>
+        {/if}
       </ConfirmModal>
 
       {#if showDetails}