setIsOpen(false)}
+ aria-hidden="true"
+ />
+ )}
+
+
+ >
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/providers/app-providers.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/providers/app-providers.tsx
new file mode 100644
index 000000000..6a19cb36c
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/providers/app-providers.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { SessionProvider } from "next-auth/react";
+import { useState } from "react";
+import { ThemeProvider } from "@/components/providers/theme-provider";
+import { Toaster } from "@/components/providers/toast-provider";
+
+import type { Session } from "next-auth";
+
+export function AppProviders({ children, session }: { children: React.ReactNode; session?: Session | null }) {
+ const [queryClient] = useState(
+ () =>
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 60 * 1000,
+ refetchOnWindowFocus: false,
+ },
+ },
+ })
+ );
+
+ return (
+
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/providers/theme-provider.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/providers/theme-provider.tsx
new file mode 100644
index 000000000..e898a7021
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/providers/theme-provider.tsx
@@ -0,0 +1,60 @@
+"use client";
+
+import { createContext, useContext, useEffect, useState } from "react";
+
+type Theme = "light" | "dark";
+
+interface ThemeContextType {
+ theme: Theme;
+ setTheme: (theme: Theme) => void;
+ toggleTheme: () => void;
+}
+
+const ThemeContext = createContext
(undefined);
+
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
+ const [theme, setThemeState] = useState("light");
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ const stored = localStorage.getItem("theme");
+ const validTheme = stored === "light" || stored === "dark" ? stored : null;
+
+ if (validTheme) {
+ setThemeState(validTheme);
+ document.documentElement.classList.toggle("dark", validTheme === "dark");
+ } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
+ setThemeState("dark");
+ document.documentElement.classList.add("dark");
+ }
+ }, []);
+
+ const setTheme = (newTheme: Theme) => {
+ setThemeState(newTheme);
+ localStorage.setItem("theme", newTheme);
+ document.documentElement.classList.toggle("dark", newTheme === "dark");
+ };
+
+ const toggleTheme = () => {
+ setTheme(theme === "light" ? "dark" : "light");
+ };
+
+ const contextValue = mounted
+ ? { theme, setTheme, toggleTheme }
+ : { theme: "light" as Theme, setTheme: () => {}, toggleTheme: () => {} };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme() {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error("useTheme must be used within a ThemeProvider");
+ }
+ return context;
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/providers/toast-provider.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/providers/toast-provider.tsx
new file mode 100644
index 000000000..1b7f00eb9
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/providers/toast-provider.tsx
@@ -0,0 +1,18 @@
+"use client";
+
+import { Toaster as SonnerToaster } from "sonner";
+
+export function Toaster() {
+ return (
+
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/badge.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/badge.tsx
new file mode 100644
index 000000000..7ea2908f7
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/badge.tsx
@@ -0,0 +1,27 @@
+import { tv, type VariantProps } from "tailwind-variants";
+import { twMerge } from "tailwind-merge";
+import type { ComponentProps } from "react";
+
+export const badgeVariants = tv({
+ base: [
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors",
+ ],
+ variants: {
+ variant: {
+ default: "border-transparent bg-primary text-primary-foreground",
+ secondary: "border-transparent bg-secondary text-secondary-foreground",
+ destructive: "border-transparent bg-destructive text-destructive-foreground",
+ success: "border-transparent bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
+ warning: "border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
+ },
+ },
+ defaultVariants: { variant: "default" },
+});
+
+export interface BadgeProps extends ComponentProps<"span">, VariantProps {}
+
+export function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/button.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/button.tsx
new file mode 100644
index 000000000..f431788bb
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/button.tsx
@@ -0,0 +1,43 @@
+import { tv, type VariantProps } from "tailwind-variants";
+import { twMerge } from "tailwind-merge";
+import type { ComponentProps } from "react";
+
+export const buttonVariants = tv({
+ base: [
+ "inline-flex cursor-pointer items-center justify-center font-medium rounded-lg transition-colors",
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ ],
+ variants: {
+ variant: {
+ primary: "bg-primary text-primary-foreground hover:bg-primary-hover",
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover",
+ ghost: "border-transparent bg-transparent text-muted-foreground hover:text-foreground hover:bg-muted",
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ },
+ size: {
+ sm: "h-9 px-3 text-sm",
+ md: "h-10 px-4 text-sm",
+ lg: "h-11 px-6 text-base",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: { variant: "primary", size: "md" },
+});
+
+export interface ButtonProps extends ComponentProps<"button">, VariantProps {}
+
+export function Button({ className, variant, size, disabled, children, ...props }: ButtonProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/card.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/card.tsx
new file mode 100644
index 000000000..d6cbf2ba0
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/card.tsx
@@ -0,0 +1,57 @@
+import { twMerge } from "tailwind-merge";
+import type { ComponentProps } from "react";
+
+export interface CardProps extends ComponentProps<"div"> {}
+
+export function Card({ className, ...props }: CardProps) {
+ return (
+
+ );
+}
+
+export function CardHeader({ className, ...props }: ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export function CardTitle({ className, ...props }: ComponentProps<"h3">) {
+ return (
+
+ );
+}
+
+export function CardContent({ className, ...props }: ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export function CardDescription({ className, ...props }: ComponentProps<"p">) {
+ return (
+
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/dialog.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/dialog.tsx
new file mode 100644
index 000000000..d6e68a768
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/dialog.tsx
@@ -0,0 +1,116 @@
+"use client";
+
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { X } from "lucide-react";
+import { twMerge } from "tailwind-merge";
+import { Button } from "./button";
+
+export interface DialogProps {
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+ children: React.ReactNode;
+}
+
+export function Dialog({ open, onOpenChange, children }: DialogProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export interface DialogTriggerProps {
+ children: React.ReactNode;
+ className?: string;
+ asChild?: boolean;
+}
+
+export function DialogTrigger({ children, className, asChild }: DialogTriggerProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export interface DialogContentProps {
+ children: React.ReactNode;
+ className?: string;
+}
+
+export function DialogContent({ children, className = "" }: DialogContentProps) {
+ return (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ );
+}
+
+export interface DialogHeaderProps {
+ children: React.ReactNode;
+ className?: string;
+}
+
+export function DialogHeader({ children, className = "" }: DialogHeaderProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export interface DialogFooterProps {
+ children: React.ReactNode;
+ className?: string;
+}
+
+export function DialogFooter({ children, className = "" }: DialogFooterProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export interface DialogTitleProps {
+ children: React.ReactNode;
+ className?: string;
+}
+
+export function DialogTitle({ children, className = "" }: DialogTitleProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export interface DialogDescriptionProps {
+ children: React.ReactNode;
+ className?: string;
+}
+
+export function DialogDescription({ children, className = "" }: DialogDescriptionProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function DialogClose({ children, onClick }: { children?: React.ReactNode; onClick?: () => void }) {
+ return (
+
+ {children ? children : Cancelar }
+
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/input.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/input.tsx
new file mode 100644
index 000000000..d7165ef37
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/input.tsx
@@ -0,0 +1,20 @@
+import { twMerge } from "tailwind-merge";
+import type { ComponentProps } from "react";
+
+export const inputStyles = {
+ base: [
+ "flex h-10 w-full rounded-lg border border-input bg-background px-3 py-2 text-sm",
+ "file:border-0 file:bg-transparent file:text-sm file:font-medium",
+ "placeholder:text-muted-foreground",
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
+ "disabled:cursor-not-allowed disabled:opacity-50",
+ ],
+};
+
+export interface InputProps extends ComponentProps<"input"> {}
+
+export function Input({ className, ...props }: InputProps) {
+ return (
+
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/select.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/select.tsx
new file mode 100644
index 000000000..0960cf912
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/select.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import * as SelectPrimitive from "@radix-ui/react-select";
+import { Check, ChevronDown } from "lucide-react";
+
+export interface SelectProps {
+ value?: string;
+ onValueChange?: (value: string) => void;
+ children: React.ReactNode;
+ placeholder?: string;
+ className?: string;
+ disabled?: boolean;
+ name?: string;
+ defaultValue?: string;
+}
+
+export function Select({ value, onValueChange, children, placeholder, className = "", disabled, name, defaultValue }: SelectProps) {
+ return (
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
+export interface SelectItemProps {
+ value: string;
+ children: React.ReactNode;
+ className?: string;
+}
+
+export function SelectItem({ value, children, className = "" }: SelectItemProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/theme-toggle.tsx b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/theme-toggle.tsx
new file mode 100644
index 000000000..a25ccfdbf
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/components/ui/theme-toggle.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import { Moon, Sun } from "lucide-react";
+import { Button } from "./button";
+import { useTheme } from "@/components/providers/theme-provider";
+
+export function ThemeToggle() {
+ const { theme, toggleTheme } = useTheme();
+
+ return (
+
+ {theme === "light" ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/index.ts b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/index.ts
new file mode 100644
index 000000000..e573b7984
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/index.ts
@@ -0,0 +1,6 @@
+export * from "./use-providers";
+export * from "./use-allowed-cities";
+export * from "./use-categories";
+export * from "./use-users";
+export * from "./use-dashboard";
+export * from "./use-services";
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-allowed-cities.ts b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-allowed-cities.ts
new file mode 100644
index 000000000..ea905f057
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-allowed-cities.ts
@@ -0,0 +1,101 @@
+"use client";
+
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import {
+ apiAllowedCitiesGet,
+ apiAllowedCitiesGet2,
+ apiAllowedCitiesPost,
+ apiAllowedCitiesPut,
+ apiAllowedCitiesPatch,
+ apiAllowedCitiesDelete,
+} from "@/lib/api/generated";
+import type {
+ ApiAllowedCitiesGetData,
+ ApiAllowedCitiesGet2Data,
+ ApiAllowedCitiesPostData,
+ ApiAllowedCitiesPutData,
+ ApiAllowedCitiesPatchData,
+ ApiAllowedCitiesDeleteData,
+} from "@/lib/api/generated";
+
+export const allowedCitiesKeys = {
+ all: ["allowedCities"] as const,
+ lists: () => [...allowedCitiesKeys.all, "list"] as const,
+ list: (filters?: ApiAllowedCitiesGetData["query"]) =>
+ [...allowedCitiesKeys.lists(), filters] as const,
+ details: () => [...allowedCitiesKeys.all, "detail"] as const,
+ detail: (id: string) => [...allowedCitiesKeys.details(), id] as const,
+};
+
+export function useAllowedCities(onlyActive?: boolean) {
+ return useQuery({
+ queryKey: allowedCitiesKeys.list({ onlyActive }),
+ queryFn: () => apiAllowedCitiesGet({ query: { onlyActive } } as any),
+ select: (data: any) => data.data ?? data,
+ });
+}
+
+export function useAllowedCityById(id: string) {
+ return useQuery({
+ queryKey: allowedCitiesKeys.detail(id),
+ queryFn: () => apiAllowedCitiesGet2({ path: { id } } as any),
+ select: (data: any) => data.data ?? data,
+ enabled: !!id,
+ });
+}
+
+export function useCreateAllowedCity() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: any) =>
+ apiAllowedCitiesPost(data.body ? data : { body: data } as any),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: allowedCitiesKeys.lists() });
+ },
+ });
+}
+
+export function useUpdateAllowedCity() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: any) =>
+ apiAllowedCitiesPut(data.path ? data : {
+ path: { id: data.id },
+ body: data.body ?? data,
+ }),
+ onSuccess: (_, variables: any) => {
+ queryClient.invalidateQueries({ queryKey: allowedCitiesKeys.detail(variables?.path?.id ?? variables.id) });
+ queryClient.invalidateQueries({ queryKey: allowedCitiesKeys.lists() });
+ },
+ });
+}
+
+export function usePatchAllowedCity() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: any) =>
+ apiAllowedCitiesPatch(data.path ? data : {
+ path: { id: data.id },
+ body: data.body ?? data,
+ }),
+ onSuccess: (_, variables: any) => {
+ queryClient.invalidateQueries({ queryKey: allowedCitiesKeys.detail(variables?.path?.id ?? variables.id) });
+ queryClient.invalidateQueries({ queryKey: allowedCitiesKeys.lists() });
+ },
+ });
+}
+
+export function useDeleteAllowedCity() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: any) =>
+ apiAllowedCitiesDelete(data.path ? data : { path: { id: data } } as any),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: allowedCitiesKeys.lists() });
+ },
+ });
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-categories.ts b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-categories.ts
new file mode 100644
index 000000000..5ff5b11ef
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-categories.ts
@@ -0,0 +1,165 @@
+"use client";
+
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import {
+ apiCategoriesGet,
+ apiCategoriesGet2,
+ apiCategoriesPost,
+ apiCategoriesPut,
+ apiCategoriesDelete,
+ apiActivatePost2,
+ apiDeactivatePost2,
+} from "@/lib/api/generated";
+import type {
+ ApiCategoriesGetData,
+ ApiCategoriesGet2Data,
+ ApiCategoriesPostData,
+ ApiCategoriesPutData,
+ ApiCategoriesDeleteData,
+} from "@/lib/api/generated";
+import type { ServiceCategoryDto } from "@/lib/types";
+
+type CategoryCreateInput = {
+ name: string;
+ description?: string;
+ displayOrder?: number;
+};
+
+type CategoryUpdateInput = {
+ id: string;
+ name: string;
+ description?: string;
+ displayOrder?: number;
+};
+
+export const categoryKeys = {
+ all: ["categories"] as const,
+ lists: () => [...categoryKeys.all, "list"] as const,
+ list: (filters?: ApiCategoriesGetData["query"]) =>
+ [...categoryKeys.lists(), filters] as const,
+ details: () => [...categoryKeys.all, "detail"] as const,
+ detail: (id: string) => [...categoryKeys.details(), id] as const,
+};
+
+function isDataPayload(obj: unknown): obj is { data?: ServiceCategoryDto[] } {
+ return obj !== null && typeof obj === "object" && "data" in obj;
+}
+
+function normalizeCategoriesResponse(data: unknown): ServiceCategoryDto[] {
+ if (!data) return [];
+ if (Array.isArray(data)) return data as ServiceCategoryDto[];
+ if (isDataPayload(data)) {
+ return data.data ?? [];
+ }
+ return [];
+}
+
+export function useCategories() {
+ return useQuery({
+ queryKey: categoryKeys.lists(),
+ queryFn: () => apiCategoriesGet(),
+ select: (data) => normalizeCategoriesResponse(data),
+ });
+}
+
+export function useCategoryById(id: string) {
+ return useQuery({
+ queryKey: categoryKeys.detail(id),
+ queryFn: () => apiCategoriesGet2({ path: { id } }),
+ select: (data) => {
+ if (!data) return undefined;
+ if (data !== null && typeof data === "object" && "data" in data) {
+ return (data as { data?: ServiceCategoryDto }).data;
+ }
+ return data as ServiceCategoryDto;
+ },
+ enabled: !!id,
+ });
+}
+
+export function useCreateCategory() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (input: CategoryCreateInput) =>
+ apiCategoriesPost({
+ body: {
+ name: input.name,
+ description: input.description ?? null,
+ displayOrder: input.displayOrder ?? 0,
+ },
+ }),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: categoryKeys.lists() });
+ },
+ });
+}
+
+export function useUpdateCategory() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (input: CategoryUpdateInput) => {
+ const body: {
+ name: string;
+ description: string | null;
+ displayOrder?: number;
+ } = {
+ name: input.name,
+ description: input.description ?? null,
+ };
+ if (input.displayOrder !== undefined) {
+ body.displayOrder = input.displayOrder;
+ }
+ return apiCategoriesPut({
+ path: { id: input.id },
+ body,
+ });
+ },
+ onSuccess: (_, variables) => {
+ queryClient.invalidateQueries({ queryKey: categoryKeys.detail(variables.id) });
+ queryClient.invalidateQueries({ queryKey: categoryKeys.lists() });
+ queryClient.invalidateQueries({ queryKey: ["services"] });
+ },
+ });
+}
+
+export function useDeleteCategory() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (id: string) =>
+ apiCategoriesDelete({ path: { id } }),
+ onSuccess: (_, id) => {
+ queryClient.invalidateQueries({ queryKey: categoryKeys.detail(id) });
+ queryClient.invalidateQueries({ queryKey: categoryKeys.lists() });
+ queryClient.invalidateQueries({ queryKey: ["services"] });
+ },
+ });
+}
+
+export function useActivateCategory() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (id: string) =>
+ apiActivatePost2({ path: { id } }),
+ onSuccess: (_, id) => {
+ queryClient.invalidateQueries({ queryKey: categoryKeys.detail(id) });
+ queryClient.invalidateQueries({ queryKey: categoryKeys.lists() });
+ },
+ });
+}
+
+export function useDeactivateCategory() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (id: string) =>
+ apiDeactivatePost2({ path: { id } }),
+ onSuccess: (_, id) => {
+ queryClient.invalidateQueries({ queryKey: categoryKeys.detail(id) });
+ queryClient.invalidateQueries({ queryKey: categoryKeys.lists() });
+ },
+ });
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-dashboard.ts b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-dashboard.ts
new file mode 100644
index 000000000..0770ca36a
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-dashboard.ts
@@ -0,0 +1,76 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { apiProvidersGet2 } from "@/lib/api/generated";
+import type { ApiProvidersGet2Data } from "@/lib/api/generated";
+import { EVerificationStatus, EProviderType } from "@/lib/types";
+
+export interface DashboardStats {
+ total: number;
+ pending: number;
+ approved: number;
+ rejected: number;
+ suspended: number;
+ underReview: number;
+ individual: number;
+ company: number;
+ freelancer: number;
+ cooperative: number;
+}
+
+export function useDashboardStats() {
+ return useQuery({
+ queryKey: ["dashboard", "stats"],
+ queryFn: async (): Promise => {
+ let currentPage = 1;
+ let totalPages = 1;
+ let allProviders: any[] = [];
+
+ do {
+ const response: any = await apiProvidersGet2({
+ query: { pageNumber: currentPage, pageSize: 100 }
+ } as any);
+
+ const data = response.data?.value ?? response.data ?? response ?? {};
+ const items = data.items ?? data.data ?? [];
+
+ allProviders = allProviders.concat(items);
+ totalPages = data.totalPages ?? 1;
+ currentPage++;
+ } while (currentPage <= totalPages);
+
+ const stats: DashboardStats = {
+ total: allProviders.length,
+ pending: 0,
+ approved: 0,
+ rejected: 0,
+ suspended: 0,
+ underReview: 0,
+ individual: 0,
+ company: 0,
+ freelancer: 0,
+ cooperative: 0,
+ };
+
+ allProviders.forEach((p) => {
+ switch (p.verificationStatus) {
+ case EVerificationStatus.Pending: stats.pending++; break;
+ case EVerificationStatus.InProgress: stats.underReview++; break;
+ case EVerificationStatus.Verified: stats.approved++; break;
+ case EVerificationStatus.Rejected: stats.rejected++; break;
+ case EVerificationStatus.Suspended: stats.suspended++; break;
+ }
+
+ switch (p.type) {
+ case EProviderType.Individual: stats.individual++; break;
+ case EProviderType.Company: stats.company++; break;
+ case EProviderType.Cooperative: stats.cooperative++; break;
+ case EProviderType.Freelancer: stats.freelancer++; break;
+ }
+ });
+
+ return stats;
+ },
+ staleTime: 1000 * 60 * 5,
+ });
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-providers.ts b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-providers.ts
new file mode 100644
index 000000000..6f4ee7451
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-providers.ts
@@ -0,0 +1,142 @@
+"use client";
+
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import {
+ apiProvidersGet,
+ apiProviderGet,
+ apiProvidersPost,
+ apiProvidersPut,
+ apiProvidersDelete,
+ apiProvidersGet2,
+ apiProvidersGet3,
+ apiActivatePost,
+ apiDeactivatePost,
+} from "@/lib/api/generated";
+import type {
+ ApiProvidersGetData,
+ ApiProvidersGet2Data,
+ ApiProvidersGet3Data,
+ ApiProviderGetData,
+ ApiProvidersPostData,
+ ApiProvidersPutData,
+ ApiProvidersDeleteData,
+ ApiActivatePostData,
+ ApiDeactivatePostData,
+} from "@/lib/api/generated";
+import type { ApiProvidersGet2Error, ApiProvidersGet4Error } from "@/lib/api/generated";
+
+export const providerKeys = {
+ all: ["providers"] as const,
+ lists: () => [...providerKeys.all, "list"] as const,
+ list: (filters?: ApiProvidersGet2Data["query"]) =>
+ [...providerKeys.lists(), filters] as const,
+ details: () => [...providerKeys.all, "detail"] as const,
+ detail: (id: string) => [...providerKeys.details(), id] as const,
+ byStatus: (status: string) =>
+ [...providerKeys.all, "byStatus", status] as const,
+ byType: (type: string) => [...providerKeys.all, "byType", type] as const,
+};
+
+export function useProviders(filters?: any) {
+ return useQuery({
+ queryKey: providerKeys.list(filters),
+ queryFn: () => apiProvidersGet2({ query: filters } as any) as any,
+ select: (data: any) => data.data ?? data,
+ });
+}
+
+export function useProviderById(id: string) {
+ return useQuery({
+ queryKey: providerKeys.detail(id),
+ queryFn: () => apiProvidersGet3({ path: { id } } as any) as any,
+ select: (data: any) => data.data ?? data,
+ enabled: !!id,
+ });
+}
+
+export function useProvidersByStatus(status: string) {
+ return useQuery({
+ queryKey: providerKeys.byStatus(status),
+ queryFn: () =>
+ apiProvidersGet2({
+ query: { verificationStatus: status },
+ } as any) as any,
+ select: (data: any) => data.data ?? data,
+ enabled: !!status,
+ });
+}
+
+export function useProvidersByType(type: string) {
+ return useQuery({
+ queryKey: providerKeys.byType(type),
+ queryFn: () =>
+ apiProvidersGet2({
+ query: { type },
+ } as any) as any,
+ select: (data: any) => data.data ?? data,
+ enabled: !!type,
+ });
+}
+
+export function useCreateProvider() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: any) =>
+ apiProvidersPost({ body: data } as any),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: providerKeys.lists() });
+ },
+ });
+}
+
+export function useUpdateProvider() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: any) =>
+ apiProvidersPut({ path: { id: data.id }, body: data.data } as any),
+ onSuccess: (_, variables: any) => {
+ queryClient.invalidateQueries({ queryKey: providerKeys.detail(variables.id) });
+ queryClient.invalidateQueries({ queryKey: providerKeys.lists() });
+ },
+ });
+}
+
+export function useDeleteProvider() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (id: string) =>
+ apiProvidersDelete({ path: { id } } as any),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: providerKeys.lists() });
+ },
+ });
+}
+
+export function useActivateProvider() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (id: string) =>
+ apiActivatePost({ path: { id } } as any),
+ onSuccess: (_, id) => {
+ queryClient.invalidateQueries({ queryKey: providerKeys.detail(id) });
+ queryClient.invalidateQueries({ queryKey: providerKeys.lists() });
+ },
+ });
+}
+
+export function useDeactivateProvider() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (id: string) =>
+ apiDeactivatePost({ path: { id } } as any),
+ onSuccess: (_, id) => {
+ queryClient.invalidateQueries({ queryKey: providerKeys.detail(id) });
+ queryClient.invalidateQueries({ queryKey: providerKeys.lists() });
+ },
+ });
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-services.ts b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-services.ts
new file mode 100644
index 000000000..c841cda63
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-services.ts
@@ -0,0 +1,85 @@
+"use client";
+
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import {
+ apiServicesGet,
+ apiServicesGet2,
+ apiServicesPost,
+ apiServicesPut,
+ apiServicesDelete,
+ apiCategoryGet,
+} from "@/lib/api/generated";
+import type {
+ ApiServicesGetData,
+ ApiServicesGet2Data,
+ ApiServicesPostData,
+ ApiServicesPutData,
+ ApiServicesDeleteData,
+} from "@/lib/api/generated";
+
+export const serviceKeys = {
+ all: ["services"] as const,
+ lists: () => [...serviceKeys.all, "list"] as const,
+ list: (filters?: ApiServicesGetData["query"]) =>
+ [...serviceKeys.lists(), filters] as const,
+ details: () => [...serviceKeys.all, "detail"] as const,
+ detail: (id: string) => [...serviceKeys.details(), id] as const,
+};
+
+export function useServices(categoryId?: string) {
+ return useQuery({
+ queryKey: serviceKeys.list({ categoryId } as any),
+ queryFn: () => categoryId ? apiCategoryGet({ path: { categoryId } }) : apiServicesGet(),
+ select: (data: any) => data.data ?? data,
+ });
+}
+
+export function useServiceById(id: string) {
+ return useQuery({
+ queryKey: serviceKeys.detail(id),
+ queryFn: () => apiServicesGet2({ path: { id } } as any) as any,
+ select: (data: any) => data.data ?? data,
+ enabled: !!id,
+ });
+}
+
+export function useCreateService() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: any) =>
+ apiServicesPost(data.body ? data : { body: data } as any),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: serviceKeys.lists() });
+ },
+ });
+}
+
+export function useUpdateService() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: any) =>
+ apiServicesPut(data.path ? data : {
+ path: { id: data.id },
+ body: data.body ?? data,
+ } as any),
+ onSuccess: (_, variables: any) => {
+ queryClient.invalidateQueries({ queryKey: serviceKeys.detail(variables?.path?.id ?? variables.id) });
+ queryClient.invalidateQueries({ queryKey: serviceKeys.lists() });
+ },
+ });
+}
+
+export function useDeleteService() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: any) =>
+ apiServicesDelete(data.path ? data : { path: { id: data } } as any),
+ onSuccess: (_, id: any) => {
+ queryClient.invalidateQueries({ queryKey: serviceKeys.detail(id?.path?.id ?? id) });
+ queryClient.invalidateQueries({ queryKey: serviceKeys.lists() });
+ },
+ });
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-users.ts b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-users.ts
new file mode 100644
index 000000000..cf73f9393
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/hooks/admin/use-users.ts
@@ -0,0 +1,65 @@
+"use client";
+
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import {
+ apiUsersGet,
+ apiUsersGet2,
+ apiUsersPost,
+ apiUsersDelete,
+} from "@/lib/api/generated";
+import type {
+ ApiUsersGetData,
+ ApiUsersGet2Data,
+ ApiUsersPostData,
+ ApiUsersDeleteData,
+} from "@/lib/api/generated";
+
+export const userKeys = {
+ all: ["users"] as const,
+ lists: () => [...userKeys.all, "list"] as const,
+ list: (filters?: ApiUsersGetData["query"]) =>
+ [...userKeys.lists(), filters] as const,
+ details: () => [...userKeys.all, "detail"] as const,
+ detail: (id: string) => [...userKeys.details(), id] as const,
+};
+
+export function useUsers(filters?: ApiUsersGetData["query"]) {
+ return useQuery({
+ queryKey: userKeys.list(filters),
+ queryFn: () => apiUsersGet({ query: filters }),
+ select: (data) => data.data,
+ });
+}
+
+export function useUserById(id: string) {
+ return useQuery({
+ queryKey: userKeys.detail(id),
+ queryFn: () => apiUsersGet2({ path: { id } } as ApiUsersGet2Data),
+ select: (data) => data.data,
+ enabled: !!id,
+ });
+}
+
+export function useCreateUser() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (data: ApiUsersPostData["body"]) =>
+ apiUsersPost({ body: data }),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: userKeys.lists() });
+ },
+ });
+}
+
+export function useDeleteUser() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (id: string) =>
+ apiUsersDelete({ path: { id } } as ApiUsersDeleteData),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: userKeys.lists() });
+ },
+ });
+}
diff --git a/src/Web/MeAjudaAi.Web.Admin-React/src/lib/api/generated/@tanstack/react-query.gen.ts b/src/Web/MeAjudaAi.Web.Admin-React/src/lib/api/generated/@tanstack/react-query.gen.ts
new file mode 100644
index 000000000..0fcb3b031
--- /dev/null
+++ b/src/Web/MeAjudaAi.Web.Admin-React/src/lib/api/generated/@tanstack/react-query.gen.ts
@@ -0,0 +1,1754 @@
+// This file is auto-generated by @hey-api/openapi-ts
+
+import { type DefaultError, type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/react-query';
+
+import { client } from '../client.gen';
+import { apiActivatePost, apiActivatePost2, apiActivatePost3, apiAllowedCitiesDelete, apiAllowedCitiesGet, apiAllowedCitiesGet2, apiAllowedCitiesPatch, apiAllowedCitiesPost, apiAllowedCitiesPut, apiBecomePost, apiByCityGet, apiByEmailGet, apiByStateGet, apiByTypeGet, apiByUserGet, apiCategoriesDelete, apiCategoriesGet, apiCategoriesGet2, apiCategoriesPost, apiCategoriesPut, apiCategoryGet, apiChangeCategoryPost, apiClientGet, apiCspReportPost, apiDeactivatePost, apiDeactivatePost2, apiDeactivatePost3, apiDocumentsDelete, apiDocumentsPost, apiDocumentsPost2, apiMeGet, apiMePut, apiProfilePut, apiProviderGet, apiProvidersDelete, apiProvidersGet, apiProvidersGet2, apiProvidersGet3, apiProvidersGet4, apiProvidersPost, apiProvidersPut, apiPublicGet, apiRegisterPost, apiRegisterPost2, apiRequestVerificationPost, apiRequireBasicInfoCorrectionPost, apiSearchGet, apiServicesDelete, apiServicesDelete2, apiServicesGet, apiServicesGet2, apiServicesPost, apiServicesPost2, apiServicesPut, apiStatusGet, apiStatusGet2, apiUploadPost, apiUsersDelete, apiUsersGet, apiUsersGet2, apiUsersPost, apiValidatePost, apiVerificationStatusGet, apiVerificationStatusPut, apiVerifyPost, type Options } from '../sdk.gen';
+import type { ApiActivatePost2Data, ApiActivatePost2Response, ApiActivatePost3Data, ApiActivatePost3Response, ApiActivatePostData, ApiActivatePostResponse, ApiAllowedCitiesDeleteData, ApiAllowedCitiesDeleteResponse, ApiAllowedCitiesGet2Data, ApiAllowedCitiesGet2Response, ApiAllowedCitiesGetData, ApiAllowedCitiesGetResponse, ApiAllowedCitiesPatchData, ApiAllowedCitiesPatchError, ApiAllowedCitiesPatchResponse, ApiAllowedCitiesPostData, ApiAllowedCitiesPostError, ApiAllowedCitiesPostResponse, ApiAllowedCitiesPutData, ApiAllowedCitiesPutResponse, ApiBecomePostData, ApiBecomePostError, ApiBecomePostResponse, ApiByCityGetData, ApiByCityGetResponse, ApiByEmailGetData, ApiByEmailGetResponse, ApiByStateGetData, ApiByStateGetResponse, ApiByTypeGetData, ApiByTypeGetResponse, ApiByUserGetData, ApiByUserGetResponse, ApiCategoriesDeleteData, ApiCategoriesDeleteResponse, ApiCategoriesGet2Data, ApiCategoriesGet2Response, ApiCategoriesGetData, ApiCategoriesGetResponse, ApiCategoriesPostData, ApiCategoriesPostResponse, ApiCategoriesPutData, ApiCategoriesPutResponse, ApiCategoryGetData, ApiCategoryGetResponse, ApiChangeCategoryPostData, ApiChangeCategoryPostResponse, ApiClientGetData, ApiClientGetResponse, ApiCspReportPostData, ApiDeactivatePost2Data, ApiDeactivatePost2Response, ApiDeactivatePost3Data, ApiDeactivatePost3Response, ApiDeactivatePostData, ApiDeactivatePostResponse, ApiDocumentsDeleteData, ApiDocumentsDeleteResponse, ApiDocumentsPost2Data, ApiDocumentsPost2Response, ApiDocumentsPostData, ApiDocumentsPostResponse, ApiMeGetData, ApiMeGetResponse, ApiMePutData, ApiMePutResponse, ApiProfilePutData, ApiProfilePutResponse, ApiProviderGetData, ApiProviderGetResponse, ApiProvidersDeleteData, ApiProvidersDeleteResponse, ApiProvidersGet2Data, ApiProvidersGet2Error, ApiProvidersGet2Response, ApiProvidersGet3Data, ApiProvidersGet3Response, ApiProvidersGet4Data, ApiProvidersGet4Error, ApiProvidersGet4Response, ApiProvidersGetData, ApiProvidersPostData, ApiProvidersPostResponse, ApiProvidersPutData, ApiProvidersPutResponse, ApiPublicGetData, ApiPublicGetResponse, ApiRegisterPost2Data, ApiRegisterPostData, ApiRegisterPostResponse, ApiRequestVerificationPostData, ApiRequestVerificationPostError, ApiRequireBasicInfoCorrectionPostData, ApiSearchGetData, ApiSearchGetResponse, ApiServicesDelete2Data, ApiServicesDelete2Response, ApiServicesDeleteData, ApiServicesDeleteResponse, ApiServicesGet2Data, ApiServicesGet2Response, ApiServicesGetData, ApiServicesGetResponse, ApiServicesPost2Data, ApiServicesPost2Response, ApiServicesPostData, ApiServicesPostResponse, ApiServicesPutData, ApiServicesPutResponse, ApiStatusGet2Data, ApiStatusGet2Response, ApiStatusGetData, ApiStatusGetResponse, ApiUploadPostData, ApiUploadPostResponse, ApiUsersDeleteData, ApiUsersDeleteResponse, ApiUsersGet2Data, ApiUsersGet2Response, ApiUsersGetData, ApiUsersGetError, ApiUsersGetResponse, ApiUsersPostData, ApiUsersPostResponse, ApiValidatePostData, ApiValidatePostResponse, ApiVerificationStatusGetData, ApiVerificationStatusGetError, ApiVerificationStatusGetResponse, ApiVerificationStatusPutData, ApiVerificationStatusPutResponse, ApiVerifyPostData } from '../types.gen';
+
+export type QueryKey = [
+ Pick & {
+ _id: string;
+ _infinite?: boolean;
+ tags?: ReadonlyArray;
+ }
+];
+
+const createQueryKey = (id: string, options?: TOptions, infinite?: boolean, tags?: ReadonlyArray): [
+ QueryKey[0]
+] => {
+ const params: QueryKey[0] = { _id: id, baseUrl: options?.baseUrl || (options?.client ?? client).getConfig().baseUrl } as QueryKey[0];
+ if (infinite) {
+ params._infinite = infinite;
+ }
+ if (tags) {
+ params.tags = tags;
+ }
+ if (options?.body) {
+ params.body = options.body;
+ }
+ if (options?.headers) {
+ params.headers = options.headers;
+ }
+ if (options?.path) {
+ params.path = options.path;
+ }
+ if (options?.query) {
+ params.query = options.query;
+ }
+ return [params];
+};
+
+export const apiAllowedCitiesGetQueryKey = (options?: Options) => createQueryKey('apiAllowedCitiesGet', options);
+
+/**
+ * Listar todas as cidades permitidas
+ *
+ * Recupera todas as cidades permitidas (opcionalmente apenas as ativas)
+ */
+export const apiAllowedCitiesGetOptions = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiAllowedCitiesGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiAllowedCitiesGetQueryKey(options)
+});
+
+/**
+ * Criar nova cidade permitida
+ *
+ * Cria uma nova cidade permitida para operações de prestadores (apenas Admin)
+ */
+export const apiAllowedCitiesPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiAllowedCitiesPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Deletar cidade permitida
+ *
+ * Deleta uma cidade permitida
+ */
+export const apiAllowedCitiesDeleteMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiAllowedCitiesDelete({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiAllowedCitiesGet2QueryKey = (options: Options) => createQueryKey('apiAllowedCitiesGet2', options);
+
+/**
+ * Buscar cidade permitida por ID
+ *
+ * Recupera uma cidade permitida específica pelo seu ID
+ */
+export const apiAllowedCitiesGet2Options = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiAllowedCitiesGet2({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiAllowedCitiesGet2QueryKey(options)
+});
+
+/**
+ * Atualizar parcialmente cidade permitida
+ *
+ * Atualiza campos específicos de uma cidade permitida (Raio, Ativo)
+ */
+export const apiAllowedCitiesPatchMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiAllowedCitiesPatch({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Atualizar cidade permitida
+ *
+ * Atualiza uma cidade permitida existente
+ */
+export const apiAllowedCitiesPutMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiAllowedCitiesPut({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiProvidersGetQueryKey = (options?: Options) => createQueryKey('apiProvidersGet', options);
+
+/**
+ * Lista os provedores de identidade social disponíveis.
+ */
+export const apiProvidersGetOptions = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiProvidersGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiProvidersGetQueryKey(options)
+});
+
+export const apiClientGetQueryKey = (options?: Options) => createQueryKey('apiClientGet', options);
+
+/**
+ * Retorna a configuração do cliente.
+ * Apenas informações não-sensíveis são expostas.
+ *
+ * Retorna configurações não-sensíveis necessárias para o frontend (Keycloak, URLs, feature flags)
+ */
+export const apiClientGetOptions = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiClientGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiClientGetQueryKey(options)
+});
+
+/**
+ * Gerar URL de upload com SAS token
+ *
+ * Gera uma URL de upload com SAS token para envio direto ao Azure Blob Storage.
+ *
+ * **Fluxo:**
+ * 1. Valida informações do documento
+ * 2. Gera SAS token com 1 hora de validade
+ * 3. Cria registro de documento no banco com status Uploaded
+ * 4. Retorna URL de upload (com blob name e data de expiração)
+ *
+ * **Tipos de documentos suportados:**
+ * - IdentityDocument: RG, CNH, CPF
+ * - ProofOfResidence: Comprovante de residência
+ * - CriminalRecord: Certidão de antecedentes
+ * - Other: Outros documentos
+ */
+export const apiUploadPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiUploadPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiStatusGetQueryKey = (options: Options) => createQueryKey('apiStatusGet', options);
+
+/**
+ * Consultar status de documento
+ *
+ * Retorna informações detalhadas sobre um documento específico.
+ *
+ * **Informações retornadas:**
+ * - Status atual (Uploaded, PendingVerification, Verified, Rejected, Failed)
+ * - Datas de upload e verificação
+ * - Motivo de rejeição (se aplicável)
+ * - Dados extraídos por OCR (se disponível)
+ * - URLs de acesso ao documento
+ */
+export const apiStatusGetOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiStatusGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiStatusGetQueryKey(options)
+});
+
+export const apiProviderGetQueryKey = (options: Options) => createQueryKey('apiProviderGet', options);
+
+/**
+ * Listar documentos de um prestador
+ *
+ * Retorna todos os documentos associados a um prestador específico.
+ *
+ * **Casos de uso:**
+ * - Visualizar todos os documentos enviados
+ * - Verificar status de verificação de documentos
+ * - Acompanhar progresso de validação de cadastro
+ */
+export const apiProviderGetOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiProviderGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiProviderGetQueryKey(options)
+});
+
+/**
+ * Solicitar verificação manual
+ *
+ * Solicita verificação manual de um documento quando OCR falha ou precisa validação adicional.
+ *
+ * **Quando usar:**
+ * - OCR não conseguiu extrair dados do documento
+ * - Documento foi rejeitado automaticamente mas precisa revisão
+ * - Necessidade de validação humana adicional
+ *
+ * **Resultado:**
+ * - Documento entra em fila de verificação manual
+ * - Status alterado para PendingVerification
+ * - Administrador será notificado para análise
+ */
+export const apiRequestVerificationPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiRequestVerificationPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Aprovar ou rejeitar documento
+ *
+ * Aprova ou rejeita um documento após verificação manual.
+ *
+ * **Aprovar documento:**
+ * ```json
+ * {
+ * "IsVerified": true,
+ * "VerificationNotes": "Documento válido e legível"
+ * }
+ * ```
+ *
+ * **Rejeitar documento:**
+ * ```json
+ * {
+ * "IsVerified": false,
+ * "VerificationNotes": "Documento ilegível ou inválido"
+ * }
+ * ```
+ *
+ * **Requisitos:**
+ * - Documento deve estar em status PendingVerification
+ * - Apenas administradores podem executar esta ação
+ */
+export const apiVerifyPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiVerifyPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiSearchGetQueryKey = (options: Options) => createQueryKey('apiSearchGet', options);
+
+/**
+ * Busca cidades/endereços para cadastro
+ *
+ * Retorna candidatos de localização baseados na query
+ */
+export const apiSearchGetOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiSearchGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiSearchGetQueryKey(options)
+});
+
+export const apiProvidersGet2QueryKey = (options?: Options) => createQueryKey('apiProvidersGet2', options);
+
+/**
+ * Consultar prestadores paginados
+ *
+ * Recupera uma lista paginada de prestadores de serviços do sistema com suporte a filtros de busca.
+ *
+ * **Características:**
+ * - 🔍 Busca por nome, tipo de serviço e status de verificação
+ * - 📄 Paginação otimizada com metadados
+ * - ⚡ Cache automático para consultas frequentes
+ * - 🔒 Controle de acesso baseado em papéis
+ * - 🌍 Restrição geográfica (piloto em cidades específicas)
+ *
+ * **Restrição geográfica (HTTP 451):**
+ *
+ * Este endpoint está sujeito a restrições geográficas durante a fase piloto.
+ * O acesso é permitido apenas para usuários nas seguintes cidades:
+ *
+ * - **Muriaé** (MG) - IBGE: 3143906
+ * - **Itaperuna** (RJ) - IBGE: 3302205
+ * - **Linhares** (ES) - IBGE: 3203205
+ *
+ * A localização é determinada através dos headers HTTP:
+ * - `X-User-City`: Nome da cidade
+ * - `X-User-State`: Sigla do estado (UF)
+ * - `X-User-Location`: Combinação "cidade|estado"
+ *
+ * Se o acesso for bloqueado, você receberá HTTP 451 com detalhes:
+ * - Sua localização detectada
+ * - Lista de cidades permitidas
+ * - Códigos IBGE para validação
+ *
+ * **Parâmetros de busca:**
+ * - `name`: Termo para filtrar prestadores por nome
+ * - `type`: Filtro por tipo de serviço (ID numérico)
+ * - `verificationStatus`: Status de verificação (ID numérico)
+ * - `pageNumber`: Número da página (padrão: 1)
+ * - `pageSize`: Tamanho da página (padrão: 10, máximo: 100)
+ *
+ * **Exemplos de uso:**
+ * - Buscar prestadores: `?name=joão`
+ * - Por tipo: `?type=1`
+ * - Por status: `?verificationStatus=2`
+ * - Paginação: `?pageNumber=2&pageSize=20`
+ * - Combinado: `?name=médico&type=1&pageNumber=1&pageSize=10`
+ */
+export const apiProvidersGet2Options = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiProvidersGet2({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiProvidersGet2QueryKey(options)
+});
+
+/**
+ * Processa requisição de criação de prestador de forma assíncrona.
+ *
+ * Fluxo de execução:
+ * 1. Converte request em comando CQRS
+ * 2. Envia comando através do dispatcher
+ * 3. Processa resultado e retorna resposta HTTP apropriada
+ * 4. Inclui localização do recurso criado no header
+ */
+export const apiProvidersPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiProvidersPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Processa requisição de exclusão de prestador de forma assíncrona.
+ *
+ * Fluxo de execução:
+ * 1. Valida ID do prestador e autorização administrativa
+ * 2. Cria comando usando mapper ToDeleteCommand
+ * 3. Envia comando através do dispatcher
+ * 4. Processa resultado e retorna status apropriado
+ * 5. Registra evento de auditoria (futuro)
+ */
+export const apiProvidersDeleteMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiProvidersDelete({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiProvidersGet3QueryKey = (options: Options) => createQueryKey('apiProvidersGet3', options);
+
+/**
+ * Implementa a lógica de consulta de prestador por ID.
+ *
+ * Processo da consulta:
+ * 1. Valida ID do prestador no formato GUID
+ * 2. Cria query usando mapper ToQuery
+ * 3. Envia query através do dispatcher CQRS
+ * 4. Retorna resposta HTTP com dados do prestador ou NotFound
+ */
+export const apiProvidersGet3Options = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiProvidersGet3({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiProvidersGet3QueryKey(options)
+});
+
+/**
+ * Processa requisição de atualização de perfil de forma assíncrona.
+ *
+ * Fluxo de execução:
+ * 1. Valida ID do prestador e autorização
+ * 2. Converte request em comando CQRS
+ * 3. Envia comando através do dispatcher
+ * 4. Processa resultado e retorna resposta HTTP apropriada
+ */
+export const apiProvidersPutMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiProvidersPut({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiPublicGetQueryKey = (options: Options) => createQueryKey('apiPublicGet', options);
+
+/**
+ * Consultar perfil público do prestador
+ *
+ * Recupera dados públicos e seguros de um prestador para exibição no site.
+ * Não requer autenticação. Aceita ID (GUID) ou slug amigável (ex.: "joao-silva-a1b2c3d4").
+ *
+ * **Dados Retornados:**
+ * - Informações básicas (Nome, Fantasia, Descrição)
+ * - Localização aproximada (Cidade/Estado)
+ * - Avaliação média e contagem de reviews
+ * - Lista de serviços oferecidos (Nota: esta lista será vazia se a configuração PublicProfilePrivacy do provedor estiver ativa e o solicitante for anônimo)
+ *
+ * **Dados Ocultados (Privacidade):**
+ * - Documentos (CPF/CNPJ)
+ * - Endereço completo (Rua/Número)
+ * - Dados de auditoria interna
+ */
+export const apiPublicGetOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiPublicGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiPublicGetQueryKey(options)
+});
+
+/**
+ * Tornar-se prestador (usuário já autenticado)
+ *
+ * Transforma o usuário autenticado em um prestador de serviços. Requer token de usuário.
+ */
+export const apiBecomePostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiBecomePost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiByUserGetQueryKey = (options: Options) => createQueryKey('apiByUserGet', options);
+
+/**
+ * Implementa a lógica de consulta de prestador por ID do usuário.
+ *
+ * Processo da consulta:
+ * 1. Valida ID do usuário no formato GUID
+ * 2. Cria query usando mapper ToUserQuery
+ * 3. Envia query através do dispatcher CQRS
+ * 4. Retorna resposta HTTP com dados do prestador ou NotFound
+ */
+export const apiByUserGetOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiByUserGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiByUserGetQueryKey(options)
+});
+
+export const apiByCityGetQueryKey = (options: Options) => createQueryKey('apiByCityGet', options);
+
+/**
+ * Implementa a lógica de consulta de prestadores por cidade.
+ *
+ * Processo da consulta:
+ * 1. Valida parâmetro de cidade
+ * 2. Cria query usando mapper ToCityQuery
+ * 3. Envia query através do dispatcher CQRS
+ * 4. Retorna resposta HTTP com lista de prestadores
+ */
+export const apiByCityGetOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiByCityGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiByCityGetQueryKey(options)
+});
+
+export const apiByStateGetQueryKey = (options: Options) => createQueryKey('apiByStateGet', options);
+
+/**
+ * Implementa a lógica de consulta de prestadores por estado.
+ *
+ * Processo da consulta:
+ * 1. Valida parâmetro de estado
+ * 2. Cria query usando mapper ToStateQuery
+ * 3. Envia query através do dispatcher CQRS
+ * 4. Retorna resposta HTTP com lista de prestadores
+ */
+export const apiByStateGetOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiByStateGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiByStateGetQueryKey(options)
+});
+
+export const apiByTypeGetQueryKey = (options: Options) => createQueryKey('apiByTypeGet', options);
+
+/**
+ * Implementa a lógica de consulta de prestadores por tipo.
+ *
+ * Processo da consulta:
+ * 1. Valida enum de tipo do prestador
+ * 2. Cria query usando mapper ToTypeQuery
+ * 3. Envia query através do dispatcher CQRS
+ * 4. Retorna resposta HTTP com lista de prestadores
+ */
+export const apiByTypeGetOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiByTypeGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiByTypeGetQueryKey(options)
+});
+
+export const apiVerificationStatusGetQueryKey = (options: Options) => createQueryKey('apiVerificationStatusGet', options);
+
+/**
+ * Implementa a lógica de consulta de prestadores por status de verificação.
+ *
+ * Processo da consulta:
+ * 1. Valida enum de status de verificação
+ * 2. Cria query usando mapper ToVerificationStatusQuery
+ * 3. Envia query através do dispatcher CQRS
+ * 4. Retorna resposta HTTP com lista de prestadores
+ */
+export const apiVerificationStatusGetOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiVerificationStatusGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiVerificationStatusGetQueryKey(options)
+});
+
+/**
+ * Processa requisição de adição de documento de forma assíncrona.
+ *
+ * Fluxo de execução:
+ * 1. Valida ID do prestador e autorização
+ * 2. Converte request em comando CQRS
+ * 3. Envia comando através do dispatcher
+ * 4. Processa resultado e retorna prestador atualizado
+ */
+export const apiDocumentsPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiDocumentsPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Processa requisição de remoção de documento de forma assíncrona.
+ *
+ * Fluxo de execução:
+ * 1. Valida ID do prestador e autorização
+ * 2. Cria comando usando mapper ToRemoveDocumentCommand
+ * 3. Envia comando através do dispatcher
+ * 4. Processa resultado e retorna prestador atualizado
+ */
+export const apiDocumentsDeleteMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiDocumentsDelete({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Processa requisição de atualização de status de forma assíncrona.
+ *
+ * Fluxo de execução:
+ * 1. Valida ID do prestador e autorização administrativa
+ * 2. Converte request em comando CQRS
+ * 3. Envia comando através do dispatcher
+ * 4. Processa resultado e retorna prestador atualizado
+ * 5. Registra evento de auditoria (futuro)
+ */
+export const apiVerificationStatusPutMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiVerificationStatusPut({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Processa requisição de solicitação de correção de forma assíncrona.
+ *
+ * Fluxo de execução:
+ * 1. Valida ID do prestador e autorização
+ * 2. Extrai identidade do usuário autenticado do contexto HTTP
+ * 3. Converte request em comando CQRS com identidade verificada
+ * 4. Envia comando através do dispatcher
+ * 5. Processa resultado e retorna confirmação
+ * 6. Emite evento de domínio para notificação
+ */
+export const apiRequireBasicInfoCorrectionPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiRequireBasicInfoCorrectionPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiMeGetQueryKey = (options?: Options) => createQueryKey('apiMeGet', options);
+
+export const apiMeGetOptions = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiMeGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiMeGetQueryKey(options)
+});
+
+export const apiMePutMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiMePut({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Upload de documento pelo próprio prestador
+ *
+ * Permite que o prestador adicione documentos ao seu próprio perfil.
+ */
+export const apiDocumentsPost2Mutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiDocumentsPost2({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiStatusGet2QueryKey = (options?: Options) => createQueryKey('apiStatusGet2', options);
+
+/**
+ * Status de aprovação do prestador
+ *
+ * Retorna o status atual de aprovação e tier do prestador autenticado.
+ */
+export const apiStatusGet2Options = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiStatusGet2({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiStatusGet2QueryKey(options)
+});
+
+export const apiActivatePostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiActivatePost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiDeactivatePostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiDeactivatePost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Auto-registro de prestador de serviços
+ *
+ * Inicia o cadastro de um prestador. Cria usuário no Keycloak com role 'provider-standard' e a entidade Provider com Tier=Standard. Endpoint público, sem autenticação.
+ */
+export const apiRegisterPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiRegisterPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Processa requisição de remoção de serviço do provider.
+ *
+ * ### Remove um serviço do catálogo do provider
+ *
+ * **Funcionalidades:**
+ * - ✅ Remove associação entre provider e serviço
+ * - ✅ Emite evento de domínio ProviderServiceRemovedDomainEvent
+ * - ✅ Valida que o provider oferece o serviço antes de remover
+ *
+ * **Campos obrigatórios:**
+ * - providerId: ID do provider (UUID)
+ * - serviceId: ID do serviço do catálogo (UUID)
+ *
+ * **Validações:**
+ * - Provider deve existir
+ * - Provider deve oferecer o serviço
+ * - Provider não pode estar deletado
+ */
+export const apiServicesDeleteMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiServicesDelete({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Processa requisição de adição de serviço ao provider.
+ *
+ * ### Adiciona um serviço do catálogo ao provider
+ *
+ * **Funcionalidades:**
+ * - ✅ Valida existência e status do serviço via IServiceCatalogsModuleApi
+ * - ✅ Verifica se o serviço está ativo
+ * - ✅ Previne duplicação de serviços
+ * - ✅ Emite evento de domínio ProviderServiceAddedDomainEvent
+ *
+ * **Campos obrigatórios:**
+ * - providerId: ID do provider (UUID)
+ * - serviceId: ID do serviço do catálogo (UUID)
+ *
+ * **Validações:**
+ * - Serviço deve existir no catálogo
+ * - Serviço deve estar ativo
+ * - Provider não pode já oferecer o serviço
+ * - Provider não pode estar deletado
+ */
+export const apiServicesPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiServicesPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiProvidersGet4QueryKey = (options: Options) => createQueryKey('apiProvidersGet4', options);
+
+/**
+ * Buscar prestadores de serviço
+ *
+ * Busca prestadores de serviço ativos com base em geolocalização e filtros.
+ *
+ * **Algoritmo de Busca:**
+ * 1. Filtrar por raio a partir da localização de busca
+ * 2. Aplicar filtro textual (nome, descrição) se fornecido
+ * 3. Aplicar filtros opcionais (serviços, avaliação, nível de assinatura)
+ * 4. Classificar resultados por:
+ * - Nível de assinatura (Platinum > Gold > Standard > Free)
+ * - Avaliação média (maior primeiro)
+ * - Distância (mais próximo primeiro)
+ *
+ * **Casos de Uso:**
+ * - Encontrar prestadores próximos a uma localização específica
+ * - Buscar prestadores por nome ou termo (ex: "Eletricista", "João")
+ * - Buscar prestadores que oferecem serviços específicos
+ * - Filtrar por avaliação mínima ou nível de assinatura
+ */
+export const apiProvidersGet4Options = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiProvidersGet4({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiProvidersGet4QueryKey(options)
+});
+
+const createInfiniteParams = [0], 'body' | 'headers' | 'path' | 'query'>>(queryKey: QueryKey, page: K) => {
+ const params = { ...queryKey[0] };
+ if (page.body) {
+ params.body = {
+ ...queryKey[0].body as any,
+ ...page.body as any
+ };
+ }
+ if (page.headers) {
+ params.headers = {
+ ...queryKey[0].headers,
+ ...page.headers
+ };
+ }
+ if (page.path) {
+ params.path = {
+ ...queryKey[0].path as any,
+ ...page.path as any
+ };
+ }
+ if (page.query) {
+ params.query = {
+ ...queryKey[0].query as any,
+ ...page.query as any
+ };
+ }
+ return params as unknown as typeof page;
+};
+
+export const apiProvidersGet4InfiniteQueryKey = (options: Options): QueryKey> => createQueryKey('apiProvidersGet4', options, true);
+
+/**
+ * Buscar prestadores de serviço
+ *
+ * Busca prestadores de serviço ativos com base em geolocalização e filtros.
+ *
+ * **Algoritmo de Busca:**
+ * 1. Filtrar por raio a partir da localização de busca
+ * 2. Aplicar filtro textual (nome, descrição) se fornecido
+ * 3. Aplicar filtros opcionais (serviços, avaliação, nível de assinatura)
+ * 4. Classificar resultados por:
+ * - Nível de assinatura (Platinum > Gold > Standard > Free)
+ * - Avaliação média (maior primeiro)
+ * - Distância (mais próximo primeiro)
+ *
+ * **Casos de Uso:**
+ * - Encontrar prestadores próximos a uma localização específica
+ * - Buscar prestadores por nome ou termo (ex: "Eletricista", "João")
+ * - Buscar prestadores que oferecem serviços específicos
+ * - Filtrar por avaliação mínima ou nível de assinatura
+ */
+export const apiProvidersGet4InfiniteOptions = (options: Options) => infiniteQueryOptions, QueryKey>, number | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>(
+// @ts-ignore
+{
+ queryFn: async ({ pageParam, queryKey, signal }) => {
+ // @ts-ignore
+ const page: Pick>[0], 'body' | 'headers' | 'path' | 'query'> = typeof pageParam === 'object' ? pageParam : {
+ query: {
+ page: pageParam
+ }
+ };
+ const params = createInfiniteParams(queryKey, page);
+ const { data } = await apiProvidersGet4({
+ ...options,
+ ...params,
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiProvidersGet4InfiniteQueryKey(options)
+});
+
+/**
+ * Recebe e registra violações de CSP.
+ */
+export const apiCspReportPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiCspReportPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiCategoriesGetQueryKey = (options?: Options) => createQueryKey('apiCategoriesGet', options);
+
+/**
+ * Listar todas as categorias
+ *
+ * Retorna todas as categorias de serviços do catálogo.
+ *
+ * **Filtros Opcionais:**
+ * - `activeOnly` (bool): Filtra apenas categorias ativas (padrão: false)
+ *
+ * **Ordenação:**
+ * - Categorias são ordenadas por DisplayOrder (crescente)
+ *
+ * **Casos de Uso:**
+ * - Exibir menu de categorias para usuários
+ * - Administração do catálogo de categorias
+ * - Seleção de categoria ao criar serviço
+ */
+export const apiCategoriesGetOptions = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiCategoriesGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiCategoriesGetQueryKey(options)
+});
+
+/**
+ * Criar categoria de serviço
+ *
+ * Cria uma nova categoria de serviços no catálogo.
+ *
+ * **Validações:**
+ * - Nome é obrigatório (máximo 100 caracteres)
+ * - Descrição opcional (máximo 500 caracteres)
+ * - DisplayOrder deve ser >= 0
+ * - Nome deve ser único no sistema
+ *
+ * **Efeitos:**
+ * - Categoria criada como ativa por padrão
+ * - Pode receber serviços imediatamente
+ *
+ * **Permissões:** Requer privilégios de administrador
+ */
+export const apiCategoriesPostMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiCategoriesPost({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Deletar categoria de serviço
+ *
+ * Deleta uma categoria de serviços permanentemente.
+ *
+ * **Validações:**
+ * - ID não pode ser vazio
+ * - Categoria deve existir
+ * - Categoria não pode ter serviços associados
+ *
+ * **Importante:** Operação destrutiva. Categorias com serviços não podem
+ * ser deletadas. Use desativação ou mova os serviços primeiro.
+ *
+ * **Permissões:** Requer privilégios de administrador
+ */
+export const apiCategoriesDeleteMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiCategoriesDelete({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiCategoriesGet2QueryKey = (options: Options) => createQueryKey('apiCategoriesGet2', options);
+
+/**
+ * Buscar categoria por ID
+ *
+ * Retorna os detalhes completos de uma categoria específica.
+ *
+ * **Retorno:**
+ * - Informações completas da categoria
+ * - Status de ativação
+ * - DisplayOrder para ordenação
+ * - Datas de criação e atualização
+ *
+ * **Casos de Uso:**
+ * - Exibir detalhes da categoria para edição
+ * - Validar existência de categoria
+ * - Visualizar informações completas
+ */
+export const apiCategoriesGet2Options = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiCategoriesGet2({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiCategoriesGet2QueryKey(options)
+});
+
+/**
+ * Atualizar categoria de serviço
+ *
+ * Atualiza as informações de uma categoria existente.
+ *
+ * **Validações:**
+ * - ID não pode ser vazio
+ * - Categoria deve existir
+ * - Nome é obrigatório (máximo 100 caracteres)
+ * - Descrição opcional (máximo 500 caracteres)
+ * - DisplayOrder deve ser >= 0
+ *
+ * **Nota:** Requer atualização completa (full-update pattern).
+ * Todos os campos devem ser fornecidos.
+ *
+ * **Permissões:** Requer privilégios de administrador
+ */
+export const apiCategoriesPutMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiCategoriesPut({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Ativar categoria de serviço
+ *
+ * Ativa uma categoria de serviços.
+ *
+ * **Efeitos:**
+ * - Categoria fica visível em listagens públicas
+ * - Permite criação de novos serviços nesta categoria
+ * - Serviços existentes na categoria voltam a ser acessíveis
+ *
+ * **Permissões:** Requer privilégios de administrador
+ */
+export const apiActivatePost2Mutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiActivatePost2({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Desativar categoria de serviço
+ *
+ * Desativa uma categoria de serviços.
+ *
+ * **Efeitos:**
+ * - Categoria não aparece em listagens públicas
+ * - Impede criação de novos serviços nesta categoria
+ * - Serviços existentes permanecem no sistema (soft-delete)
+ *
+ * **Nota:** Preferível à deleção quando há serviços associados.
+ *
+ * **Permissões:** Requer privilégios de administrador
+ */
+export const apiDeactivatePost2Mutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await apiDeactivatePost2({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const apiServicesGetQueryKey = (options?: Options) => createQueryKey('apiServicesGet', options);
+
+/**
+ * Listar todos os serviços
+ *
+ * Retorna todos os serviços do catálogo.
+ *
+ * **Filtros Opcionais:**
+ * - `activeOnly` (bool): Filtra apenas serviços ativos (padrão: false)
+ *
+ * **Casos de Uso:**
+ * - Listar todo o catálogo de serviços
+ * - Obter apenas serviços ativos para exibição pública
+ * - Administração do catálogo completo
+ */
+export const apiServicesGetOptions = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await apiServicesGet({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: apiServicesGetQueryKey(options)
+});
+
+/**
+ * Criar serviço
+ *
+ * Cria um novo serviço no catálogo.
+ *
+ * **Validações:**
+ * - Nome é obrigatório (máximo 150 caracteres)
+ * - Descrição opcional (máximo 1000 caracteres)
+ * - DisplayOrder deve ser >= 0
+ * - Categoria deve existir e estar ativa
+ *
+ * **Permissões:** Requer privilégios de administrador
+ */
+export const apiServicesPost2Mutation = (options?: Partial