1620 lines
48 KiB
TypeScript
1620 lines
48 KiB
TypeScript
// Serviço para comunicação com o backend
|
|
const API_BASE_URL =
|
|
import.meta.env.VITE_API_URL || "http://localhost:8080";
|
|
|
|
interface ApiResponse<T> {
|
|
data: T | null;
|
|
error: string | null;
|
|
isBackendDown: boolean;
|
|
}
|
|
|
|
// Wrapper request handler for 401 token refresh
|
|
let isRefreshing = false;
|
|
let refreshSubscribers: ((token: string) => void)[] = [];
|
|
|
|
function subscribeTokenRefresh(cb: (token: string) => void) {
|
|
refreshSubscribers.push(cb);
|
|
}
|
|
|
|
function onRefreshed(token: string) {
|
|
refreshSubscribers.forEach((cb) => cb(token));
|
|
refreshSubscribers = [];
|
|
}
|
|
|
|
export async function apiFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
|
let response = await fetch(input, init);
|
|
|
|
if (response.status === 401) {
|
|
const originalRequest = input;
|
|
const originalInit = init || {};
|
|
|
|
if (!isRefreshing) {
|
|
isRefreshing = true;
|
|
try {
|
|
const refreshRes = await fetch(`${API_BASE_URL}/auth/refresh`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include', // Send the HttpOnly refresh_token cookie
|
|
});
|
|
|
|
if (refreshRes.ok) {
|
|
const data = await refreshRes.json();
|
|
const newAccessToken = data.access_token;
|
|
localStorage.setItem('token', newAccessToken);
|
|
isRefreshing = false;
|
|
onRefreshed(newAccessToken);
|
|
} else {
|
|
isRefreshing = false;
|
|
// Refresh failed (token expired/invalid), logout
|
|
localStorage.removeItem('token');
|
|
window.location.href = '/entrar';
|
|
throw new Error('Session expired');
|
|
}
|
|
} catch (err) {
|
|
isRefreshing = false;
|
|
localStorage.removeItem('token');
|
|
window.location.href = '/entrar';
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
// Wait for the token to refresh and then retry the request
|
|
return new Promise((resolve, reject) => {
|
|
subscribeTokenRefresh((newToken) => {
|
|
// Update the Authorization header with the new token
|
|
const newHeaders = new Headers(originalInit.headers);
|
|
newHeaders.set('Authorization', `Bearer ${newToken}`);
|
|
originalInit.headers = newHeaders;
|
|
|
|
fetch(originalRequest, originalInit).then(resolve).catch(reject);
|
|
});
|
|
});
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
// Função auxiliar para fazer requisições
|
|
async function fetchFromBackend<T>(endpoint: string): Promise<ApiResponse<T>> {
|
|
try {
|
|
const region = localStorage.getItem("photum_selected_region") || "SP";
|
|
const response = await apiFetch(`${API_BASE_URL}${endpoint}`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"x-regiao": region,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error(`Error fetching ${endpoint}:`, error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Funções específicas para cada endpoint
|
|
|
|
/**
|
|
* Busca as funções profissionais disponíveis
|
|
*/
|
|
export async function getProfessionalRoles(): Promise<ApiResponse<string[]>> {
|
|
return fetchFromBackend<string[]>("/professional-roles");
|
|
}
|
|
|
|
/**
|
|
* Busca as empresas disponíveis
|
|
*/
|
|
export async function getCompanies(): Promise<
|
|
ApiResponse<
|
|
Array<{
|
|
id: string;
|
|
nome: string;
|
|
}>
|
|
>
|
|
> {
|
|
return fetchFromBackend("/api/empresas");
|
|
}
|
|
|
|
/**
|
|
* Busca as funções profissionais disponíveis do backend
|
|
*/
|
|
export async function getFunctions(): Promise<
|
|
ApiResponse<
|
|
Array<{
|
|
id: string;
|
|
nome: string;
|
|
}>
|
|
>
|
|
> {
|
|
return fetchFromBackend("/api/funcoes");
|
|
}
|
|
|
|
/**
|
|
* Cria um novo perfil profissional
|
|
*/
|
|
export async function createProfessional(data: any, token?: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const headers: any = {
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
const region = localStorage.getItem("photum_selected_region") || "SP";
|
|
|
|
if (token) {
|
|
headers["Authorization"] = `Bearer ${token}`;
|
|
}
|
|
headers["x-regiao"] = region;
|
|
|
|
const response = await apiFetch(`${API_BASE_URL}/api/profissionais`, {
|
|
method: "POST",
|
|
headers: headers,
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return {
|
|
data: responseData,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error creating professional:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Atualiza um profissional existente
|
|
*/
|
|
export async function updateProfessional(id: string, data: any, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/profissionais/${id}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return {
|
|
data: responseData,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error updating professional:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Atualiza o perfil do usuário logado (Cliente/Evento Owner)
|
|
*/
|
|
export async function updateUserProfile(data: any, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/me`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return {
|
|
data: responseData,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error updating user profile:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove um profissional
|
|
*/
|
|
export async function deleteProfessional(id: string, token: string): Promise<ApiResponse<void>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/profissionais/${id}`, {
|
|
method: "DELETE",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return {
|
|
data: null,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error deleting professional:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Busca um profissional pelo ID
|
|
*/
|
|
export async function getProfessionalById(id: string, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/profissionais/${id}`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error fetching professional by id:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Busca a lista de profissionais
|
|
*/
|
|
export async function getProfessionals(token: string): Promise<ApiResponse<any[]>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/profissionais`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error fetching professionals:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
export interface EventTypeResponse {
|
|
id: string;
|
|
nome: string;
|
|
precos: any[];
|
|
}
|
|
|
|
/**
|
|
* Busca os tipos de eventos disponíveis
|
|
*/
|
|
export async function getEventTypes(): Promise<
|
|
ApiResponse<EventTypeResponse[]>
|
|
> {
|
|
return fetchFromBackend<EventTypeResponse[]>("/api/tipos-eventos");
|
|
}
|
|
|
|
/**
|
|
* Busca os cursos/turmas disponíveis
|
|
*/
|
|
export async function getCourses(): Promise<
|
|
ApiResponse<
|
|
Array<{
|
|
id: string;
|
|
name: string;
|
|
institution: string;
|
|
year: number;
|
|
}>
|
|
>
|
|
> {
|
|
return fetchFromBackend("/courses");
|
|
}
|
|
|
|
/**
|
|
* Busca as instituições/empresas disponíveis
|
|
*/
|
|
export async function getInstitutions(): Promise<
|
|
ApiResponse<
|
|
Array<{
|
|
id: string;
|
|
name: string;
|
|
}>
|
|
>
|
|
> {
|
|
return fetchFromBackend("/institutions");
|
|
}
|
|
|
|
/**
|
|
* Busca os anos de formatura disponíveis
|
|
*/
|
|
export async function getGraduationYears(): Promise<ApiResponse<Array<{ id: string; ano_semestre: string }>>> {
|
|
return fetchFromBackend<Array<{ id: string; ano_semestre: string }>>("/api/anos-formaturas");
|
|
}
|
|
|
|
/**
|
|
* Busca os cursos disponíveis
|
|
*/
|
|
export async function getAvailableCourses(): Promise<ApiResponse<Array<{ id: string; nome: string }>>> {
|
|
return fetchFromBackend<Array<{ id: string; nome: string }>>("/api/cursos");
|
|
}
|
|
|
|
/**
|
|
* Busca a listagem de Cadastro FOT
|
|
*/
|
|
/**
|
|
* Busca a listagem de Cadastro FOT
|
|
*/
|
|
export async function getCadastroFot(token: string, empresaId?: string): Promise<ApiResponse<any[]>> {
|
|
try {
|
|
let url = `${API_BASE_URL}/api/cadastro-fot?_t=${new Date().getTime()}`;
|
|
if (empresaId) {
|
|
url += `&empresa_id=${empresaId}`;
|
|
}
|
|
|
|
const response = await apiFetch(url, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error fetching cadastro fot:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cria um novo cadastro FOT
|
|
*/
|
|
export async function createCadastroFot(data: any, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/cadastro-fot`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return {
|
|
data: responseData,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error creating cadastro fot:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Atualiza um cadastro FOT existente
|
|
*/
|
|
export async function updateCadastroFot(id: string, data: any, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/cadastro-fot/${id}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return {
|
|
data: responseData,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error updating cadastro fot:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifica se há eventos associados a um FOT
|
|
*/
|
|
export async function checkFotHasEvents(fotId: string, token: string): Promise<ApiResponse<{ hasEvents: boolean; eventCount: number }>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/cadastro-fot/${fotId}/eventos`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
// Se o endpoint não existir, vamos usar a lista de eventos para verificar localmente
|
|
return { data: { hasEvents: false, eventCount: 0 }, error: null, isBackendDown: false };
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data: { hasEvents: data.count > 0, eventCount: data.count },
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro ao verificar eventos",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finaliza ou reabre uma turma (FOT)
|
|
*/
|
|
export async function finalizeFOT(id: string, finalizada: boolean, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/cadastro-fot/${id}/finalize`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify({ finalizada }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return { data: responseData, error: null, isBackendDown: false };
|
|
} catch (error: any) {
|
|
console.error("Error finalizing FOT:", error);
|
|
return { data: null, error: error.message || "Erro ao finalizar turma", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove um cadastro FOT
|
|
*/
|
|
export async function deleteCadastroFot(id: string, token: string): Promise<ApiResponse<void>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/cadastro-fot/${id}`, {
|
|
method: "DELETE",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return {
|
|
data: null,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error deleting cadastro fot:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Busca os níveis educacionais disponíveis (EF I / EF II)
|
|
*/
|
|
export async function getEducationLevels(): Promise<
|
|
ApiResponse<
|
|
Array<{
|
|
id: string;
|
|
nome: string;
|
|
}>
|
|
>
|
|
> {
|
|
return fetchFromBackend("/api/niveis-educacionais");
|
|
}
|
|
|
|
/**
|
|
* Busca as universidades cadastradas
|
|
*/
|
|
export async function getUniversities(): Promise<
|
|
ApiResponse<
|
|
Array<{
|
|
id: string;
|
|
nome: string;
|
|
}>
|
|
>
|
|
> {
|
|
return fetchFromBackend("/api/universidades");
|
|
}
|
|
|
|
// Agenda
|
|
export const createAgenda = async (token: string, data: any) => {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return { data: responseData, error: null };
|
|
} catch (error: any) {
|
|
console.error("Erro ao criar agenda:", error);
|
|
return { data: null, error: error.message || "Erro ao criar agenda" };
|
|
}
|
|
};
|
|
|
|
// Agenda
|
|
export const getAgendas = async (token: string): Promise<ApiResponse<any[]>> => {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return { data, error: null, isBackendDown: false };
|
|
} catch (error: any) {
|
|
console.error("Erro ao buscar agendas:", error);
|
|
return { data: null, error: error.message || "Erro ao buscar agendas", isBackendDown: true };
|
|
}
|
|
};
|
|
|
|
// Agenda - Update
|
|
export const updateAgenda = async (token: string, id: string, data: any) => {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda/${id}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return { data: responseData, error: null };
|
|
} catch (error: any) {
|
|
console.error("Erro ao atualizar agenda:", error);
|
|
return { data: null, error: error.message || "Erro ao atualizar agenda" };
|
|
}
|
|
};
|
|
|
|
export const updateAssignmentStatus = async (token: string, eventId: string, professionalId: string, status: string, reason?: string) => {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals/${professionalId}/status`, {
|
|
method: "PATCH",
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
"Content-Type": "application/json",
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify({ status, reason }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
return { error: errorData.error || "Failed to update assignment status" };
|
|
}
|
|
const data = await response.json();
|
|
return { data };
|
|
} catch (error) {
|
|
console.error("API updateAssignmentStatus error:", error);
|
|
return { error: "Network error" };
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Busca usuários pendentes de aprovação
|
|
*/
|
|
export async function getPendingUsers(token: string): Promise<ApiResponse<any[]>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/admin/users/pending`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error fetching pending users:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Aprova um usuário
|
|
*/
|
|
export async function approveUser(userId: string, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/admin/users/${userId}/approve`, {
|
|
method: "PATCH",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error approving user:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rejeita um usuário
|
|
*/
|
|
export async function rejectUser(userId: string, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/admin/users/${userId}/reject`, {
|
|
method: "PATCH",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
// body: JSON.stringify({ reason }) // Future improvement
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error rejecting user:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Atualiza a role de um usuário
|
|
*/
|
|
export async function updateUserRole(userId: string, role: string, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/admin/users/${userId}/role`, {
|
|
method: "PATCH",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify({ role })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error updating user role:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Atribui um profissional a um evento
|
|
*/
|
|
export async function assignProfessional(token: string, eventId: string, professionalId: string, funcaoId?: string): Promise<ApiResponse<void>> {
|
|
try {
|
|
const body: any = { professional_id: professionalId };
|
|
if (funcaoId) {
|
|
body.funcao_id = funcaoId;
|
|
}
|
|
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(body)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return { data: undefined, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
console.error("Error assigning professional:", error);
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove um profissional de um evento
|
|
*/
|
|
export async function removeProfessional(token: string, eventId: string, professionalId: string): Promise<ApiResponse<void>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals/${professionalId}`, {
|
|
method: "DELETE",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return { data: undefined, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
console.error("Error removing professional:", error);
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Busca todos os usuários (Admin)
|
|
*/
|
|
export async function getAllUsers(token: string): Promise<ApiResponse<any[]>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/admin/users`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error fetching all users:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cria um novo usuário (Admin)
|
|
*/
|
|
export async function createAdminUser(data: any, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/admin/users`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return {
|
|
data: responseData,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error creating user:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Busca profissionais de um evento
|
|
*/
|
|
export async function getEventProfessionals(token: string, eventId: string): Promise<ApiResponse<any[]>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return { data, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
console.error("Error fetching event professionals:", error);
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Atualiza o status de um evento
|
|
*/
|
|
export async function updateEventStatus(token: string, eventId: string, status: string): Promise<ApiResponse<void>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda/${eventId}/status`, {
|
|
method: "PATCH",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify({ status })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return { data: undefined, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
console.error("Error updating event status:", error);
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Envia notificação de logística para todos os profissionais do evento
|
|
*/
|
|
export async function notifyLogistics(token: string, eventId: string, passengerOrders?: any): Promise<ApiResponse<void>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda/${eventId}/notify-logistics`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify({ passenger_orders: passengerOrders })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return { data: undefined, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
console.error("Error notifying logistics:", error);
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cria um usuário pela interface administrativa
|
|
*/
|
|
export async function adminCreateUser(data: any, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/admin/users`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return {
|
|
data: responseData,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error creating admin user:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtém URL pré-assinada para upload de arquivo
|
|
*/
|
|
export async function getUploadURL(filename: string, contentType: string): Promise<ApiResponse<{ upload_url: string; public_url: string }>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/auth/upload-url`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ filename, content_type: contentType }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error getting upload URL:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Access Codes
|
|
export async function createAccessCode(token: string, data: { codigo: string; descricao?: string; validade_dias: number; empresa_id?: string }) {
|
|
return mutationFetch(`${API_BASE_URL}/api/codigos-acesso`, "POST", data, token);
|
|
}
|
|
|
|
export async function listAccessCodes(token: string) {
|
|
return fetchFromBackendAuthenticated(`${API_BASE_URL}/api/codigos-acesso`, token);
|
|
}
|
|
|
|
export async function deleteAccessCode(token: string, id: string) {
|
|
return mutationFetch(`${API_BASE_URL}/api/codigos-acesso/${id}`, "DELETE", {}, token);
|
|
}
|
|
|
|
// Helpers for unified fetch
|
|
async function fetchFromBackendAuthenticated(url: string, token: string) {
|
|
try {
|
|
const res = await apiFetch(url, {
|
|
headers: {
|
|
"Authorization": `Bearer ${token}`,
|
|
"Content-Type": "application/json",
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
}
|
|
});
|
|
if (!res.ok) throw new Error(res.statusText);
|
|
const data = await res.json();
|
|
return { data, error: null };
|
|
} catch (e: any) {
|
|
return { data: null, error: e.message };
|
|
}
|
|
}
|
|
|
|
async function mutationFetch(url: string, method: string, body: any, token: string) {
|
|
try {
|
|
const res = await apiFetch(url, {
|
|
method,
|
|
headers: {
|
|
"Authorization": `Bearer ${token}`,
|
|
"Content-Type": "application/json",
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(body)
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({}));
|
|
throw new Error(err.error || res.statusText);
|
|
}
|
|
const data = await res.json();
|
|
return { data, error: null };
|
|
} catch (e: any) {
|
|
return { data: null, error: e.message };
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Realiza o upload do arquivo para a URL pré-assinada
|
|
*/
|
|
export async function uploadFileToSignedUrl(uploadUrl: string, file: File): Promise<void> {
|
|
const response = await apiFetch(uploadUrl, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": file.type,
|
|
},
|
|
body: file,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to upload file to S3. Status: ${response.status}`);
|
|
}
|
|
}
|
|
|
|
// --- Escalas / Scheduling ---
|
|
|
|
export interface EscalaInput {
|
|
agenda_id: string;
|
|
profissional_id: string;
|
|
data_hora_inicio: string; // ISO String
|
|
data_hora_fim: string; // ISO String
|
|
funcao_especifica?: string;
|
|
}
|
|
|
|
export async function createEscala(data: EscalaInput, token: string): Promise<ApiResponse<{ id: string }>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/escalas`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return { data: responseData, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
export async function listEscalas(agendaId: string, token: string): Promise<ApiResponse<any[]>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/escalas?agenda_id=${agendaId}`, {
|
|
headers: { "Authorization": `Bearer ${token}` },
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return { data: responseData, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
export async function deleteEscala(id: string, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/escalas/${id}`, {
|
|
method: "DELETE",
|
|
headers: { "Authorization": `Bearer ${token}` },
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return { data: { message: "deleted" }, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
export async function updateEscala(id: string, data: Partial<EscalaInput>, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/escalas/${id}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return { data: { message: "updated" }, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
// --- Logistica (Carros/Passageiros) ---
|
|
|
|
export interface CarroInput {
|
|
agenda_id: string;
|
|
motorista_id?: string;
|
|
nome_motorista?: string;
|
|
horario_chegada?: string;
|
|
observacoes?: string;
|
|
}
|
|
|
|
export async function createCarro(data: CarroInput, token: string): Promise<ApiResponse<{ id: string }>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/carros`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
|
|
body: JSON.stringify(data),
|
|
});
|
|
const resData = await response.json();
|
|
if (!response.ok) throw new Error(resData.error || "Erro ao criar carro");
|
|
return { data: resData, error: null, isBackendDown: false };
|
|
} catch (err: any) { return { data: null, error: err.message, isBackendDown: true }; }
|
|
}
|
|
|
|
export async function listCarros(agendaId: string, token: string): Promise<ApiResponse<any[]>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/carros?agenda_id=${agendaId}`, {
|
|
headers: { "Authorization": `Bearer ${token}` },
|
|
});
|
|
const resData = await response.json();
|
|
if (!response.ok) throw new Error(resData.error || "Erro ao listar carros");
|
|
return { data: resData, error: null, isBackendDown: false };
|
|
} catch (err: any) { return { data: null, error: err.message, isBackendDown: true }; }
|
|
}
|
|
|
|
export async function deleteCarro(id: string, token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/carros/${id}`, {
|
|
method: "DELETE",
|
|
headers: { "Authorization": `Bearer ${token}` },
|
|
});
|
|
if (!response.ok) throw new Error("Erro ao deletar carro");
|
|
return { data: { message: "deleted" }, error: null, isBackendDown: false };
|
|
} catch (err: any) { return { data: null, error: err.message, isBackendDown: true }; }
|
|
}
|
|
|
|
export async function addPassenger(carroId: string, profissionalId: string, token: string) {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/carros/${carroId}/passageiros`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
body: JSON.stringify({ profissional_id: profissionalId })
|
|
});
|
|
if (!response.ok) throw new Error("Erro ao adicionar passageiro");
|
|
return { error: null };
|
|
} catch (err: any) { return { error: err.message }; }
|
|
}
|
|
|
|
export async function removePassenger(carroId: string, profissionalId: string, token: string) {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/carros/${carroId}/passageiros/${profissionalId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
if (!response.ok) throw new Error("Erro ao remover passageiro");
|
|
return { error: null };
|
|
} catch (err: any) { return { error: err.message }; }
|
|
}
|
|
|
|
export async function listPassengers(carroId: string, token: string) {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/carros/${carroId}/passageiros`, {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
const data = await response.json();
|
|
if (!response.ok) throw new Error("Erro ao listar passageiros");
|
|
return { data, error: null };
|
|
} catch (err: any) { return { data: null, error: err.message }; }
|
|
}
|
|
|
|
/**
|
|
* Verifica se um código de acesso é válido
|
|
*/
|
|
export async function verifyAccessCode(code: string): Promise<ApiResponse<{ valid: boolean; error?: string; empresa_id?: string; empresa_nome?: string }>> {
|
|
return fetchFromBackend<{ valid: boolean; error?: string; empresa_id?: string; empresa_nome?: string }>(`/api/public/codigos-acesso/verificar?code=${encodeURIComponent(code)}`);
|
|
}
|
|
|
|
/**
|
|
* Busca o extrato financeiro do profissional logado
|
|
*/
|
|
export async function getProfessionalFinancialStatement(token: string): Promise<ApiResponse<any>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/profissionais/me/financial-statement`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return {
|
|
data,
|
|
error: null,
|
|
isBackendDown: false,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error fetching financial statement:", error);
|
|
return {
|
|
data: null,
|
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
|
isBackendDown: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Busca o preço base para um dado evento e serviço
|
|
*/
|
|
export async function getPrice(token: string, eventName: string, serviceName: string): Promise<ApiResponse<{ valor: number }>> {
|
|
try {
|
|
const response = await apiFetch(`${API_BASE_URL}/api/finance/price?event=${encodeURIComponent(eventName)}&service=${encodeURIComponent(serviceName)}`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${token}`,
|
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
const data = await response.json();
|
|
return { data, error: null, isBackendDown: false };
|
|
} catch (error) {
|
|
console.error("Error fetching price:", error);
|
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
|
}
|
|
}
|
|
|
|
export const setCoordinator = async (token: string, eventId: string, professionalId: string, isCoordinator: boolean) => {
|
|
try {
|
|
const region = localStorage.getItem("photum_selected_region") || "SP";
|
|
const response = await apiFetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals/${professionalId}/coordinator`, {
|
|
method: "PUT",
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
"Content-Type": "application/json",
|
|
"x-regiao": region
|
|
},
|
|
body: JSON.stringify({ is_coordinator: isCoordinator }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
return { error: errorData.error || "Failed to set coordinator" };
|
|
}
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error("API setCoordinator error:", error);
|
|
return { error: "Network error" };
|
|
}
|
|
};
|
|
|
|
// --------------- LOGISTICS DAILY INVITATIONS --------------- //
|
|
|
|
export async function getAvailableProfessionalsLogistics(date: string, page: number = 1, limit: number = 50, search: string = "") {
|
|
try {
|
|
const region = localStorage.getItem("photum_selected_region") || "SP";
|
|
const query = new URLSearchParams({
|
|
data: date,
|
|
page: page.toString(),
|
|
limit: limit.toString(),
|
|
search: search
|
|
});
|
|
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/disponiveis?${query.toString()}`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"x-regiao": region,
|
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error(`Error fetching daily professionals:`, error);
|
|
return { data: [], total: 0, page: 1, total_pages: 1 };
|
|
}
|
|
}
|
|
|
|
export async function createDailyInvitation(profissionalId: string, data: string) {
|
|
try {
|
|
const region = localStorage.getItem("photum_selected_region") || "SP";
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/convites`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"x-regiao": region,
|
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
|
},
|
|
body: JSON.stringify({ profissional_id: profissionalId, data: data }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errData = await response.json().catch(() => ({}));
|
|
throw new Error(errData.error || `Failed to create invitation (status ${response.status})`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error(`Error creating daily invitation:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function getDailyInvitationsProfessional(profId: string) {
|
|
try {
|
|
const region = localStorage.getItem("photum_selected_region") || "SP";
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/convites?prof_id=${profId}`, {
|
|
method: "GET",
|
|
headers: {
|
|
"x-regiao": region,
|
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Failed to fetch daily invitations");
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error(`Error fetching daily invitations:`, error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function respondDailyInvitation(invitationId: string, status: "ACEITO" | "REJEITADO", reason?: string) {
|
|
try {
|
|
const region = localStorage.getItem("photum_selected_region") || "SP";
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/convites/${invitationId}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"x-regiao": region,
|
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
|
},
|
|
body: JSON.stringify({ status, motivo_rejeicao: reason || "" }),
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Failed to respond to invitation");
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error(`Error responding daily invitation:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function getDailyInvitationsByDate(date: string) {
|
|
try {
|
|
const region = localStorage.getItem("photum_selected_region") || "SP";
|
|
const response = await apiFetch(`${API_BASE_URL}/api/logistica/convites-por-data?data=${date}`, {
|
|
method: "GET",
|
|
headers: {
|
|
"x-regiao": region,
|
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Failed to fetch daily invitations by date");
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error(`Error fetching daily invitations by date:`, error);
|
|
return [];
|
|
}
|
|
}
|