|
|
@@ -2,8 +2,10 @@
|
|
|
import { computed, onMounted, ref } from "vue";
|
|
|
import LayoutSistema from "../../layout/LayoutSistema.vue";
|
|
|
import ModalUsuario from "../../components/ModalUsuario.vue";
|
|
|
+import ModalRedefinirSenha from "../../components/ModalRedefinirSenha.vue";
|
|
|
import { useUsers } from "../../composables/useUsers.js";
|
|
|
import { useToast } from "../../composables/useToast.js";
|
|
|
+import { listarSolicitacoesReset, dispensarSolicitacaoReset } from "../../api/users.js";
|
|
|
import BaseButton from "../../components/base/BaseButton.vue";
|
|
|
import BaseBadge from "../../components/base/BaseBadge.vue";
|
|
|
import { avatarIniciais, avatarCor } from "../../utils/avatar.js";
|
|
|
@@ -14,6 +16,15 @@ const toast = useToast();
|
|
|
const totalUsuarios = computed(() => users.value?.length ?? 0);
|
|
|
const modalAberto = ref(false);
|
|
|
const usuarioEmEdicao = ref(null);
|
|
|
+const modalSenhaAberto = ref(false);
|
|
|
+const usuarioRedefinicao = ref(null);
|
|
|
+const solicitacoes = ref([]);
|
|
|
+
|
|
|
+const solicitacaoPorUsuario = computed(() => {
|
|
|
+ const map = new Map();
|
|
|
+ for (const s of solicitacoes.value) map.set(s.UsuarioId, s);
|
|
|
+ return map;
|
|
|
+});
|
|
|
|
|
|
const busca = ref("");
|
|
|
const filtroStatus = ref("todos");
|
|
|
@@ -78,8 +89,57 @@ function aoSalvarUsuario() {
|
|
|
fecharModal();
|
|
|
}
|
|
|
|
|
|
+async function carregarSolicitacoes() {
|
|
|
+ try {
|
|
|
+ solicitacoes.value = await listarSolicitacoesReset();
|
|
|
+ } catch {
|
|
|
+ solicitacoes.value = [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function abrirRedefinicaoSenha(usuario) {
|
|
|
+ usuarioRedefinicao.value = usuario;
|
|
|
+ modalSenhaAberto.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+function fecharModalSenha() {
|
|
|
+ modalSenhaAberto.value = false;
|
|
|
+ usuarioRedefinicao.value = null;
|
|
|
+}
|
|
|
+
|
|
|
+async function aoRedefinirSenha() {
|
|
|
+ toast.sucesso("Senha redefinida com sucesso. Comunique o usuário.");
|
|
|
+ fecharModalSenha();
|
|
|
+ await carregarSolicitacoes();
|
|
|
+}
|
|
|
+
|
|
|
+async function dispensarSolicitacao(solicitacao) {
|
|
|
+ try {
|
|
|
+ await dispensarSolicitacaoReset(solicitacao.Id);
|
|
|
+ toast.sucesso("Solicitação dispensada.");
|
|
|
+ await carregarSolicitacoes();
|
|
|
+ } catch (e) {
|
|
|
+ toast.erro(e?.message || "Erro ao dispensar solicitação.");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function usuarioDaSolicitacao(solicitacao) {
|
|
|
+ return (
|
|
|
+ (users.value ?? []).find((u) => u.Id === solicitacao.UsuarioId) ??
|
|
|
+ solicitacao.usuario ??
|
|
|
+ { Id: solicitacao.UsuarioId }
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function formatarDataSolicitacao(valor) {
|
|
|
+ const data = new Date(valor);
|
|
|
+ return Number.isNaN(data.getTime())
|
|
|
+ ? ""
|
|
|
+ : data.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
|
+}
|
|
|
+
|
|
|
onMounted(async () => {
|
|
|
- await loadUsers();
|
|
|
+ await Promise.all([loadUsers(), carregarSolicitacoes()]);
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
@@ -111,7 +171,36 @@ onMounted(async () => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
-
|
|
|
+ <div
|
|
|
+ v-if="solicitacoes.length"
|
|
|
+ class="mt-4 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 dark:border-amber-500/20 dark:bg-amber-500/10"
|
|
|
+ >
|
|
|
+ <div class="text-sm font-semibold text-amber-800 dark:text-amber-300">
|
|
|
+ {{ solicitacoes.length }} solicitaç{{ solicitacoes.length === 1 ? "ão" : "ões" }} de redefinição de senha pendente{{ solicitacoes.length === 1 ? "" : "s" }}
|
|
|
+ </div>
|
|
|
+ <ul class="mt-2 grid gap-2">
|
|
|
+ <li
|
|
|
+ v-for="solicitacao in solicitacoes"
|
|
|
+ :key="solicitacao.Id"
|
|
|
+ class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"
|
|
|
+ >
|
|
|
+ <div class="text-sm text-amber-700 dark:text-amber-200">
|
|
|
+ <span class="font-medium">{{ solicitacao.usuario?.Nome || solicitacao.usuario?.Login || `Usuário #${solicitacao.UsuarioId}` }}</span>
|
|
|
+ <span v-if="solicitacao.usuario?.Login" class="text-amber-600/80 dark:text-amber-200/70"> ({{ solicitacao.usuario.Login }})</span>
|
|
|
+ <span class="text-amber-600/80 dark:text-amber-200/70"> — {{ formatarDataSolicitacao(solicitacao.CreatedAt) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex shrink-0 items-center gap-1.5">
|
|
|
+ <BaseButton size="sm" @click="abrirRedefinicaoSenha(usuarioDaSolicitacao(solicitacao))">
|
|
|
+ Redefinir senha
|
|
|
+ </BaseButton>
|
|
|
+ <BaseButton variant="ghost" size="sm" @click="dispensarSolicitacao(solicitacao)">
|
|
|
+ Dispensar
|
|
|
+ </BaseButton>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div class="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
|
|
|
<div class="relative flex-1">
|
|
|
<svg
|
|
|
@@ -205,6 +294,9 @@ onMounted(async () => {
|
|
|
<span class="font-medium text-gray-900 dark:text-gray-100">
|
|
|
{{ usuario.Nome || usuario.Login || "Usuário sem nome" }}
|
|
|
</span>
|
|
|
+ <BaseBadge v-if="solicitacaoPorUsuario.has(usuario.Id)" variant="warning">
|
|
|
+ Reset pendente
|
|
|
+ </BaseBadge>
|
|
|
</div>
|
|
|
</td>
|
|
|
|
|
|
@@ -252,6 +344,18 @@ onMounted(async () => {
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" />
|
|
|
</svg>
|
|
|
</BaseButton>
|
|
|
+ <BaseButton
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ class="!px-2"
|
|
|
+ title="Redefinir senha"
|
|
|
+ aria-label="Redefinir senha"
|
|
|
+ @click="abrirRedefinicaoSenha(usuario)"
|
|
|
+ >
|
|
|
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" />
|
|
|
+ </svg>
|
|
|
+ </BaseButton>
|
|
|
<BaseButton
|
|
|
:variant="String(usuario.Status) === '1' ? 'danger' : 'secondary'"
|
|
|
size="sm"
|
|
|
@@ -284,5 +388,12 @@ onMounted(async () => {
|
|
|
@close="fecharModal"
|
|
|
@saved="aoSalvarUsuario"
|
|
|
/>
|
|
|
+
|
|
|
+ <ModalRedefinirSenha
|
|
|
+ v-if="modalSenhaAberto && usuarioRedefinicao"
|
|
|
+ :usuario="usuarioRedefinicao"
|
|
|
+ @close="fecharModalSenha"
|
|
|
+ @saved="aoRedefinirSenha"
|
|
|
+ />
|
|
|
</LayoutSistema>
|
|
|
</template>
|