+page.svelte 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <script>
  2. import Header from '$lib/layout/Header.svelte';
  3. import Tables from '$lib/components/Tables.svelte';
  4. import UserCreateModal from '$lib/components/users/UserCreateModal.svelte';
  5. import ConfirmModal from '$lib/components/ui/PopUpDelete.svelte';
  6. import { authToken } from '$lib/utils/stores';
  7. import { onMount } from 'svelte';
  8. const apiUrl = import.meta.env.VITE_API_URL;
  9. const breadcrumb = [{ label: 'Início' }, { label: 'Usuários', active: true }];
  10. // Tabela
  11. let columns = [
  12. { key: 'name', label: 'Nome' },
  13. { key: 'email', label: 'E-mail' }
  14. ];
  15. let data = [];
  16. let loadError = '';
  17. let loadServerResponse = '';
  18. let successMessage = '';
  19. let isLoadingUsers = false;
  20. let createLoading = false;
  21. let createError = '';
  22. let createResetToken = 0;
  23. // Modal criar usuário
  24. let showCreate = false;
  25. // Confirmação de delete
  26. let showDeleteConfirm = false;
  27. let rowToDelete = null;
  28. let selectedUser = null;
  29. let showDetails = false;
  30. function handleAddTop() {
  31. showCreate = true;
  32. }
  33. onMount(loadUsers);
  34. async function loadUsers() {
  35. isLoadingUsers = true;
  36. try {
  37. loadError = '';
  38. loadServerResponse = '';
  39. const url = `${apiUrl}/user/get`;
  40. //console.log('fetch url:', url);
  41. const res = await fetch(url, {
  42. method: 'POST',
  43. headers: {
  44. 'content-type': 'application/json',
  45. ...( $authToken ? { Authorization: `Bearer ${$authToken}` } : {} )
  46. },
  47. body: JSON.stringify({})
  48. });
  49. const raw = await res.text();
  50. loadServerResponse = raw?.trim() ?? '';
  51. //console.log('response raw body:', raw);
  52. let body = null;
  53. if (raw) {
  54. try {
  55. body = JSON.parse(raw);
  56. } catch (err) {
  57. console.error('Resposta inválida do endpoint /users:', err);
  58. throw new Error('Resposta inválida do servidor.');
  59. }
  60. }
  61. if (!res.ok || body?.status !== 'ok') {
  62. throw new Error(body?.msg ?? 'Falha ao carregar usuários.');
  63. }
  64. const list = Array.isArray(body?.data) ? body.data : [];
  65. //console.log('parsed users length:', list.length);
  66. data = list.map((u) => ({
  67. __raw: u,
  68. name: u?.name ?? u?.user_name ?? u?.userName ?? u?.fullName ?? '-',
  69. email: u?.email ?? u?.user_email ?? u?.userEmail ?? '-'
  70. }));
  71. } catch (e) {
  72. //console.log('fetch users error:', e);
  73. loadError = e?.message ?? 'Falha ao carregar usuários.';
  74. data = [];
  75. } finally {
  76. isLoadingUsers = false;
  77. }
  78. }
  79. async function handleCreateSubmit(e) {
  80. const payload = e?.detail;
  81. if (!payload) return;
  82. createLoading = true;
  83. createError = '';
  84. try {
  85. const requestBody = {
  86. ...payload,
  87. role_id: payload?.role_id ?? 1
  88. };
  89. const res = await fetch(`${apiUrl}/register`, {
  90. method: 'POST',
  91. headers: {
  92. 'content-type': 'application/json',
  93. ...( $authToken ? { Authorization: `Bearer ${$authToken}` } : {} )
  94. },
  95. body: JSON.stringify(requestBody)
  96. });
  97. const raw = await res.text();
  98. //console.log('create user raw body:', raw);
  99. let body = null;
  100. if (raw) {
  101. try {
  102. body = JSON.parse(raw);
  103. } catch (err) {
  104. console.error('Resposta inválida do endpoint /register:', err);
  105. throw new Error('Resposta inválida do servidor.');
  106. }
  107. }
  108. const isSuccess = body?.status === 'success' || body?.status === 'ok' || body?.code === 'S_CREATED';
  109. if (!res.ok || !isSuccess) {
  110. throw new Error(body?.message ?? body?.msg ?? 'Falha ao criar usuário.');
  111. }
  112. successMessage = body?.message ?? body?.msg ?? 'Usuário criado com sucesso!';
  113. showCreate = false;
  114. createResetToken = Date.now();
  115. await loadUsers();
  116. } catch (err) {
  117. console.error('Erro na criação do usuário:', err);
  118. createError = err?.message ?? 'Falha ao criar usuário.';
  119. } finally {
  120. createLoading = false;
  121. }
  122. }
  123. function handleCreateCancel() {
  124. showCreate = false;
  125. createError = '';
  126. createResetToken = Date.now();
  127. }
  128. function handleDeleteRow(e) {
  129. const { row } = e?.detail || {};
  130. if (!row) return;
  131. rowToDelete = row;
  132. showDeleteConfirm = true;
  133. }
  134. function handleEditRow(e) {
  135. const { row } = e?.detail || {};
  136. if (!row) return;
  137. selectedUser = row.__raw ?? row;
  138. //console.log('clicked user details:', selectedUser);
  139. showDetails = true;
  140. }
  141. async function confirmDelete() {
  142. if (!rowToDelete) { showDeleteConfirm = false; return; }
  143. const userId =
  144. rowToDelete?.__raw?.user_id ??
  145. rowToDelete?.__raw?.userId ??
  146. rowToDelete?.__raw?.id ??
  147. rowToDelete?.user_id ??
  148. rowToDelete?.userId ??
  149. rowToDelete?.id;
  150. if (userId == null) {
  151. console.error('ID do usuário não encontrado para exclusão.');
  152. showDeleteConfirm = false;
  153. rowToDelete = null;
  154. return;
  155. }
  156. try {
  157. const res = await fetch(`${apiUrl}/user/delete`, {
  158. method: 'POST',
  159. headers: {
  160. 'content-type': 'application/json',
  161. ...( $authToken ? { Authorization: `Bearer ${$authToken}` } : {} )
  162. },
  163. body: JSON.stringify({ user_id: userId })
  164. });
  165. const raw = await res.text();
  166. //console.log('delete user raw body:', raw);
  167. let body = null;
  168. if (raw) {
  169. try {
  170. body = JSON.parse(raw);
  171. } catch (err) {
  172. console.error('Resposta inválida do endpoint /user/delete:', err);
  173. throw new Error('Resposta inválida do servidor.');
  174. }
  175. }
  176. const isSuccess = body?.status === 'success' || body?.status === 'ok' || body?.code === 'S_DELETED';
  177. if (!res.ok || !isSuccess) {
  178. throw new Error(body?.message ?? body?.msg ?? 'Falha ao excluir usuário.');
  179. }
  180. successMessage = body?.message ?? body?.msg ?? 'Usuário excluído com sucesso!';
  181. await loadUsers();
  182. } catch (e) {
  183. console.error('Erro ao excluir usuário:', e);
  184. } finally {
  185. showDeleteConfirm = false;
  186. rowToDelete = null;
  187. }
  188. }
  189. function cancelDelete() {
  190. showDeleteConfirm = false;
  191. rowToDelete = null;
  192. }
  193. </script>
  194. <div>
  195. <Header title="Usuários" subtitle="Gestão de usuários" breadcrumb={breadcrumb} />
  196. <div class="p-4">
  197. <div class="max-w-6xl mx-auto mt-4">
  198. <Tables
  199. title="Usuários"
  200. {columns}
  201. {data}
  202. on:addTop={handleAddTop}
  203. on:rowClick={handleEditRow}
  204. on:editRow={handleEditRow}
  205. on:deleteRow={handleDeleteRow}
  206. showEdit={false}
  207. />
  208. <UserCreateModal
  209. visible={showCreate}
  210. loading={createLoading}
  211. errorMessage={createError}
  212. resetToken={createResetToken}
  213. on:submit={handleCreateSubmit}
  214. on:cancel={handleCreateCancel}
  215. />
  216. <ConfirmModal
  217. visible={showDeleteConfirm}
  218. title="Confirmar exclusão"
  219. confirmText="Excluir"
  220. cancelText="Cancelar"
  221. on:confirm={confirmDelete}
  222. on:cancel={cancelDelete}
  223. >
  224. <p>Tem certeza que deseja excluir o usuário "{rowToDelete?.name}"?</p>
  225. </ConfirmModal>
  226. {#if showDetails}
  227. <div
  228. class="fixed inset-0 bg-black/40 flex items-center justify-center z-50"
  229. role="button"
  230. tabindex="0"
  231. on:click={(e) => {
  232. if (e.target === e.currentTarget) showDetails = false;
  233. }}
  234. on:keydown={(e) => {
  235. if (e.key === 'Escape' || e.key === 'Enter' || e.key === ' ') showDetails = false;
  236. }}
  237. >
  238. <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg w-full max-w-lg p-6" role="dialog" aria-modal="true">
  239. <div class="flex items-center justify-between mb-4">
  240. <h4 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Detalhes do usuário</h4>
  241. <button class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" on:click={() => showDetails = false}>✕</button>
  242. </div>
  243. {#if selectedUser}
  244. <div class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
  245. <div><span class="text-gray-500">ID:</span> <span class="dark:text-gray-100">{selectedUser.userId}</span></div>
  246. <div><span class="text-gray-500">Nome:</span> <span class="dark:text-gray-100">{selectedUser.userName}</span></div>
  247. <div><span class="text-gray-500">E-mail:</span> <span class="dark:text-gray-100">{selectedUser.userEmail}</span></div>
  248. <div><span class="text-gray-500">Telefone:</span> <span class="dark:text-gray-100">{selectedUser.userPhone}</span></div>
  249. <div><span class="text-gray-500">CPF:</span> <span class="dark:text-gray-100">{selectedUser.userCpf}</span></div>
  250. <div><span class="text-gray-500">Data Nasc.:</span> <span class="dark:text-gray-100">{selectedUser.userBirthdate}</span></div>
  251. <div><span class="text-gray-500">KYC:</span> <span class="dark:text-gray-100">{selectedUser.userKyc}</span></div>
  252. <div><span class="text-gray-500">Papel (roleId):</span> <span class="dark:text-gray-100">{selectedUser.roleId}</span></div>
  253. <div><span class="text-gray-500">Status:</span> <span class="dark:text-gray-100">{selectedUser.userFlag}</span></div>
  254. <div class="sm:col-span-2"><span class="text-gray-500">Endereço:</span> <span class="dark:text-gray-100">{selectedUser.userAddress}</span></div>
  255. <div><span class="text-gray-500">Cidade:</span> <span class="dark:text-gray-100">{selectedUser.userCity}</span></div>
  256. <div><span class="text-gray-500">Estado:</span> <span class="dark:text-gray-100">{selectedUser.userState}</span></div>
  257. <div><span class="text-gray-500">CEP:</span> <span class="dark:text-gray-100">{selectedUser.userZip}</span></div>
  258. <div><span class="text-gray-500">País:</span> <span class="dark:text-gray-100">{selectedUser.userCountry}</span></div>
  259. </div>
  260. {/if}
  261. <div class="mt-5 flex justify-end">
  262. <button class="px-4 py-2 rounded bg-blue-600 hover:bg-blue-700 text-white" on:click={() => showDetails = false}>Fechar</button>
  263. </div>
  264. </div>
  265. </div>
  266. {/if}
  267. </div>
  268. </div>
  269. </div>