atualizacoes modulo fechamento
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp";
|
||||
|
||||
export type AsanaWorkspaceUser = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type ApiErrorShape = {
|
||||
error?: {
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
|
||||
type AsanaWorkspaceUsersResponse = {
|
||||
data: Array<{
|
||||
gid: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
class FechamentoAsanaWorkspaceUsersService {
|
||||
private handleAxiosError(error: unknown, fallback: string): never {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
throw new Error(
|
||||
"Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
|
||||
);
|
||||
}
|
||||
const message = ((error.response.data as ApiErrorShape | undefined)?.error?.message ??
|
||||
error.message ??
|
||||
fallback) as string;
|
||||
throw new Error(message);
|
||||
}
|
||||
throw new Error(fallback);
|
||||
}
|
||||
|
||||
async listarUsuariosDoWorkspace(workspaceId: string): Promise<AsanaWorkspaceUser[]> {
|
||||
if (!workspaceId.trim()) {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.get<AsanaWorkspaceUsersResponse>(
|
||||
`${baseUrl}/asana/workspaces/${encodeURIComponent(workspaceId)}/users`,
|
||||
{ headers },
|
||||
);
|
||||
|
||||
return (response.data.data ?? []).map((item) => ({
|
||||
id: item.gid,
|
||||
name: item.name?.trim() || item.email?.trim() || item.gid,
|
||||
}));
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao listar usuários do workspace do Asana.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fechamentoAsanaWorkspaceUsersService = new FechamentoAsanaWorkspaceUsersService();
|
||||
@@ -0,0 +1,123 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp";
|
||||
|
||||
export type PapelUsuario = "admin" | "parceiro";
|
||||
|
||||
export type MeData = {
|
||||
id: string;
|
||||
nome: string;
|
||||
email: string;
|
||||
papel: PapelUsuario;
|
||||
parceiroId: string | null;
|
||||
estaAtivo: boolean;
|
||||
unidadeId: string;
|
||||
};
|
||||
|
||||
export type BootstrapStatusData = {
|
||||
requiresBootstrap: boolean;
|
||||
estabelecimentoId: string;
|
||||
unidade: { id: string; nome: string; estabelecimentoId: string } | null;
|
||||
};
|
||||
|
||||
export type BootstrapInitInput = {
|
||||
estabelecimentoId: string;
|
||||
unidadeNome: string;
|
||||
adminNome: string;
|
||||
adminEmail: string;
|
||||
};
|
||||
|
||||
type MeResponse = {
|
||||
success: boolean;
|
||||
data: MeData;
|
||||
};
|
||||
|
||||
type BootstrapStatusResponse = {
|
||||
success: boolean;
|
||||
data: BootstrapStatusData;
|
||||
};
|
||||
|
||||
type BootstrapInitResponse = {
|
||||
success: boolean;
|
||||
data: {
|
||||
unidadeId: string;
|
||||
adminId: string;
|
||||
};
|
||||
};
|
||||
|
||||
type ApiErrorShape = {
|
||||
status?: number;
|
||||
data?: {
|
||||
error?: {
|
||||
code?: string;
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
message?: string;
|
||||
};
|
||||
|
||||
function isApiError(value: unknown): value is ApiErrorShape {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
class AuthMeService {
|
||||
async getMe(usuarioEmail: string): Promise<MeData | null> {
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const headers = await buildCommanderHeaders({ omitUnidadeId: true });
|
||||
|
||||
try {
|
||||
const response = await axios.get<MeResponse>(`${baseUrl}/me`, {
|
||||
params: { usuarioEmail },
|
||||
headers,
|
||||
});
|
||||
return response.data.data;
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const status = error.response?.status;
|
||||
const code = (error.response?.data as ApiErrorShape["data"] | undefined)?.error?.code;
|
||||
if (status === 404 || code === "USUARIO_NAO_CADASTRADO") {
|
||||
return null;
|
||||
}
|
||||
if (!error.response) {
|
||||
throw new Error(
|
||||
"Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
|
||||
);
|
||||
}
|
||||
const message =
|
||||
(error.response?.data as ApiErrorShape["data"] | undefined)?.error?.message ?? error.message;
|
||||
throw new Error(message || "Erro ao consultar acesso do usuário.");
|
||||
}
|
||||
if (isApiError(error)) {
|
||||
const status = error.status;
|
||||
const code = error.data?.error?.code;
|
||||
if (status === 404 || code === "USUARIO_NAO_CADASTRADO") {
|
||||
return null;
|
||||
}
|
||||
const message = error.data?.error?.message ?? error.message;
|
||||
throw new Error(message || "Erro ao consultar acesso do usuário.");
|
||||
}
|
||||
throw new Error("Erro ao consultar acesso do usuário.");
|
||||
}
|
||||
}
|
||||
|
||||
async getBootstrapStatus(estabelecimentoId: string): Promise<BootstrapStatusData> {
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const headers = await buildCommanderHeaders({ omitUnidadeId: true });
|
||||
const response = await axios.get<BootstrapStatusResponse>(`${baseUrl}/bootstrap-status`, {
|
||||
headers,
|
||||
params: { estabelecimentoId },
|
||||
});
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
async bootstrapInitialize(input: BootstrapInitInput): Promise<{ unidadeId: string; adminId: string }> {
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const headers = await buildCommanderHeaders({ omitUnidadeId: true });
|
||||
const response = await axios.post<BootstrapInitResponse>(`${baseUrl}/bootstrap`, input, {
|
||||
headers,
|
||||
});
|
||||
return response.data.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const authMeService = new AuthMeService();
|
||||
@@ -0,0 +1,27 @@
|
||||
import { GlobalFunctions } from "@/GlobalFunctions";
|
||||
|
||||
import { resolveCommanderUnidadeId } from "./unidadeContext";
|
||||
import { resolveCommanderBaseUrl } from "./fechamentoBaseUrl";
|
||||
|
||||
export { resolveCommanderBaseUrl } from "./fechamentoBaseUrl";
|
||||
|
||||
export type BuildCommanderHeadersOptions = {
|
||||
/** Rotas allowlisted no Commander (ex.: `/me`, lookup de `/unidades`) não devem enviar `X-Unidade-Id`. */
|
||||
omitUnidadeId?: boolean;
|
||||
};
|
||||
|
||||
export async function buildCommanderHeaders(
|
||||
opts?: BuildCommanderHeadersOptions,
|
||||
): Promise<Record<string, string>> {
|
||||
const token = await GlobalFunctions.getToken();
|
||||
const apiKey = import.meta.env.VITE_API_KEY || "";
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
...(apiKey ? { apikey: apiKey } : {}),
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
};
|
||||
if (!opts?.omitUnidadeId) {
|
||||
headers["X-Unidade-Id"] = await resolveCommanderUnidadeId();
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp";
|
||||
|
||||
export type CompetenciaStatus = "em_aberto" | "concluido";
|
||||
|
||||
export type CompetenciaItem = {
|
||||
id: string;
|
||||
mes: number;
|
||||
ano: number;
|
||||
status: CompetenciaStatus;
|
||||
quantidadeFechamentos: number;
|
||||
criadoEm: string;
|
||||
atualizadoEm: string;
|
||||
};
|
||||
|
||||
export type FechamentoDaCompetenciaItem = {
|
||||
id: string;
|
||||
competenciaId: string;
|
||||
parceiroId: string;
|
||||
parceiroNome: string;
|
||||
parceiroCodinome: string | null;
|
||||
parceiroLogoUrl: string | null;
|
||||
horasTotal: number;
|
||||
status: "em_aberto" | "fechado";
|
||||
pontuacaoTotalEntregue: number;
|
||||
pontuacaoMeta: number;
|
||||
pontuacaoPaga: number;
|
||||
pontuacaoBanco: number;
|
||||
exportadoFinanceiro: boolean;
|
||||
fechadoEm: string | null;
|
||||
criadoEm: string;
|
||||
atualizadoEm: string;
|
||||
};
|
||||
|
||||
type ListarCompetenciasResponse = {
|
||||
data: CompetenciaItem[];
|
||||
};
|
||||
|
||||
type ListarFechamentosDaCompetenciaResponse = {
|
||||
data: FechamentoDaCompetenciaItem[];
|
||||
};
|
||||
|
||||
type CriarCompetenciaResponse = {
|
||||
data: CompetenciaItem;
|
||||
};
|
||||
|
||||
type ImportarCompetenciaResponse = {
|
||||
data: {
|
||||
competenciaId: string;
|
||||
tarefasRecebidas: number;
|
||||
tarefasImportadas: number;
|
||||
fechamentosCriados: number;
|
||||
tarefasIgnoradasSemParceiro: number;
|
||||
chamadasSubtarefasAsana: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type ImportacaoAsanaModo =
|
||||
| "reprocessar_tudo"
|
||||
| "reprocessar_alguns"
|
||||
| "buscar_novos_fechamentos";
|
||||
|
||||
type ApiErrorShape = {
|
||||
error?: {
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
|
||||
class FechamentoCompetenciasService {
|
||||
private handleAxiosError(error: unknown, fallback: string): never {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
throw new Error(
|
||||
"Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
|
||||
);
|
||||
}
|
||||
const message = ((error.response.data as ApiErrorShape | undefined)?.error?.message ??
|
||||
error.message ??
|
||||
fallback) as string;
|
||||
throw new Error(message);
|
||||
}
|
||||
throw new Error(fallback);
|
||||
}
|
||||
|
||||
async listarCompetencias(params: { ano?: number; status?: CompetenciaStatus }): Promise<CompetenciaItem[]> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.get<ListarCompetenciasResponse>(`${baseUrl}/competencias`, {
|
||||
headers,
|
||||
params: {
|
||||
ano: params.ano,
|
||||
status: params.status,
|
||||
},
|
||||
});
|
||||
return response.data.data ?? [];
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao listar competências.");
|
||||
}
|
||||
}
|
||||
|
||||
async listarFechamentosDaCompetencia(competenciaId: string): Promise<FechamentoDaCompetenciaItem[]> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.get<ListarFechamentosDaCompetenciaResponse>(
|
||||
`${baseUrl}/competencias/${competenciaId}/fechamentos`,
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data ?? [];
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao listar fechamentos da competência.");
|
||||
}
|
||||
}
|
||||
|
||||
async criarCompetencia(input: { mes: number; ano: number }): Promise<CompetenciaItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<CriarCompetenciaResponse>(
|
||||
`${baseUrl}/competencias`,
|
||||
{ mes: input.mes, ano: input.ano },
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao criar competência.");
|
||||
}
|
||||
}
|
||||
|
||||
async importarDoAsana(
|
||||
competenciaId: string,
|
||||
opcoes?: { modo?: ImportacaoAsanaModo; parceiroIds?: string[] },
|
||||
): Promise<{ tarefasRecebidas: number; tarefasImportadas: number; fechamentosCriados: number; tarefasIgnoradasSemParceiro: number }> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<ImportarCompetenciaResponse>(
|
||||
`${baseUrl}/competencias/${competenciaId}/importar`,
|
||||
{
|
||||
modo: opcoes?.modo ?? "reprocessar_tudo",
|
||||
parceiroIds: opcoes?.parceiroIds,
|
||||
},
|
||||
{ headers },
|
||||
);
|
||||
return {
|
||||
tarefasRecebidas: response.data.data.tarefasRecebidas,
|
||||
tarefasImportadas: response.data.data.tarefasImportadas,
|
||||
fechamentosCriados: response.data.data.fechamentosCriados,
|
||||
tarefasIgnoradasSemParceiro: response.data.data.tarefasIgnoradasSemParceiro,
|
||||
};
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao importar tasks do Asana.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fechamentoCompetenciasService = new FechamentoCompetenciasService();
|
||||
@@ -0,0 +1,83 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp";
|
||||
|
||||
export type ConfiguracaoPublica = {
|
||||
id: string;
|
||||
asanaWorkspaceId: string | null;
|
||||
asanaWorkspaceNome: string | null;
|
||||
asanaToken: string | null;
|
||||
asanaTokenConfigured: boolean;
|
||||
atualizadoEm: string;
|
||||
atualizadoPorId: string | null;
|
||||
};
|
||||
|
||||
type GetConfiguracoesResponse = {
|
||||
data: ConfiguracaoPublica | null;
|
||||
};
|
||||
|
||||
type SalvarConfiguracoesBody = {
|
||||
asanaToken?: string;
|
||||
asanaWorkspaceId?: string | null;
|
||||
asanaWorkspaceNome?: string | null;
|
||||
};
|
||||
|
||||
type SalvarConfiguracoesResponse = {
|
||||
data: ConfiguracaoPublica;
|
||||
};
|
||||
|
||||
class FechamentoConfiguracoesService {
|
||||
async getConfiguracoes(): Promise<ConfiguracaoPublica | null> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.get<GetConfiguracoesResponse>(`${baseUrl}/configuracoes`, {
|
||||
headers,
|
||||
});
|
||||
return response.data.data;
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
throw new Error(
|
||||
"Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
|
||||
);
|
||||
}
|
||||
const message =
|
||||
((error.response.data as { error?: { message?: string } } | undefined)?.error?.message ??
|
||||
error.message) ||
|
||||
"Erro ao carregar configurações.";
|
||||
throw new Error(message);
|
||||
}
|
||||
throw new Error("Erro ao carregar configurações.");
|
||||
}
|
||||
}
|
||||
|
||||
async salvarConfiguracoes(body: SalvarConfiguracoesBody): Promise<ConfiguracaoPublica> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<SalvarConfiguracoesResponse>(
|
||||
`${baseUrl}/configuracoes`,
|
||||
body,
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error: unknown) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
throw new Error(
|
||||
"Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
|
||||
);
|
||||
}
|
||||
const message =
|
||||
((error.response.data as { error?: { message?: string } } | undefined)?.error?.message ??
|
||||
error.message) ||
|
||||
"Erro ao salvar configurações.";
|
||||
throw new Error(message);
|
||||
}
|
||||
throw new Error("Erro ao salvar configurações.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fechamentoConfiguracoesService = new FechamentoConfiguracoesService();
|
||||
@@ -0,0 +1,9 @@
|
||||
export function resolveCommanderBaseUrl(): string {
|
||||
const raw = import.meta.env.VITE_API_BASE_URL_COMMANDER?.trim();
|
||||
if (!raw) {
|
||||
throw new Error(
|
||||
"A variável VITE_API_BASE_URL_COMMANDER não está configurada. Defina, por exemplo, http://localhost:3333/api/",
|
||||
);
|
||||
}
|
||||
return raw.endsWith("/") ? raw.slice(0, -1) : raw;
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp";
|
||||
|
||||
export type FechamentoTarefaItem = {
|
||||
id: string;
|
||||
fechamentoId: string;
|
||||
tipo: string;
|
||||
numeroTicket: string | null;
|
||||
descricao: string;
|
||||
cliente: string | null;
|
||||
linkAsana: string | null;
|
||||
tempoMinutos: number | null;
|
||||
pontuacao: number;
|
||||
pontuacaoOriginal: number;
|
||||
estaRevisada: boolean;
|
||||
dataConclusao: string | null;
|
||||
editadoEm: string | null;
|
||||
editadoPorId: string | null;
|
||||
criadoEm: string;
|
||||
};
|
||||
|
||||
type ListarTarefasResponse = {
|
||||
data: FechamentoTarefaItem[];
|
||||
};
|
||||
|
||||
type PatchTarefaResponse = {
|
||||
data: FechamentoTarefaItem;
|
||||
};
|
||||
|
||||
type CriarLancamentoResponse = {
|
||||
data: FechamentoTarefaItem;
|
||||
};
|
||||
|
||||
export type FechamentoFechadoResponse = {
|
||||
id: string;
|
||||
competenciaId: string;
|
||||
parceiroId: string;
|
||||
status: "fechado";
|
||||
versao: number;
|
||||
pontuacaoTotalEntregue: number;
|
||||
pontuacaoMeta: number;
|
||||
pontuacaoPaga: number;
|
||||
pontuacaoBanco: number;
|
||||
exportadoFinanceiro: boolean;
|
||||
fechadoPorId: string | null;
|
||||
fechadoEm: string | null;
|
||||
criadoEm: string;
|
||||
atualizadoEm: string;
|
||||
};
|
||||
|
||||
export type FechamentoReabertoResponse = {
|
||||
id: string;
|
||||
competenciaId: string;
|
||||
parceiroId: string;
|
||||
status: "em_aberto";
|
||||
versao: number;
|
||||
pontuacaoTotalEntregue: number;
|
||||
pontuacaoMeta: number;
|
||||
pontuacaoPaga: number;
|
||||
pontuacaoBanco: number;
|
||||
exportadoFinanceiro: boolean;
|
||||
fechadoPorId: string | null;
|
||||
fechadoEm: string | null;
|
||||
criadoEm: string;
|
||||
atualizadoEm: string;
|
||||
};
|
||||
|
||||
type ConcluirFechamentoResponse = {
|
||||
data: FechamentoFechadoResponse;
|
||||
};
|
||||
|
||||
type ReabrirFechamentoResponse = {
|
||||
data: FechamentoReabertoResponse;
|
||||
};
|
||||
|
||||
type ReprocessarAsanaResponse = {
|
||||
data: {
|
||||
fechamentoId: string;
|
||||
tarefasRecebidas: number;
|
||||
tarefasImportadas: number;
|
||||
tarefasIgnoradasSemParceiro: number;
|
||||
chamadasSubtarefasAsana: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type ExportarPlanilhaResponse = {
|
||||
buffer: ArrayBuffer;
|
||||
filename: string | null;
|
||||
};
|
||||
|
||||
type ApiErrorShape = {
|
||||
error?: {
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
|
||||
class FechamentoFechamentosService {
|
||||
private extractFilenameFromContentDisposition(value: string | undefined): string | null {
|
||||
if (!value) return null;
|
||||
const utf8Match = value.match(/filename\*=UTF-8''([^;]+)/i);
|
||||
if (utf8Match?.[1]) {
|
||||
try {
|
||||
return decodeURIComponent(utf8Match[1]);
|
||||
} catch {
|
||||
return utf8Match[1];
|
||||
}
|
||||
}
|
||||
const regularMatch = value.match(/filename="?([^";]+)"?/i);
|
||||
return regularMatch?.[1] ?? null;
|
||||
}
|
||||
|
||||
private handleAxiosError(error: unknown, fallback: string): never {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
throw new Error(
|
||||
"Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
|
||||
);
|
||||
}
|
||||
const message = ((error.response.data as ApiErrorShape | undefined)?.error?.message ??
|
||||
error.message ??
|
||||
fallback) as string;
|
||||
throw new Error(message);
|
||||
}
|
||||
throw new Error(fallback);
|
||||
}
|
||||
|
||||
async listarTarefas(fechamentoId: string): Promise<FechamentoTarefaItem[]> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.get<ListarTarefasResponse>(`${baseUrl}/fechamentos/${fechamentoId}/tarefas`, {
|
||||
headers,
|
||||
});
|
||||
return response.data.data ?? [];
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao listar tarefas do fechamento.");
|
||||
}
|
||||
}
|
||||
|
||||
async patchTarefa(
|
||||
fechamentoId: string,
|
||||
tarefaId: string,
|
||||
input: {
|
||||
estaRevisada?: boolean;
|
||||
pontuacao?: number;
|
||||
numeroTicket?: string | null;
|
||||
descricao?: string;
|
||||
cliente?: string | null;
|
||||
tempoMinutos?: number | null;
|
||||
editadoPorId: string;
|
||||
},
|
||||
): Promise<FechamentoTarefaItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.patch<PatchTarefaResponse>(
|
||||
`${baseUrl}/fechamentos/${fechamentoId}/tarefas/${tarefaId}`,
|
||||
{
|
||||
...(input.estaRevisada !== undefined ? { esta_revisada: input.estaRevisada } : {}),
|
||||
...(input.pontuacao !== undefined ? { pontuacao: input.pontuacao } : {}),
|
||||
...(input.numeroTicket !== undefined ? { numero_ticket: input.numeroTicket } : {}),
|
||||
...(input.descricao !== undefined ? { descricao: input.descricao } : {}),
|
||||
...(input.cliente !== undefined ? { cliente: input.cliente } : {}),
|
||||
...(input.tempoMinutos !== undefined ? { tempo_minutos: input.tempoMinutos } : {}),
|
||||
editado_por_id: input.editadoPorId,
|
||||
},
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao atualizar aprovação da tarefa.");
|
||||
}
|
||||
}
|
||||
|
||||
async criarLancamento(
|
||||
fechamentoId: string,
|
||||
input: { tipo: "bonus" | "desconto"; descricao: string; pontuacao: number },
|
||||
): Promise<FechamentoTarefaItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<CriarLancamentoResponse>(
|
||||
`${baseUrl}/fechamentos/${fechamentoId}/tarefas`,
|
||||
{
|
||||
tipo: input.tipo,
|
||||
descricao: input.descricao,
|
||||
pontuacao: input.pontuacao,
|
||||
},
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao criar lançamento no fechamento.");
|
||||
}
|
||||
}
|
||||
|
||||
async excluirLancamento(fechamentoId: string, tarefaId: string): Promise<FechamentoTarefaItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.delete<PatchTarefaResponse>(
|
||||
`${baseUrl}/fechamentos/${fechamentoId}/tarefas/${tarefaId}`,
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao excluir lançamento.");
|
||||
}
|
||||
}
|
||||
|
||||
async concluirFechamento(
|
||||
fechamentoId: string,
|
||||
input: { pontuacaoPaga: number; fechadoPorId?: string; motivoAjuste?: string },
|
||||
): Promise<FechamentoFechadoResponse> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<ConcluirFechamentoResponse>(
|
||||
`${baseUrl}/fechamentos/${fechamentoId}/concluir`,
|
||||
{
|
||||
pontuacao_paga: input.pontuacaoPaga,
|
||||
...(input.fechadoPorId ? { fechado_por_id: input.fechadoPorId } : {}),
|
||||
...(input.motivoAjuste?.trim() ? { motivo_ajuste: input.motivoAjuste.trim() } : {}),
|
||||
},
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao concluir fechamento.");
|
||||
}
|
||||
}
|
||||
|
||||
async exportarPlanilha(fechamentoId: string): Promise<ExportarPlanilhaResponse> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.get<ArrayBuffer>(`${baseUrl}/fechamentos/${fechamentoId}/exportar`, {
|
||||
headers,
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
return {
|
||||
buffer: response.data,
|
||||
filename: this.extractFilenameFromContentDisposition(response.headers["content-disposition"]),
|
||||
};
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao exportar planilha do fechamento.");
|
||||
}
|
||||
}
|
||||
|
||||
async reabrirFechamento(
|
||||
fechamentoId: string,
|
||||
input: { reabertoPorId: string; motivo?: string },
|
||||
): Promise<FechamentoReabertoResponse> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<ReabrirFechamentoResponse>(
|
||||
`${baseUrl}/fechamentos/${fechamentoId}/reabrir`,
|
||||
{
|
||||
reaberto_por_id: input.reabertoPorId,
|
||||
...(input.motivo?.trim() ? { motivo: input.motivo.trim() } : {}),
|
||||
},
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao reabrir fechamento.");
|
||||
}
|
||||
}
|
||||
|
||||
async reprocessarAsana(fechamentoId: string): Promise<ReprocessarAsanaResponse["data"]> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<ReprocessarAsanaResponse>(
|
||||
`${baseUrl}/fechamentos/${fechamentoId}/reprocessar-asana`,
|
||||
{},
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao reprocessar tarefas do Asana.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fechamentoFechamentosService = new FechamentoFechamentosService();
|
||||
@@ -0,0 +1,157 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp";
|
||||
|
||||
export type ParceiroTipoPessoa = "fisica" | "juridica";
|
||||
export type ParceiroStatusFiltro = "all" | "true" | "false";
|
||||
|
||||
export type ParceiroItem = {
|
||||
id: string;
|
||||
nome: string;
|
||||
codinome: string | null;
|
||||
tipoPessoa: ParceiroTipoPessoa;
|
||||
cpf: string | null;
|
||||
cnpj: string | null;
|
||||
email: string;
|
||||
whatsapp: string | null;
|
||||
asanaId: string | null;
|
||||
logoUrl: string | null;
|
||||
observacoes: string | null;
|
||||
pontuacaoMeta: number;
|
||||
estaAtivo: boolean;
|
||||
criadoEm: string;
|
||||
atualizadoEm: string;
|
||||
};
|
||||
|
||||
type MetaResponse = {
|
||||
total: number;
|
||||
paginaAtual: number;
|
||||
totalPaginas: number;
|
||||
};
|
||||
|
||||
export type ListarParceirosResponse = {
|
||||
data: ParceiroItem[];
|
||||
meta: MetaResponse;
|
||||
};
|
||||
|
||||
type ApiErrorShape = {
|
||||
error?: {
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ListarParceirosParams = {
|
||||
busca?: string;
|
||||
estaAtivo?: ParceiroStatusFiltro;
|
||||
page: number;
|
||||
perPage: number;
|
||||
};
|
||||
|
||||
export type SalvarParceiroPayload = {
|
||||
nome: string;
|
||||
codinome?: string | null;
|
||||
tipoPessoa: ParceiroTipoPessoa;
|
||||
cpf?: string | null;
|
||||
cnpj?: string | null;
|
||||
email: string;
|
||||
whatsapp?: string | null;
|
||||
asanaId?: string | null;
|
||||
logoUrl?: string | null;
|
||||
observacoes?: string | null;
|
||||
pontuacaoMeta: number;
|
||||
estaAtivo?: boolean;
|
||||
};
|
||||
|
||||
type SalvarParceiroResponse = {
|
||||
data: ParceiroItem;
|
||||
};
|
||||
|
||||
class FechamentoParceirosService {
|
||||
private handleAxiosError(error: unknown, fallback: string): never {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
throw new Error(
|
||||
"Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
|
||||
);
|
||||
}
|
||||
const message = ((error.response.data as ApiErrorShape | undefined)?.error?.message ??
|
||||
error.message ??
|
||||
fallback) as string;
|
||||
throw new Error(message);
|
||||
}
|
||||
throw new Error(fallback);
|
||||
}
|
||||
|
||||
async listarParceiros(params: ListarParceirosParams): Promise<ListarParceirosResponse> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.get<ListarParceirosResponse>(`${baseUrl}/parceiros`, {
|
||||
headers,
|
||||
params: {
|
||||
busca: params.busca || undefined,
|
||||
estaAtivo: params.estaAtivo ?? "all",
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao listar parceiros.");
|
||||
}
|
||||
}
|
||||
|
||||
async listarParceirosAtivos(): Promise<ParceiroItem[]> {
|
||||
const response = await this.listarParceiros({
|
||||
estaAtivo: "true",
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
});
|
||||
return response.data ?? [];
|
||||
}
|
||||
|
||||
async criarParceiro(payload: SalvarParceiroPayload): Promise<ParceiroItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<SalvarParceiroResponse>(`${baseUrl}/parceiros`, payload, {
|
||||
headers,
|
||||
});
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao criar parceiro.");
|
||||
}
|
||||
}
|
||||
|
||||
async editarParceiro(id: string, payload: Partial<SalvarParceiroPayload>): Promise<ParceiroItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.patch<SalvarParceiroResponse>(`${baseUrl}/parceiros/${id}`, payload, {
|
||||
headers,
|
||||
});
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao editar parceiro.");
|
||||
}
|
||||
}
|
||||
|
||||
async toggleAtivoParceiro(id: string): Promise<ParceiroItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.patch<SalvarParceiroResponse>(
|
||||
`${baseUrl}/parceiros/${id}/toggle-ativo`,
|
||||
{},
|
||||
{
|
||||
headers,
|
||||
},
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao atualizar status do parceiro.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fechamentoParceirosService = new FechamentoParceirosService();
|
||||
@@ -0,0 +1,106 @@
|
||||
import axios from "axios";
|
||||
import { GlobalFunctions, TransferAreaProperties } from "@/GlobalFunctions";
|
||||
|
||||
import { resolveCommanderBaseUrl } from "./fechamentoBaseUrl";
|
||||
|
||||
export type UnidadeLookupRow = {
|
||||
id: string;
|
||||
nome: string;
|
||||
estabelecimentoId: string;
|
||||
};
|
||||
|
||||
const cacheByCodigo = new Map<string, UnidadeLookupRow>();
|
||||
|
||||
type UnidadesListResponse = {
|
||||
data: Array<{
|
||||
id: string;
|
||||
nome: string;
|
||||
estabelecimentoId: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
async function buildLookupHeaders(): Promise<Record<string, string>> {
|
||||
const token = await GlobalFunctions.getToken();
|
||||
const apiKey = import.meta.env.VITE_API_KEY || "";
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
...(apiKey ? { apikey: apiKey } : {}),
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function clearCommanderUnidadeIdCache(): void {
|
||||
cacheByCodigo.clear();
|
||||
}
|
||||
|
||||
/** Código de estabelecimento vindo do Transfer (host), ou string vazia. */
|
||||
export function getEstabelecimentoCodigoFromTransfer(): string {
|
||||
const raw = GlobalFunctions.getTransferProperty(TransferAreaProperties.EstabelecimentoCodigo);
|
||||
const codigo =
|
||||
typeof raw === "string" ? raw.trim() : raw != null && String(raw).trim() !== "" ? String(raw).trim() : "";
|
||||
return codigo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consulta `GET /unidades?estabelecimentoId=…` sem lançar; retorna `null` se não houver linha.
|
||||
* Resultado é cacheado por código.
|
||||
*/
|
||||
export async function lookupUnidadeByEstabelecimento(codigo: string): Promise<UnidadeLookupRow | null> {
|
||||
const key = codigo.trim();
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cached = cacheByCodigo.get(key);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const headers = await buildLookupHeaders();
|
||||
const response = await axios.get<UnidadesListResponse>(`${baseUrl}/unidades`, {
|
||||
headers,
|
||||
params: {
|
||||
estabelecimentoId: key,
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const row =
|
||||
response.data.data?.find((u) => u.estabelecimentoId === key) ?? response.data.data?.[0];
|
||||
if (!row?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mapped: UnidadeLookupRow = {
|
||||
id: row.id,
|
||||
nome: row.nome,
|
||||
estabelecimentoId: row.estabelecimentoId,
|
||||
};
|
||||
cacheByCodigo.set(key, mapped);
|
||||
return mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve o UUID da unidade no Commander a partir do código de estabelecimento
|
||||
* disponibilizado pelo host (mesmo padrão de `audioGeneration.ts` / Codex).
|
||||
* Lança se o código estiver ausente ou não existir unidade cadastrada.
|
||||
*/
|
||||
export async function resolveCommanderUnidadeId(): Promise<string> {
|
||||
const codigo = getEstabelecimentoCodigoFromTransfer();
|
||||
if (!codigo) {
|
||||
throw new Error(
|
||||
"EstabelecimentoCodigo não disponível (TransferArea). Não foi possível resolver a unidade para o Commander.",
|
||||
);
|
||||
}
|
||||
|
||||
const row = await lookupUnidadeByEstabelecimento(codigo);
|
||||
if (!row) {
|
||||
throw new Error(
|
||||
`Nenhuma unidade encontrada no Commander para o estabelecimento "${codigo}". Cadastre a unidade ou verifique o código.`,
|
||||
);
|
||||
}
|
||||
|
||||
return row.id;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import axios from "axios";
|
||||
import { GlobalFunctions } from "@/GlobalFunctions";
|
||||
|
||||
import { buildCommanderHeaders } from "./commanderHttp";
|
||||
import { resolveCommanderBaseUrl } from "./fechamentoBaseUrl";
|
||||
import { clearCommanderUnidadeIdCache, type UnidadeLookupRow } from "./unidadeContext";
|
||||
|
||||
async function buildLookupHeaders(): Promise<Record<string, string>> {
|
||||
const token = await GlobalFunctions.getToken();
|
||||
const apiKey = import.meta.env.VITE_API_KEY || "";
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
...(apiKey ? { apikey: apiKey } : {}),
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function getUsuarioEmailParaCommander(): string {
|
||||
const email = GlobalFunctions.getUsuarioLogado().email?.trim();
|
||||
if (!email) {
|
||||
throw new Error("E-mail do usuário logado não disponível para cadastro de unidade.");
|
||||
}
|
||||
return email;
|
||||
}
|
||||
|
||||
export type CriarUnidadeInput = {
|
||||
nome: string;
|
||||
estabelecimentoId: string;
|
||||
};
|
||||
|
||||
export type AtualizarUnidadeInput = {
|
||||
nome?: string;
|
||||
estabelecimentoId?: string;
|
||||
};
|
||||
|
||||
type UnidadeResponse = { data: UnidadeLookupRow & { criadoEm: string } };
|
||||
|
||||
class FechamentoUnidadesService {
|
||||
/**
|
||||
* `POST /unidades` sem `X-Unidade-Id`. Exige `usuarioEmail` na query (validação admin no Commander).
|
||||
*/
|
||||
async createUnidade(input: CriarUnidadeInput): Promise<UnidadeLookupRow & { criadoEm: string }> {
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const headers = await buildLookupHeaders();
|
||||
const usuarioEmail = getUsuarioEmailParaCommander();
|
||||
const response = await axios.post<UnidadeResponse>(
|
||||
`${baseUrl}/unidades`,
|
||||
{
|
||||
nome: input.nome.trim(),
|
||||
estabelecimentoId: input.estabelecimentoId.trim(),
|
||||
},
|
||||
{
|
||||
headers,
|
||||
params: { usuarioEmail },
|
||||
},
|
||||
);
|
||||
clearCommanderUnidadeIdCache();
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
async updateUnidade(
|
||||
unidadeId: string,
|
||||
input: AtualizarUnidadeInput,
|
||||
): Promise<UnidadeLookupRow & { criadoEm: string }> {
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const headers = await buildCommanderHeaders();
|
||||
const body: Record<string, string> = {};
|
||||
if (input.nome !== undefined) body.nome = input.nome.trim();
|
||||
if (input.estabelecimentoId !== undefined) body.estabelecimentoId = input.estabelecimentoId.trim();
|
||||
|
||||
const response = await axios.patch<UnidadeResponse>(`${baseUrl}/unidades/${unidadeId}`, body, {
|
||||
headers,
|
||||
});
|
||||
clearCommanderUnidadeIdCache();
|
||||
return response.data.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const fechamentoUnidadesService = new FechamentoUnidadesService();
|
||||
@@ -0,0 +1,72 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp";
|
||||
|
||||
type ApiErrorShape = {
|
||||
error?: {
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
|
||||
type PresignParceiroLogoResponse = {
|
||||
data: {
|
||||
uploadUrl: string;
|
||||
publicUrl: string;
|
||||
objectKey: string;
|
||||
expiresIn: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type PresignParceiroLogoPayload = {
|
||||
fileName: string;
|
||||
contentType: "image/jpeg" | "image/png" | "image/webp";
|
||||
};
|
||||
|
||||
class FechamentoUploadsService {
|
||||
private handleAxiosError(error: unknown, fallback: string): never {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
throw new Error(
|
||||
"Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
|
||||
);
|
||||
}
|
||||
const message = ((error.response.data as ApiErrorShape | undefined)?.error?.message ??
|
||||
error.message ??
|
||||
fallback) as string;
|
||||
throw new Error(message);
|
||||
}
|
||||
throw new Error(fallback);
|
||||
}
|
||||
|
||||
async presignUploadParceiroLogo(payload: PresignParceiroLogoPayload) {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<PresignParceiroLogoResponse>(
|
||||
`${baseUrl}/uploads/parceiros/logo/presign`,
|
||||
payload,
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao preparar upload da logo.");
|
||||
}
|
||||
}
|
||||
|
||||
async uploadFileToSignedUrl(uploadUrl: string, file: File): Promise<void> {
|
||||
try {
|
||||
await axios.put(uploadUrl, file, {
|
||||
headers: {
|
||||
"Content-Type": file.type,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
throw new Error(error.message || "Falha ao enviar arquivo para armazenamento.");
|
||||
}
|
||||
throw new Error("Falha ao enviar arquivo para armazenamento.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fechamentoUploadsService = new FechamentoUploadsService();
|
||||
@@ -0,0 +1,132 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp";
|
||||
|
||||
export type UsuarioPapel = "admin" | "parceiro";
|
||||
export type UsuarioStatusFiltro = "all" | "true" | "false";
|
||||
|
||||
export type UsuarioItem = {
|
||||
id: string;
|
||||
parceiroId: string | null;
|
||||
nome: string | null;
|
||||
email: string | null;
|
||||
papel: UsuarioPapel;
|
||||
estaAtivo: boolean;
|
||||
criadoEm: string;
|
||||
atualizadoEm: string;
|
||||
};
|
||||
|
||||
type MetaResponse = {
|
||||
total: number;
|
||||
paginaAtual: number;
|
||||
totalPaginas: number;
|
||||
};
|
||||
|
||||
type ListarUsuariosResponse = {
|
||||
data: UsuarioItem[];
|
||||
meta: MetaResponse;
|
||||
};
|
||||
|
||||
type ApiErrorShape = {
|
||||
error?: {
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ListarUsuariosParams = {
|
||||
busca?: string;
|
||||
papel?: UsuarioPapel;
|
||||
estaAtivo?: UsuarioStatusFiltro;
|
||||
page: number;
|
||||
perPage: number;
|
||||
};
|
||||
|
||||
export type SalvarUsuarioPayload = {
|
||||
nome?: string | null;
|
||||
email?: string | null;
|
||||
papel: UsuarioPapel;
|
||||
parceiroId?: string | null;
|
||||
estaAtivo?: boolean;
|
||||
};
|
||||
|
||||
type SalvarUsuarioResponse = {
|
||||
data: UsuarioItem;
|
||||
};
|
||||
|
||||
class FechamentoUsuariosService {
|
||||
private handleAxiosError(error: unknown, fallback: string): never {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (!error.response) {
|
||||
throw new Error(
|
||||
"Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
|
||||
);
|
||||
}
|
||||
const message = ((error.response.data as ApiErrorShape | undefined)?.error?.message ??
|
||||
error.message ??
|
||||
fallback) as string;
|
||||
throw new Error(message);
|
||||
}
|
||||
throw new Error(fallback);
|
||||
}
|
||||
|
||||
async listarUsuarios(params: ListarUsuariosParams): Promise<ListarUsuariosResponse> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.get<ListarUsuariosResponse>(`${baseUrl}/usuarios`, {
|
||||
headers,
|
||||
params: {
|
||||
busca: params.busca || undefined,
|
||||
papel: params.papel || undefined,
|
||||
estaAtivo: params.estaAtivo ?? "all",
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao listar usuários.");
|
||||
}
|
||||
}
|
||||
|
||||
async criarUsuario(payload: SalvarUsuarioPayload): Promise<UsuarioItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.post<SalvarUsuarioResponse>(`${baseUrl}/usuarios`, payload, { headers });
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao criar usuário.");
|
||||
}
|
||||
}
|
||||
|
||||
async editarUsuario(id: string, payload: Partial<SalvarUsuarioPayload>): Promise<UsuarioItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.patch<SalvarUsuarioResponse>(`${baseUrl}/usuarios/${id}`, payload, {
|
||||
headers,
|
||||
});
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao editar usuário.");
|
||||
}
|
||||
}
|
||||
|
||||
async toggleAtivoUsuario(id: string): Promise<UsuarioItem> {
|
||||
try {
|
||||
const headers = await buildCommanderHeaders();
|
||||
const baseUrl = resolveCommanderBaseUrl();
|
||||
const response = await axios.patch<SalvarUsuarioResponse>(
|
||||
`${baseUrl}/usuarios/${id}/toggle-ativo`,
|
||||
{},
|
||||
{ headers },
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
this.handleAxiosError(error, "Erro ao atualizar status do usuário.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fechamentoUsuariosService = new FechamentoUsuariosService();
|
||||
Reference in New Issue
Block a user