├── .gitignore ├── README.md ├── api ├── ApiConfig.ts ├── axios.ts ├── config.ts └── responses │ └── PaginatedResponse.ts ├── auth ├── AuthContext.tsx ├── AuthProvider.tsx └── TokenService.ts ├── components ├── dialogs │ └── ConfirDialog.tsx ├── form-components │ ├── autocomplate-input │ │ └── AutocompleteInput.tsx │ └── text-field │ │ ├── FormTextField.tsx │ │ └── FormTextProps.ts └── tools │ └── validators.ts ├── enums └── drawerFormType.ts ├── hooks ├── useAuth.ts ├── useDebounce.ts ├── useDebouncedForFunc.ts ├── useFormManager.ts └── useGenericGrid.ts ├── index.ts ├── models ├── AuthContextType.ts └── QueryParams.ts ├── package-lock.json ├── package.json └── services ├── GenericService.ts ├── IGenericModel.ts └── IGenericService.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Core 2 | 3 | A modular, developer-friendly React boilerplate built to reduce repetitive code and speed up project setup. 4 | 5 | # Features 6 | 7 | - Modular and scalable architecture 8 | - Built-in form manager (`useFormManager`) 9 | - Generic data grid hook (`useGenericGrid`) 10 | - Axios interceptor with refresh token support 11 | - Authentication context with provider 12 | - TypeScript-based reusable form components 13 | - Generic service layer for API communication 14 | - Easy-to-extend structure for rapid development 15 | 16 | # Why use this? 17 | 18 | This core module was created to avoid writing the same boilerplate code in every React project. It helps you: 19 | 20 | - Save time when starting a new project 21 | - Maintain clean, organized code 22 | - Focus more on business logic rather than setup 23 | 24 | # Installation 25 | 26 | Clone the repository: 27 | 28 | git clone https://github.com/adnanertorer/react-core.git 29 | -------------------------------------------------------------------------------- /api/ApiConfig.ts: -------------------------------------------------------------------------------- 1 | export interface ApiConfig { 2 | apiUrl: string; 3 | } -------------------------------------------------------------------------------- /api/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { toast } from "react-toastify"; 3 | import { getApiConfig } from "./config"; 4 | import { clearToken, getAccessToken, getRefreshToken, setToken } from "../auth/TokenService"; 5 | 6 | const { apiUrl } = getApiConfig(); 7 | 8 | const api = axios.create({ 9 | baseURL: apiUrl, 10 | }); 11 | 12 | let isRefreshing = false; 13 | 14 | type FailedRequest = { 15 | resolve: (token: string | null) => void; 16 | reject: (error: TError) => void; 17 | }; 18 | 19 | let failedQueue: FailedRequest[] = []; 20 | 21 | const processQueue = (error: AxiosError | Error | null, token: string | null = null) => { 22 | failedQueue.forEach((prom) => { 23 | if (error) { 24 | prom.reject(error); 25 | } else { 26 | prom.resolve(token); 27 | } 28 | }); 29 | failedQueue = []; 30 | }; 31 | 32 | api.interceptors.request.use( 33 | (config) => { 34 | const token = getAccessToken(); 35 | if (token) { 36 | config.headers.Authorization = `Bearer ${token}`; 37 | } 38 | return config; 39 | }, 40 | (error) => { 41 | return Promise.reject(error); 42 | } 43 | ); 44 | 45 | api.interceptors.response.use( 46 | (res) => res, 47 | async (err) => { 48 | const originalRequest = err.config; 49 | 50 | // Token expired ve retry edilmemişse 51 | if (err.response?.status === 401 && !originalRequest._retry) { 52 | originalRequest._retry = true; 53 | 54 | if (isRefreshing) { 55 | return new Promise((resolve, reject) => { 56 | failedQueue.push({ 57 | resolve: (token: string) => { 58 | originalRequest.headers.Authorization = `Bearer ${token}`; 59 | resolve(api(originalRequest)); 60 | }, 61 | reject, 62 | }); 63 | }); 64 | } 65 | 66 | isRefreshing = true; 67 | 68 | try { 69 | const refreshToken = getRefreshToken(); 70 | 71 | const response = await axios.post('auth/refreshtoken', { 72 | refreshToken, 73 | }); 74 | 75 | const { accessToken, refreshToken: newRefreshToken } = response.data; 76 | setToken(accessToken, newRefreshToken); 77 | processQueue(null, accessToken); 78 | 79 | originalRequest.headers.Authorization = `Bearer ${accessToken}`; 80 | return api(originalRequest); 81 | } catch (refreshError) { 82 | processQueue(refreshError as AxiosError | Error | null, null); 83 | clearToken(); 84 | window.location.href = '/login'; 85 | return Promise.reject(refreshError); 86 | } finally { 87 | isRefreshing = false; 88 | } 89 | } else if (err.response?.status === 400) { 90 | handle400Error(err.response.data); 91 | return Promise.resolve(undefined); 92 | } 93 | 94 | return Promise.reject(err); 95 | } 96 | 97 | ); 98 | 99 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 100 | function handle400Error(data: any) { 101 | if (Array.isArray(data)) { 102 | data.forEach((res: { code: string; name: string }) => { 103 | toast.error(`${res.code}: ${res.name}`); 104 | }); 105 | } else if (data?.errors && Array.isArray(data.errors)) { 106 | data.errors.forEach((res: { code: string; name: string }) => { 107 | toast.error(`${res.code}: ${res.name}`); 108 | }); 109 | } else if (data?.code && data?.name) { 110 | toast.error(`${data.code}: ${data.name}`); 111 | } else { 112 | toast.error('Geçersiz istek. Lütfen bilgilerinizi kontrol edin.'); 113 | console.error('Unhandled 400 error structure:', data); 114 | } 115 | } 116 | 117 | 118 | export default api; 119 | -------------------------------------------------------------------------------- /api/config.ts: -------------------------------------------------------------------------------- 1 | // api/config.ts 2 | import type { ApiConfig } from "./ApiConfig"; 3 | 4 | let config: ApiConfig; 5 | 6 | export const setApiConfig = (conf: ApiConfig) => { 7 | config = conf; 8 | }; 9 | 10 | export const getApiConfig = (): ApiConfig => { 11 | if (!config) { 12 | throw new Error("ApiConfig has not been set. Call setApiConfig() first."); 13 | } 14 | return config; 15 | }; 16 | -------------------------------------------------------------------------------- /api/responses/PaginatedResponse.ts: -------------------------------------------------------------------------------- 1 | export interface PaginatedResponse { 2 | pageItems: T[]; 3 | pageNumber: number; 4 | totalPages: number; 5 | totalCount: number; 6 | pageSize: number; 7 | hasPreviousPage: boolean; 8 | hasNextPage: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /auth/AuthContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | import type { AuthContextType } from "../models/AuthContextType"; 3 | 4 | export const AuthContext = createContext(undefined); -------------------------------------------------------------------------------- /auth/AuthProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { getAccessToken, setToken, clearToken } from './TokenService'; 3 | import { AuthContext } from './AuthContext'; 4 | 5 | 6 | export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { 7 | const [isAuthenticated, setIsAuthenticated] = useState(!!getAccessToken()); 8 | 9 | const login = (accessToken: string, refreshToken: string) => { 10 | setToken(accessToken, refreshToken); 11 | setIsAuthenticated(true); 12 | }; 13 | 14 | const logout = () => { 15 | clearToken(); 16 | setIsAuthenticated(false); 17 | }; 18 | 19 | useEffect(() => { 20 | setIsAuthenticated(!!getAccessToken()); 21 | }, []); 22 | 23 | return ( 24 | 25 | {children} 26 | 27 | ); 28 | }; 29 | 30 | 31 | -------------------------------------------------------------------------------- /auth/TokenService.ts: -------------------------------------------------------------------------------- 1 | export const getAccessToken = () : string | null => localStorage.getItem('access_token'); 2 | export const getRefreshToken = () : string | null => localStorage.getItem('refresh_token'); 3 | 4 | export const setToken = (accessToken: string, refreshToken: string) => { 5 | localStorage.setItem('access_token', accessToken); 6 | localStorage.setItem('refresh_token', refreshToken); 7 | }; 8 | 9 | export const clearToken = () => { 10 | localStorage.removeItem('access_token'); 11 | localStorage.removeItem('refresh_token'); 12 | } -------------------------------------------------------------------------------- /components/dialogs/ConfirDialog.tsx: -------------------------------------------------------------------------------- 1 | // src/components/dialogs/ConfirmDialog.tsx 2 | "use client"; 3 | 4 | import * as React from "react"; 5 | import Dialog from "@mui/material/Dialog"; 6 | import DialogActions from "@mui/material/DialogActions"; 7 | import DialogContent from "@mui/material/DialogContent"; 8 | import DialogContentText from "@mui/material/DialogContentText"; 9 | import DialogTitle from "@mui/material/DialogTitle"; 10 | import Button from "@mui/material/Button"; 11 | 12 | interface ConfirmDialogProps { 13 | open: boolean; 14 | title?: string; 15 | content?: string; 16 | confirmText?: string; 17 | cancelText?: string; 18 | onConfirm: () => void; 19 | onCancel: () => void; 20 | } 21 | 22 | export const ConfirmDialog: React.FC = ({ 23 | open, 24 | title = "Onayla", 25 | content = "Bu işlemi gerçekleştirmek istediğinize emin misiniz?", 26 | confirmText = "Evet", 27 | cancelText = "İptal", 28 | onConfirm, 29 | onCancel, 30 | }) => { 31 | return ( 32 | 36 | {title} 37 | 38 | 39 | {content} 40 | 41 | 42 | 43 | 46 | 49 | 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /components/form-components/autocomplate-input/AutocompleteInput.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | Autocomplete, 5 | CircularProgress, 6 | TextField, 7 | } from "@mui/material"; 8 | 9 | interface AutocompleteInputProps { 10 | label: string; 11 | value: T | null; 12 | onChange: (value: T | null) => void; 13 | options: { label: string; value: T }[]; 14 | disabled?: boolean; 15 | loading?: boolean; 16 | placeholder?: string; 17 | noOptionsText?: string; 18 | } 19 | 20 | export function AutocompleteInput({ 21 | label, 22 | value, 23 | onChange, 24 | options, 25 | disabled = false, 26 | loading = false, 27 | placeholder = "Seçim yapın...", 28 | noOptionsText = "Seçenek bulunamadı", 29 | }: AutocompleteInputProps) { 30 | return ( 31 | option.label} 34 | isOptionEqualToValue={(option, value) => option.value === value.value} 35 | value={options.find((o) => o.value === value) || null} 36 | onChange={(_, newValue) => onChange(newValue ? newValue.value : null)} 37 | loading={loading} 38 | disabled={disabled} 39 | noOptionsText={noOptionsText} 40 | renderInput={(params) => ( 41 | 49 | {loading ? : null} 50 | {params.InputProps.endAdornment} 51 | 52 | ), 53 | }} 54 | /> 55 | )} 56 | /> 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /components/form-components/text-field/FormTextField.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import TextField from "@mui/material/TextField"; 3 | import InputAdornment from "@mui/material/InputAdornment"; 4 | import { FormTextProps } from "./FormTextProps"; 5 | import IconButton from "@mui/material/IconButton"; 6 | import ClearIcon from "@mui/icons-material/Clear"; 7 | import { FormHelperText } from "@mui/material"; 8 | 9 | export const FormTextField: React.FC = ({ 10 | label, 11 | name, 12 | value = "", 13 | onChange, 14 | placeholder, 15 | disabled = false, 16 | readOnly = false, 17 | required = false, 18 | error, 19 | allErrors, 20 | type = "text", 21 | maxLength, 22 | minLength, 23 | pattern, 24 | autoFocus, 25 | className = "", 26 | helperText, 27 | fullWidth = false, 28 | size = "medium", 29 | variant = "outlined", 30 | inputMode, 31 | multiline = false, 32 | rows = 3, 33 | startAdornment, 34 | endAdornment, 35 | onBlur, 36 | onFocus, 37 | debounceDelay = 300, 38 | onDebouncedChange, 39 | customValidator, 40 | theme = "light", 41 | startIcon, 42 | endIcon, 43 | clearable = false, 44 | showErrorOnlyIfNotEmpty, 45 | }) => { 46 | const [internalValue, setInternalValue] = useState(value); 47 | const [internalError, setInternalError] = useState( 48 | error 49 | ); 50 | const debounceTimer = useRef(null); 51 | 52 | useEffect(() => { 53 | setInternalValue(value); 54 | }, [value]); 55 | 56 | useEffect(() => { 57 | setInternalError(error); 58 | }, [error]); 59 | 60 | const handleChange = ( 61 | e: React.ChangeEvent 62 | ) => { 63 | const newValue = e.target.value; 64 | setInternalValue(newValue); 65 | 66 | if (typeof onChange === "function") { 67 | if (onChange.length === 1 && typeof newValue === "string") { 68 | (onChange as (value: string) => void)(newValue); 69 | } else { 70 | ( 71 | onChange as ( 72 | e: React.ChangeEvent 73 | ) => void 74 | )(e); 75 | } 76 | } 77 | 78 | if (customValidator) { 79 | const validationError = customValidator(newValue); 80 | setInternalError(validationError); 81 | } 82 | 83 | if (onDebouncedChange) { 84 | if (debounceTimer.current) clearTimeout(debounceTimer.current); 85 | debounceTimer.current = setTimeout(() => { 86 | onDebouncedChange(newValue); 87 | }, debounceDelay); 88 | } 89 | }; 90 | 91 | const handleClear = () => { 92 | const event = { 93 | target: { 94 | name, 95 | value: "", 96 | }, 97 | } as unknown as React.ChangeEvent; 98 | handleChange(event); 99 | }; 100 | 101 | const isEmpty = 102 | internalValue === null || 103 | internalValue === undefined || 104 | internalValue === "" || 105 | (Array.isArray(internalValue) && internalValue.length === 0); 106 | 107 | const shouldShow = !showErrorOnlyIfNotEmpty || !isEmpty; 108 | 109 | const displayedError = shouldShow ? error || internalError : null; 110 | 111 | return ( 112 |
115 | 145 | {startAdornment || startIcon} 146 | 147 | ), 148 | endAdornment: (clearable || endAdornment || endIcon) && ( 149 | 150 |
151 | {endAdornment || endIcon} 152 | {clearable && internalValue && ( 153 | 158 | 159 | 160 | )} 161 |
162 |
163 | ), 164 | }} 165 | /> 166 | {allErrors && allErrors.length > 1 && shouldShow && ( 167 | 168 |
    169 | {allErrors.map((err, i) => ( 170 |
  • {err}
  • 171 | ))} 172 |
173 |
174 | )} 175 |
176 | ); 177 | }; 178 | -------------------------------------------------------------------------------- /components/form-components/text-field/FormTextProps.ts: -------------------------------------------------------------------------------- 1 | import type { OverridableStringUnion } from "@mui/types"; 2 | import type { TextFieldPropsSizeOverrides } from "@mui/material"; 3 | import type { ReactNode } from "react"; 4 | 5 | type OnChangeType = 6 | ((e: React.ChangeEvent) => void) | ((value: string) => void); 7 | 8 | export interface FormTextProps { 9 | label?: string; 10 | name: string; 11 | value?: string; 12 | onChange?: OnChangeType; 13 | placeholder?: string; 14 | disabled?: boolean; 15 | readOnly?: boolean; 16 | required?: boolean; 17 | error?: string | null; 18 | allErrors?: string[]; 19 | type?: string; 20 | maxLength?: number; 21 | minLength?: number; 22 | pattern?: string; 23 | autoFocus?: boolean; 24 | className?: string; 25 | helperText?: string; 26 | fullWidth?: boolean; 27 | size?: OverridableStringUnion<"small" | "medium", TextFieldPropsSizeOverrides>; 28 | variant?: "outlined" | "filled" | "standard"; 29 | inputMode?: "text" | "numeric" | "decimal" | "email" | "search" | "tel" | "url"; 30 | multiline?: boolean; 31 | rows?: number; 32 | startAdornment?: ReactNode; 33 | endAdornment?: ReactNode; 34 | onBlur?: (e: React.FocusEvent) => void; 35 | onFocus?: (e: React.FocusEvent) => void; 36 | debounceDelay?: 300; 37 | onDebouncedChange?: (value: string) => void; 38 | customValidator?: (value: string) => string | undefined; 39 | theme?: "light" | "dark"; 40 | startIcon?: ReactNode; 41 | endIcon?: ReactNode; 42 | clearable?: boolean; 43 | inputRef?: React.Ref; 44 | showErrorOnlyIfNotEmpty?: boolean; 45 | } 46 | -------------------------------------------------------------------------------- /components/tools/validators.ts: -------------------------------------------------------------------------------- 1 | export const required = (message = "Bu alan zorunludur.") => (val: string | number | boolean) => 2 | !val || val.toString().trim() === "" ? message : null; 3 | 4 | export const minLength = (min: number, message?: string) => (val: string) => 5 | val.length < min ? message ?? `${min} karakterden kısa olamaz.` : null; 6 | 7 | export const maxLength = (max: number, message?: string) => (val: string) => 8 | val.length > max ? message ?? `${max} karakterden uzun olamaz.` : null; 9 | 10 | export const isEmail = (message = "Geçerli bir email girin.") => (val: string) => 11 | !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val) ? message : null; -------------------------------------------------------------------------------- /enums/drawerFormType.ts: -------------------------------------------------------------------------------- 1 | export const DrawerFormType = { 2 | ADD: "ADD", 3 | EDIT: "EDIT" 4 | } as const; 5 | 6 | export type DrawerFormType = keyof typeof DrawerFormType; -------------------------------------------------------------------------------- /hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import type { AuthContextType } from "../models/AuthContextType"; 3 | import { AuthContext } from "../auth/AuthContext"; 4 | 5 | export const useAuth = (): AuthContextType => { 6 | const context = useContext(AuthContext); 7 | if (!context) throw new Error('useAuth must be used within AuthProvider'); 8 | return context; 9 | }; -------------------------------------------------------------------------------- /hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export function useDebounce(value: T, delay = 300) { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | }; 14 | }, [value, delay]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /hooks/useDebouncedForFunc.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | export function useDebouncedForFunc(effect: () => void, deps: any[], delay: number) { 5 | useEffect(() => { 6 | const handler = setTimeout(() => { 7 | effect(); 8 | }, delay); 9 | 10 | return () => { 11 | clearTimeout(handler); 12 | }; 13 | // eslint-disable-next-line react-hooks/exhaustive-deps 14 | }, [...deps, delay]); 15 | } 16 | -------------------------------------------------------------------------------- /hooks/useFormManager.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useDebouncedForFunc } from "./useDebouncedForFunc"; 3 | 4 | type Validator = (value: string | number | boolean , form: T) => string | null; 5 | type ValidationSchema = { [K in keyof T]?: Validator[] }; // T tipideki objenin yani formun tüm propertylerini tek tek gezerek 6 | //K değerine atama yapar. Tıpkı bir foreach gibi dusunulebilir. Opsiyoneldir, her alan icin calismak zorunda degil. 7 | //Validator[] ile her property icin birden fazla validator ekleyebilir. 8 | 9 | 10 | export function useFormManager(initialForm: T, schema: ValidationSchema) { 11 | const [form, setForm] = useState(initialForm); 12 | const [isValid, setIsValid] = useState(true); 13 | const [errors, seteErrors] = useState>>({}); // Partial sarmaladigi tum propertyleri opsiyonel yapar. 14 | //Record T olarak verilen obje icindeki tum propertyleri string tip olarak ata, cunku donen mesaj string olacak 15 | const [touchedFields, setTouchedFields] = useState< 16 | Partial> 17 | >({}); 18 | 19 | const setFieldValue = (field: K, value: T[K]) => { 20 | // verilen K degeri mutlaka T ye ait olma zorunlulugu (type-safe) 21 | setForm((prev) => ({ ...prev, [field]: value })); 22 | setTouchedFields((prev) => ({ ...prev, [field]: true })); 23 | }; 24 | 25 | useDebouncedForFunc( 26 | () => { 27 | if (Object.values(touchedFields).some(Boolean)) { 28 | validate(); 29 | } 30 | }, 31 | [form], 32 | 300 33 | ); 34 | 35 | const validate = () => { 36 | const currentErrors: Partial> = {}; 37 | 38 | for (const key in schema) { 39 | if (!touchedFields[key as keyof T]) continue; 40 | 41 | const validators = schema[key]; 42 | if (!validators) continue; 43 | 44 | const fieldErrors: string[] = []; 45 | 46 | for (const validator of validators) { 47 | const result = validator(form[key] as string | number | boolean, form); 48 | if (result) { 49 | fieldErrors.push(result); 50 | } 51 | } 52 | 53 | if (fieldErrors.length > 0) { 54 | currentErrors[key as keyof T] = fieldErrors; 55 | } 56 | } 57 | 58 | seteErrors(currentErrors); 59 | const isValidNow = Object.keys(currentErrors).length === 0; 60 | setIsValid(isValidNow); 61 | return isValidNow; 62 | }; 63 | 64 | return { 65 | form, 66 | setForm, 67 | setFieldValue, 68 | validate, 69 | errors, 70 | getFieldErrors: (field: keyof T) => errors[field] ?? [], 71 | getFirstError: (field: keyof T) => errors[field]?.[0] ?? null, 72 | isValid, 73 | reset: () => { 74 | setForm(initialForm); 75 | seteErrors({}); 76 | setTouchedFields({}); 77 | }, 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /hooks/useGenericGrid.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import type { GridPaginationModel, GridRowId } from "@mui/x-data-grid"; 3 | import { useDebounce } from "./useDebounce"; 4 | import type { IGenericService } from "../services/IGenericService"; 5 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 6 | import type { PaginatedResponse } from "../api/responses/PaginatedResponse"; 7 | 8 | interface UseGenericQueryGridProps { 9 | service: IGenericService; 10 | parentId?: string; 11 | debounceDelay?: number; 12 | parentService?: IGenericService

; 13 | parentParameterName?: string; 14 | } 15 | 16 | export const UseGenericQueryGrid = ({ 17 | service, 18 | parentId, 19 | debounceDelay = 300, 20 | parentService, 21 | parentParameterName, 22 | }: UseGenericQueryGridProps) => { 23 | const [paginationModel, setPaginationModel] = useState({ 24 | page: 0, 25 | pageSize: 10, 26 | }); 27 | 28 | const [searchText, setSearchText] = useState(""); 29 | const [confirmDeleteId, setConfirmDeleteId] = useState( 30 | null 31 | ); 32 | const [editingState, setEditingState] = useState(null); 33 | const [openEditDrawer, setOpenEditDrawer] = useState(false); 34 | const [selectedParentId, setSelectedParentId] = useState( 35 | parentId || undefined 36 | ); 37 | 38 | const debouncedSearchText = useDebounce(searchText, debounceDelay); 39 | const queryClient = useQueryClient(); 40 | 41 | const { 42 | data: listData, 43 | isLoading: listLoading, 44 | refetch: listRefresh, 45 | } = useQuery>({ 46 | queryKey: [ 47 | "list", 48 | selectedParentId, 49 | debouncedSearchText, 50 | paginationModel.page, 51 | paginationModel.pageSize, 52 | ], 53 | queryFn: async (): Promise> => { 54 | return service.getByFilter( 55 | selectedParentId, 56 | parentParameterName, 57 | paginationModel.page, 58 | paginationModel.pageSize, 59 | debouncedSearchText 60 | ); 61 | }, 62 | enabled: true, 63 | staleTime: 0, 64 | }); 65 | 66 | const deleteMutation = useMutation({ 67 | mutationFn: (id: GridRowId) => service.remove(id.toString()), 68 | onSuccess: () => { 69 | queryClient.invalidateQueries({ queryKey: ["list"] }); 70 | }, 71 | }); 72 | 73 | const updateMutation = useMutation({ 74 | mutationFn: (model: T) => service.update(model), 75 | onSuccess: () => { 76 | queryClient.invalidateQueries({ queryKey: ["list"] }); 77 | setOpenEditDrawer(false); 78 | }, 79 | }); 80 | 81 | const { data: parentData } = useQuery

({ 82 | queryKey: ["parent", selectedParentId], 83 | queryFn: async (): Promise

=> { 84 | if (!parentService || !selectedParentId) return null; 85 | return await parentService.getById(selectedParentId); 86 | }, 87 | enabled: !!selectedParentId && !!parentService, 88 | staleTime: 1000 * 60, 89 | }); 90 | 91 | return { 92 | rows: listData?.pageItems ?? [], 93 | rowCount: listData?.totalCount ?? 0, 94 | paginationModel, 95 | searchText, 96 | confirmDeleteId, 97 | editingState, 98 | openEditDrawer, 99 | loading: listLoading, 100 | selectedParentId, 101 | setPaginationModel, 102 | setSearchText, 103 | setConfirmDeleteId, 104 | setEditingState, 105 | setOpenEditDrawer, 106 | setSelectedParentId, 107 | parentData, 108 | refresh: listRefresh, 109 | handleDelete: (id: GridRowId) => deleteMutation.mutate(id), 110 | handleEditSave: (model: T) => updateMutation.mutate(model) 111 | }; 112 | }; 113 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | // index.ts 2 | export * from "./api/axios"; 3 | export * from "./api/responses/PaginatedResponse"; 4 | export * from "./api/ApiConfig"; 5 | export * from "./api/config"; 6 | 7 | export * from "./auth/AuthContext"; 8 | export * from "./auth/AuthProvider"; 9 | export * from "./auth/TokenService"; 10 | export * from "./components/form-components/text-field/FormTextField"; 11 | export * from "./components/form-components/autocomplate-input/AutocompleteInput"; 12 | export * from "./components/tools/validators"; 13 | 14 | export * from "./enums/drawerFormType"; 15 | 16 | export * from "./hooks/useAuth"; 17 | export * from "./hooks/useDebounce"; 18 | export * from "./hooks/useDebouncedForFunc"; 19 | export * from "./hooks/useFormManager"; 20 | export * from "./hooks/useGenericGrid"; 21 | 22 | export * from "./models/AuthContextType"; 23 | export * from "./models/QueryParams"; 24 | export * from "./services/GenericService"; 25 | export * from "./services/IGenericModel"; 26 | export * from "./services/IGenericService"; 27 | -------------------------------------------------------------------------------- /models/AuthContextType.ts: -------------------------------------------------------------------------------- 1 | export interface AuthContextType { 2 | isAuthenticated: boolean; 3 | login: (accessToken: string, refreshToken: string) => void; 4 | logout: () => void; 5 | } 6 | -------------------------------------------------------------------------------- /models/QueryParams.ts: -------------------------------------------------------------------------------- 1 | export interface QueryParams { 2 | page: number; 3 | pageSize: number; 4 | search?: string; 5 | }; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react_core", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "@mui/icons-material": "^7.1.0", 9 | "@mui/material": "^7.1.0", 10 | "@mui/x-data-grid": "^8.3.1", 11 | "@tanstack/react-query": "^5.76.1", 12 | "axios": "^1.9.0", 13 | "react-toastify": "^11.0.5", 14 | "sass": "^1.88.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^22.15.18", 18 | "@types/react": "^19.1.4" 19 | } 20 | }, 21 | "node_modules/@babel/runtime": { 22 | "version": "7.27.1", 23 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", 24 | "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", 25 | "engines": { 26 | "node": ">=6.9.0" 27 | } 28 | }, 29 | "node_modules/@emotion/cache": { 30 | "version": "11.14.0", 31 | "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", 32 | "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", 33 | "dependencies": { 34 | "@emotion/memoize": "^0.9.0", 35 | "@emotion/sheet": "^1.4.0", 36 | "@emotion/utils": "^1.4.2", 37 | "@emotion/weak-memoize": "^0.4.0", 38 | "stylis": "4.2.0" 39 | } 40 | }, 41 | "node_modules/@emotion/hash": { 42 | "version": "0.9.2", 43 | "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", 44 | "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" 45 | }, 46 | "node_modules/@emotion/memoize": { 47 | "version": "0.9.0", 48 | "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", 49 | "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" 50 | }, 51 | "node_modules/@emotion/serialize": { 52 | "version": "1.3.3", 53 | "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", 54 | "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", 55 | "dependencies": { 56 | "@emotion/hash": "^0.9.2", 57 | "@emotion/memoize": "^0.9.0", 58 | "@emotion/unitless": "^0.10.0", 59 | "@emotion/utils": "^1.4.2", 60 | "csstype": "^3.0.2" 61 | } 62 | }, 63 | "node_modules/@emotion/sheet": { 64 | "version": "1.4.0", 65 | "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", 66 | "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" 67 | }, 68 | "node_modules/@emotion/unitless": { 69 | "version": "0.10.0", 70 | "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", 71 | "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" 72 | }, 73 | "node_modules/@emotion/utils": { 74 | "version": "1.4.2", 75 | "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", 76 | "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" 77 | }, 78 | "node_modules/@emotion/weak-memoize": { 79 | "version": "0.4.0", 80 | "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", 81 | "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" 82 | }, 83 | "node_modules/@mui/core-downloads-tracker": { 84 | "version": "7.1.0", 85 | "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz", 86 | "integrity": "sha512-E0OqhZv548Qdc0PwWhLVA2zmjJZSTvaL4ZhoswmI8NJEC1tpW2js6LLP827jrW9MEiXYdz3QS6+hask83w74yQ==", 87 | "funding": { 88 | "type": "opencollective", 89 | "url": "https://opencollective.com/mui-org" 90 | } 91 | }, 92 | "node_modules/@mui/icons-material": { 93 | "version": "7.1.0", 94 | "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.0.tgz", 95 | "integrity": "sha512-1mUPMAZ+Qk3jfgL5ftRR06ATH/Esi0izHl1z56H+df6cwIlCWG66RXciUqeJCttbOXOQ5y2DCjLZI/4t3Yg3LA==", 96 | "dependencies": { 97 | "@babel/runtime": "^7.27.1" 98 | }, 99 | "engines": { 100 | "node": ">=14.0.0" 101 | }, 102 | "funding": { 103 | "type": "opencollective", 104 | "url": "https://opencollective.com/mui-org" 105 | }, 106 | "peerDependencies": { 107 | "@mui/material": "^7.1.0", 108 | "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", 109 | "react": "^17.0.0 || ^18.0.0 || ^19.0.0" 110 | }, 111 | "peerDependenciesMeta": { 112 | "@types/react": { 113 | "optional": true 114 | } 115 | } 116 | }, 117 | "node_modules/@mui/material": { 118 | "version": "7.1.0", 119 | "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.0.tgz", 120 | "integrity": "sha512-ahUJdrhEv+mCp4XHW+tHIEYzZMSRLg8z4AjUOsj44QpD1ZaMxQoVOG2xiHvLFdcsIPbgSRx1bg1eQSheHBgvtg==", 121 | "dependencies": { 122 | "@babel/runtime": "^7.27.1", 123 | "@mui/core-downloads-tracker": "^7.1.0", 124 | "@mui/system": "^7.1.0", 125 | "@mui/types": "^7.4.2", 126 | "@mui/utils": "^7.1.0", 127 | "@popperjs/core": "^2.11.8", 128 | "@types/react-transition-group": "^4.4.12", 129 | "clsx": "^2.1.1", 130 | "csstype": "^3.1.3", 131 | "prop-types": "^15.8.1", 132 | "react-is": "^19.1.0", 133 | "react-transition-group": "^4.4.5" 134 | }, 135 | "engines": { 136 | "node": ">=14.0.0" 137 | }, 138 | "funding": { 139 | "type": "opencollective", 140 | "url": "https://opencollective.com/mui-org" 141 | }, 142 | "peerDependencies": { 143 | "@emotion/react": "^11.5.0", 144 | "@emotion/styled": "^11.3.0", 145 | "@mui/material-pigment-css": "^7.1.0", 146 | "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", 147 | "react": "^17.0.0 || ^18.0.0 || ^19.0.0", 148 | "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" 149 | }, 150 | "peerDependenciesMeta": { 151 | "@emotion/react": { 152 | "optional": true 153 | }, 154 | "@emotion/styled": { 155 | "optional": true 156 | }, 157 | "@mui/material-pigment-css": { 158 | "optional": true 159 | }, 160 | "@types/react": { 161 | "optional": true 162 | } 163 | } 164 | }, 165 | "node_modules/@mui/private-theming": { 166 | "version": "7.1.0", 167 | "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz", 168 | "integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==", 169 | "dependencies": { 170 | "@babel/runtime": "^7.27.1", 171 | "@mui/utils": "^7.1.0", 172 | "prop-types": "^15.8.1" 173 | }, 174 | "engines": { 175 | "node": ">=14.0.0" 176 | }, 177 | "funding": { 178 | "type": "opencollective", 179 | "url": "https://opencollective.com/mui-org" 180 | }, 181 | "peerDependencies": { 182 | "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", 183 | "react": "^17.0.0 || ^18.0.0 || ^19.0.0" 184 | }, 185 | "peerDependenciesMeta": { 186 | "@types/react": { 187 | "optional": true 188 | } 189 | } 190 | }, 191 | "node_modules/@mui/styled-engine": { 192 | "version": "7.1.0", 193 | "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz", 194 | "integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==", 195 | "dependencies": { 196 | "@babel/runtime": "^7.27.1", 197 | "@emotion/cache": "^11.13.5", 198 | "@emotion/serialize": "^1.3.3", 199 | "@emotion/sheet": "^1.4.0", 200 | "csstype": "^3.1.3", 201 | "prop-types": "^15.8.1" 202 | }, 203 | "engines": { 204 | "node": ">=14.0.0" 205 | }, 206 | "funding": { 207 | "type": "opencollective", 208 | "url": "https://opencollective.com/mui-org" 209 | }, 210 | "peerDependencies": { 211 | "@emotion/react": "^11.4.1", 212 | "@emotion/styled": "^11.3.0", 213 | "react": "^17.0.0 || ^18.0.0 || ^19.0.0" 214 | }, 215 | "peerDependenciesMeta": { 216 | "@emotion/react": { 217 | "optional": true 218 | }, 219 | "@emotion/styled": { 220 | "optional": true 221 | } 222 | } 223 | }, 224 | "node_modules/@mui/system": { 225 | "version": "7.1.0", 226 | "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.0.tgz", 227 | "integrity": "sha512-iedAWgRJMCxeMHvkEhsDlbvkK+qKf9me6ofsf7twk/jfT4P1ImVf7Rwb5VubEA0sikrVL+1SkoZM41M4+LNAVA==", 228 | "dependencies": { 229 | "@babel/runtime": "^7.27.1", 230 | "@mui/private-theming": "^7.1.0", 231 | "@mui/styled-engine": "^7.1.0", 232 | "@mui/types": "^7.4.2", 233 | "@mui/utils": "^7.1.0", 234 | "clsx": "^2.1.1", 235 | "csstype": "^3.1.3", 236 | "prop-types": "^15.8.1" 237 | }, 238 | "engines": { 239 | "node": ">=14.0.0" 240 | }, 241 | "funding": { 242 | "type": "opencollective", 243 | "url": "https://opencollective.com/mui-org" 244 | }, 245 | "peerDependencies": { 246 | "@emotion/react": "^11.5.0", 247 | "@emotion/styled": "^11.3.0", 248 | "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", 249 | "react": "^17.0.0 || ^18.0.0 || ^19.0.0" 250 | }, 251 | "peerDependenciesMeta": { 252 | "@emotion/react": { 253 | "optional": true 254 | }, 255 | "@emotion/styled": { 256 | "optional": true 257 | }, 258 | "@types/react": { 259 | "optional": true 260 | } 261 | } 262 | }, 263 | "node_modules/@mui/types": { 264 | "version": "7.4.2", 265 | "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz", 266 | "integrity": "sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA==", 267 | "dependencies": { 268 | "@babel/runtime": "^7.27.1" 269 | }, 270 | "peerDependencies": { 271 | "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" 272 | }, 273 | "peerDependenciesMeta": { 274 | "@types/react": { 275 | "optional": true 276 | } 277 | } 278 | }, 279 | "node_modules/@mui/utils": { 280 | "version": "7.1.0", 281 | "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz", 282 | "integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==", 283 | "dependencies": { 284 | "@babel/runtime": "^7.27.1", 285 | "@mui/types": "^7.4.2", 286 | "@types/prop-types": "^15.7.14", 287 | "clsx": "^2.1.1", 288 | "prop-types": "^15.8.1", 289 | "react-is": "^19.1.0" 290 | }, 291 | "engines": { 292 | "node": ">=14.0.0" 293 | }, 294 | "funding": { 295 | "type": "opencollective", 296 | "url": "https://opencollective.com/mui-org" 297 | }, 298 | "peerDependencies": { 299 | "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", 300 | "react": "^17.0.0 || ^18.0.0 || ^19.0.0" 301 | }, 302 | "peerDependenciesMeta": { 303 | "@types/react": { 304 | "optional": true 305 | } 306 | } 307 | }, 308 | "node_modules/@mui/x-data-grid": { 309 | "version": "8.3.1", 310 | "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.3.1.tgz", 311 | "integrity": "sha512-mSo2g0ZZzasDQ4kKrFdJVk7dJgz77jF/e8udvGqnnTgnQXlqLMpKne/veL3gRdi3TJxxTv2vqXtX7IZfWGJecQ==", 312 | "dependencies": { 313 | "@babel/runtime": "^7.27.1", 314 | "@mui/utils": "^7.0.2", 315 | "@mui/x-internals": "8.3.1", 316 | "clsx": "^2.1.1", 317 | "prop-types": "^15.8.1", 318 | "reselect": "^5.1.1", 319 | "use-sync-external-store": "^1.5.0" 320 | }, 321 | "engines": { 322 | "node": ">=14.0.0" 323 | }, 324 | "funding": { 325 | "type": "opencollective", 326 | "url": "https://opencollective.com/mui-org" 327 | }, 328 | "peerDependencies": { 329 | "@emotion/react": "^11.9.0", 330 | "@emotion/styled": "^11.8.1", 331 | "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", 332 | "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", 333 | "react": "^17.0.0 || ^18.0.0 || ^19.0.0", 334 | "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" 335 | }, 336 | "peerDependenciesMeta": { 337 | "@emotion/react": { 338 | "optional": true 339 | }, 340 | "@emotion/styled": { 341 | "optional": true 342 | } 343 | } 344 | }, 345 | "node_modules/@mui/x-internals": { 346 | "version": "8.3.1", 347 | "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.3.1.tgz", 348 | "integrity": "sha512-8kIxT66cea63iEseEIHSWzKju2Wzl7MsWFoAUQEyRvYqOFa2j9Un2Vn/EH2vy9nm/MtMAYpwOE/nt68/KTIA2w==", 349 | "dependencies": { 350 | "@babel/runtime": "^7.27.1", 351 | "@mui/utils": "^7.0.2" 352 | }, 353 | "engines": { 354 | "node": ">=14.0.0" 355 | }, 356 | "funding": { 357 | "type": "opencollective", 358 | "url": "https://opencollective.com/mui-org" 359 | }, 360 | "peerDependencies": { 361 | "react": "^17.0.0 || ^18.0.0 || ^19.0.0" 362 | } 363 | }, 364 | "node_modules/@parcel/watcher": { 365 | "version": "2.5.1", 366 | "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", 367 | "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", 368 | "hasInstallScript": true, 369 | "optional": true, 370 | "dependencies": { 371 | "detect-libc": "^1.0.3", 372 | "is-glob": "^4.0.3", 373 | "micromatch": "^4.0.5", 374 | "node-addon-api": "^7.0.0" 375 | }, 376 | "engines": { 377 | "node": ">= 10.0.0" 378 | }, 379 | "funding": { 380 | "type": "opencollective", 381 | "url": "https://opencollective.com/parcel" 382 | }, 383 | "optionalDependencies": { 384 | "@parcel/watcher-android-arm64": "2.5.1", 385 | "@parcel/watcher-darwin-arm64": "2.5.1", 386 | "@parcel/watcher-darwin-x64": "2.5.1", 387 | "@parcel/watcher-freebsd-x64": "2.5.1", 388 | "@parcel/watcher-linux-arm-glibc": "2.5.1", 389 | "@parcel/watcher-linux-arm-musl": "2.5.1", 390 | "@parcel/watcher-linux-arm64-glibc": "2.5.1", 391 | "@parcel/watcher-linux-arm64-musl": "2.5.1", 392 | "@parcel/watcher-linux-x64-glibc": "2.5.1", 393 | "@parcel/watcher-linux-x64-musl": "2.5.1", 394 | "@parcel/watcher-win32-arm64": "2.5.1", 395 | "@parcel/watcher-win32-ia32": "2.5.1", 396 | "@parcel/watcher-win32-x64": "2.5.1" 397 | } 398 | }, 399 | "node_modules/@parcel/watcher-android-arm64": { 400 | "version": "2.5.1", 401 | "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", 402 | "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", 403 | "cpu": [ 404 | "arm64" 405 | ], 406 | "optional": true, 407 | "os": [ 408 | "android" 409 | ], 410 | "engines": { 411 | "node": ">= 10.0.0" 412 | }, 413 | "funding": { 414 | "type": "opencollective", 415 | "url": "https://opencollective.com/parcel" 416 | } 417 | }, 418 | "node_modules/@parcel/watcher-darwin-arm64": { 419 | "version": "2.5.1", 420 | "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", 421 | "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", 422 | "cpu": [ 423 | "arm64" 424 | ], 425 | "optional": true, 426 | "os": [ 427 | "darwin" 428 | ], 429 | "engines": { 430 | "node": ">= 10.0.0" 431 | }, 432 | "funding": { 433 | "type": "opencollective", 434 | "url": "https://opencollective.com/parcel" 435 | } 436 | }, 437 | "node_modules/@parcel/watcher-darwin-x64": { 438 | "version": "2.5.1", 439 | "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", 440 | "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", 441 | "cpu": [ 442 | "x64" 443 | ], 444 | "optional": true, 445 | "os": [ 446 | "darwin" 447 | ], 448 | "engines": { 449 | "node": ">= 10.0.0" 450 | }, 451 | "funding": { 452 | "type": "opencollective", 453 | "url": "https://opencollective.com/parcel" 454 | } 455 | }, 456 | "node_modules/@parcel/watcher-freebsd-x64": { 457 | "version": "2.5.1", 458 | "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", 459 | "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", 460 | "cpu": [ 461 | "x64" 462 | ], 463 | "optional": true, 464 | "os": [ 465 | "freebsd" 466 | ], 467 | "engines": { 468 | "node": ">= 10.0.0" 469 | }, 470 | "funding": { 471 | "type": "opencollective", 472 | "url": "https://opencollective.com/parcel" 473 | } 474 | }, 475 | "node_modules/@parcel/watcher-linux-arm-glibc": { 476 | "version": "2.5.1", 477 | "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", 478 | "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", 479 | "cpu": [ 480 | "arm" 481 | ], 482 | "optional": true, 483 | "os": [ 484 | "linux" 485 | ], 486 | "engines": { 487 | "node": ">= 10.0.0" 488 | }, 489 | "funding": { 490 | "type": "opencollective", 491 | "url": "https://opencollective.com/parcel" 492 | } 493 | }, 494 | "node_modules/@parcel/watcher-linux-arm-musl": { 495 | "version": "2.5.1", 496 | "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", 497 | "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", 498 | "cpu": [ 499 | "arm" 500 | ], 501 | "optional": true, 502 | "os": [ 503 | "linux" 504 | ], 505 | "engines": { 506 | "node": ">= 10.0.0" 507 | }, 508 | "funding": { 509 | "type": "opencollective", 510 | "url": "https://opencollective.com/parcel" 511 | } 512 | }, 513 | "node_modules/@parcel/watcher-linux-arm64-glibc": { 514 | "version": "2.5.1", 515 | "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", 516 | "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", 517 | "cpu": [ 518 | "arm64" 519 | ], 520 | "optional": true, 521 | "os": [ 522 | "linux" 523 | ], 524 | "engines": { 525 | "node": ">= 10.0.0" 526 | }, 527 | "funding": { 528 | "type": "opencollective", 529 | "url": "https://opencollective.com/parcel" 530 | } 531 | }, 532 | "node_modules/@parcel/watcher-linux-arm64-musl": { 533 | "version": "2.5.1", 534 | "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", 535 | "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", 536 | "cpu": [ 537 | "arm64" 538 | ], 539 | "optional": true, 540 | "os": [ 541 | "linux" 542 | ], 543 | "engines": { 544 | "node": ">= 10.0.0" 545 | }, 546 | "funding": { 547 | "type": "opencollective", 548 | "url": "https://opencollective.com/parcel" 549 | } 550 | }, 551 | "node_modules/@parcel/watcher-linux-x64-glibc": { 552 | "version": "2.5.1", 553 | "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", 554 | "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", 555 | "cpu": [ 556 | "x64" 557 | ], 558 | "optional": true, 559 | "os": [ 560 | "linux" 561 | ], 562 | "engines": { 563 | "node": ">= 10.0.0" 564 | }, 565 | "funding": { 566 | "type": "opencollective", 567 | "url": "https://opencollective.com/parcel" 568 | } 569 | }, 570 | "node_modules/@parcel/watcher-linux-x64-musl": { 571 | "version": "2.5.1", 572 | "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", 573 | "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", 574 | "cpu": [ 575 | "x64" 576 | ], 577 | "optional": true, 578 | "os": [ 579 | "linux" 580 | ], 581 | "engines": { 582 | "node": ">= 10.0.0" 583 | }, 584 | "funding": { 585 | "type": "opencollective", 586 | "url": "https://opencollective.com/parcel" 587 | } 588 | }, 589 | "node_modules/@parcel/watcher-win32-arm64": { 590 | "version": "2.5.1", 591 | "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", 592 | "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", 593 | "cpu": [ 594 | "arm64" 595 | ], 596 | "optional": true, 597 | "os": [ 598 | "win32" 599 | ], 600 | "engines": { 601 | "node": ">= 10.0.0" 602 | }, 603 | "funding": { 604 | "type": "opencollective", 605 | "url": "https://opencollective.com/parcel" 606 | } 607 | }, 608 | "node_modules/@parcel/watcher-win32-ia32": { 609 | "version": "2.5.1", 610 | "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", 611 | "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", 612 | "cpu": [ 613 | "ia32" 614 | ], 615 | "optional": true, 616 | "os": [ 617 | "win32" 618 | ], 619 | "engines": { 620 | "node": ">= 10.0.0" 621 | }, 622 | "funding": { 623 | "type": "opencollective", 624 | "url": "https://opencollective.com/parcel" 625 | } 626 | }, 627 | "node_modules/@parcel/watcher-win32-x64": { 628 | "version": "2.5.1", 629 | "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", 630 | "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", 631 | "cpu": [ 632 | "x64" 633 | ], 634 | "optional": true, 635 | "os": [ 636 | "win32" 637 | ], 638 | "engines": { 639 | "node": ">= 10.0.0" 640 | }, 641 | "funding": { 642 | "type": "opencollective", 643 | "url": "https://opencollective.com/parcel" 644 | } 645 | }, 646 | "node_modules/@popperjs/core": { 647 | "version": "2.11.8", 648 | "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", 649 | "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", 650 | "funding": { 651 | "type": "opencollective", 652 | "url": "https://opencollective.com/popperjs" 653 | } 654 | }, 655 | "node_modules/@tanstack/query-core": { 656 | "version": "5.76.0", 657 | "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.76.0.tgz", 658 | "integrity": "sha512-FN375hb8ctzfNAlex5gHI6+WDXTNpe0nbxp/d2YJtnP+IBM6OUm7zcaoCW6T63BawGOYZBbKC0iPvr41TteNVg==", 659 | "funding": { 660 | "type": "github", 661 | "url": "https://github.com/sponsors/tannerlinsley" 662 | } 663 | }, 664 | "node_modules/@tanstack/react-query": { 665 | "version": "5.76.1", 666 | "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.76.1.tgz", 667 | "integrity": "sha512-YxdLZVGN4QkT5YT1HKZQWiIlcgauIXEIsMOTSjvyD5wLYK8YVvKZUPAysMqossFJJfDpJW3pFn7WNZuPOqq+fw==", 668 | "dependencies": { 669 | "@tanstack/query-core": "5.76.0" 670 | }, 671 | "funding": { 672 | "type": "github", 673 | "url": "https://github.com/sponsors/tannerlinsley" 674 | }, 675 | "peerDependencies": { 676 | "react": "^18 || ^19" 677 | } 678 | }, 679 | "node_modules/@types/node": { 680 | "version": "22.15.18", 681 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.18.tgz", 682 | "integrity": "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==", 683 | "dev": true, 684 | "dependencies": { 685 | "undici-types": "~6.21.0" 686 | } 687 | }, 688 | "node_modules/@types/prop-types": { 689 | "version": "15.7.14", 690 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", 691 | "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" 692 | }, 693 | "node_modules/@types/react": { 694 | "version": "19.1.4", 695 | "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", 696 | "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", 697 | "dependencies": { 698 | "csstype": "^3.0.2" 699 | } 700 | }, 701 | "node_modules/@types/react-transition-group": { 702 | "version": "4.4.12", 703 | "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", 704 | "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", 705 | "peerDependencies": { 706 | "@types/react": "*" 707 | } 708 | }, 709 | "node_modules/asynckit": { 710 | "version": "0.4.0", 711 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 712 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 713 | }, 714 | "node_modules/axios": { 715 | "version": "1.9.0", 716 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", 717 | "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", 718 | "dependencies": { 719 | "follow-redirects": "^1.15.6", 720 | "form-data": "^4.0.0", 721 | "proxy-from-env": "^1.1.0" 722 | } 723 | }, 724 | "node_modules/braces": { 725 | "version": "3.0.3", 726 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 727 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 728 | "optional": true, 729 | "dependencies": { 730 | "fill-range": "^7.1.1" 731 | }, 732 | "engines": { 733 | "node": ">=8" 734 | } 735 | }, 736 | "node_modules/call-bind-apply-helpers": { 737 | "version": "1.0.2", 738 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 739 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 740 | "dependencies": { 741 | "es-errors": "^1.3.0", 742 | "function-bind": "^1.1.2" 743 | }, 744 | "engines": { 745 | "node": ">= 0.4" 746 | } 747 | }, 748 | "node_modules/chokidar": { 749 | "version": "4.0.3", 750 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", 751 | "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", 752 | "dependencies": { 753 | "readdirp": "^4.0.1" 754 | }, 755 | "engines": { 756 | "node": ">= 14.16.0" 757 | }, 758 | "funding": { 759 | "url": "https://paulmillr.com/funding/" 760 | } 761 | }, 762 | "node_modules/clsx": { 763 | "version": "2.1.1", 764 | "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", 765 | "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", 766 | "engines": { 767 | "node": ">=6" 768 | } 769 | }, 770 | "node_modules/combined-stream": { 771 | "version": "1.0.8", 772 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 773 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 774 | "dependencies": { 775 | "delayed-stream": "~1.0.0" 776 | }, 777 | "engines": { 778 | "node": ">= 0.8" 779 | } 780 | }, 781 | "node_modules/csstype": { 782 | "version": "3.1.3", 783 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 784 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" 785 | }, 786 | "node_modules/delayed-stream": { 787 | "version": "1.0.0", 788 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 789 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 790 | "engines": { 791 | "node": ">=0.4.0" 792 | } 793 | }, 794 | "node_modules/detect-libc": { 795 | "version": "1.0.3", 796 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 797 | "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", 798 | "optional": true, 799 | "bin": { 800 | "detect-libc": "bin/detect-libc.js" 801 | }, 802 | "engines": { 803 | "node": ">=0.10" 804 | } 805 | }, 806 | "node_modules/dom-helpers": { 807 | "version": "5.2.1", 808 | "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", 809 | "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", 810 | "dependencies": { 811 | "@babel/runtime": "^7.8.7", 812 | "csstype": "^3.0.2" 813 | } 814 | }, 815 | "node_modules/dunder-proto": { 816 | "version": "1.0.1", 817 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 818 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 819 | "dependencies": { 820 | "call-bind-apply-helpers": "^1.0.1", 821 | "es-errors": "^1.3.0", 822 | "gopd": "^1.2.0" 823 | }, 824 | "engines": { 825 | "node": ">= 0.4" 826 | } 827 | }, 828 | "node_modules/es-define-property": { 829 | "version": "1.0.1", 830 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 831 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 832 | "engines": { 833 | "node": ">= 0.4" 834 | } 835 | }, 836 | "node_modules/es-errors": { 837 | "version": "1.3.0", 838 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 839 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 840 | "engines": { 841 | "node": ">= 0.4" 842 | } 843 | }, 844 | "node_modules/es-object-atoms": { 845 | "version": "1.1.1", 846 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 847 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 848 | "dependencies": { 849 | "es-errors": "^1.3.0" 850 | }, 851 | "engines": { 852 | "node": ">= 0.4" 853 | } 854 | }, 855 | "node_modules/es-set-tostringtag": { 856 | "version": "2.1.0", 857 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", 858 | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 859 | "dependencies": { 860 | "es-errors": "^1.3.0", 861 | "get-intrinsic": "^1.2.6", 862 | "has-tostringtag": "^1.0.2", 863 | "hasown": "^2.0.2" 864 | }, 865 | "engines": { 866 | "node": ">= 0.4" 867 | } 868 | }, 869 | "node_modules/fill-range": { 870 | "version": "7.1.1", 871 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 872 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 873 | "optional": true, 874 | "dependencies": { 875 | "to-regex-range": "^5.0.1" 876 | }, 877 | "engines": { 878 | "node": ">=8" 879 | } 880 | }, 881 | "node_modules/follow-redirects": { 882 | "version": "1.15.9", 883 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 884 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 885 | "funding": [ 886 | { 887 | "type": "individual", 888 | "url": "https://github.com/sponsors/RubenVerborgh" 889 | } 890 | ], 891 | "engines": { 892 | "node": ">=4.0" 893 | }, 894 | "peerDependenciesMeta": { 895 | "debug": { 896 | "optional": true 897 | } 898 | } 899 | }, 900 | "node_modules/form-data": { 901 | "version": "4.0.2", 902 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", 903 | "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", 904 | "dependencies": { 905 | "asynckit": "^0.4.0", 906 | "combined-stream": "^1.0.8", 907 | "es-set-tostringtag": "^2.1.0", 908 | "mime-types": "^2.1.12" 909 | }, 910 | "engines": { 911 | "node": ">= 6" 912 | } 913 | }, 914 | "node_modules/function-bind": { 915 | "version": "1.1.2", 916 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 917 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 918 | "funding": { 919 | "url": "https://github.com/sponsors/ljharb" 920 | } 921 | }, 922 | "node_modules/get-intrinsic": { 923 | "version": "1.3.0", 924 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 925 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 926 | "dependencies": { 927 | "call-bind-apply-helpers": "^1.0.2", 928 | "es-define-property": "^1.0.1", 929 | "es-errors": "^1.3.0", 930 | "es-object-atoms": "^1.1.1", 931 | "function-bind": "^1.1.2", 932 | "get-proto": "^1.0.1", 933 | "gopd": "^1.2.0", 934 | "has-symbols": "^1.1.0", 935 | "hasown": "^2.0.2", 936 | "math-intrinsics": "^1.1.0" 937 | }, 938 | "engines": { 939 | "node": ">= 0.4" 940 | }, 941 | "funding": { 942 | "url": "https://github.com/sponsors/ljharb" 943 | } 944 | }, 945 | "node_modules/get-proto": { 946 | "version": "1.0.1", 947 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 948 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 949 | "dependencies": { 950 | "dunder-proto": "^1.0.1", 951 | "es-object-atoms": "^1.0.0" 952 | }, 953 | "engines": { 954 | "node": ">= 0.4" 955 | } 956 | }, 957 | "node_modules/gopd": { 958 | "version": "1.2.0", 959 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 960 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 961 | "engines": { 962 | "node": ">= 0.4" 963 | }, 964 | "funding": { 965 | "url": "https://github.com/sponsors/ljharb" 966 | } 967 | }, 968 | "node_modules/has-symbols": { 969 | "version": "1.1.0", 970 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 971 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 972 | "engines": { 973 | "node": ">= 0.4" 974 | }, 975 | "funding": { 976 | "url": "https://github.com/sponsors/ljharb" 977 | } 978 | }, 979 | "node_modules/has-tostringtag": { 980 | "version": "1.0.2", 981 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 982 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 983 | "dependencies": { 984 | "has-symbols": "^1.0.3" 985 | }, 986 | "engines": { 987 | "node": ">= 0.4" 988 | }, 989 | "funding": { 990 | "url": "https://github.com/sponsors/ljharb" 991 | } 992 | }, 993 | "node_modules/hasown": { 994 | "version": "2.0.2", 995 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 996 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 997 | "dependencies": { 998 | "function-bind": "^1.1.2" 999 | }, 1000 | "engines": { 1001 | "node": ">= 0.4" 1002 | } 1003 | }, 1004 | "node_modules/immutable": { 1005 | "version": "5.1.2", 1006 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", 1007 | "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==" 1008 | }, 1009 | "node_modules/is-extglob": { 1010 | "version": "2.1.1", 1011 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1012 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1013 | "optional": true, 1014 | "engines": { 1015 | "node": ">=0.10.0" 1016 | } 1017 | }, 1018 | "node_modules/is-glob": { 1019 | "version": "4.0.3", 1020 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1021 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1022 | "optional": true, 1023 | "dependencies": { 1024 | "is-extglob": "^2.1.1" 1025 | }, 1026 | "engines": { 1027 | "node": ">=0.10.0" 1028 | } 1029 | }, 1030 | "node_modules/is-number": { 1031 | "version": "7.0.0", 1032 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1033 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1034 | "optional": true, 1035 | "engines": { 1036 | "node": ">=0.12.0" 1037 | } 1038 | }, 1039 | "node_modules/js-tokens": { 1040 | "version": "4.0.0", 1041 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1042 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 1043 | }, 1044 | "node_modules/loose-envify": { 1045 | "version": "1.4.0", 1046 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 1047 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 1048 | "dependencies": { 1049 | "js-tokens": "^3.0.0 || ^4.0.0" 1050 | }, 1051 | "bin": { 1052 | "loose-envify": "cli.js" 1053 | } 1054 | }, 1055 | "node_modules/math-intrinsics": { 1056 | "version": "1.1.0", 1057 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1058 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1059 | "engines": { 1060 | "node": ">= 0.4" 1061 | } 1062 | }, 1063 | "node_modules/micromatch": { 1064 | "version": "4.0.8", 1065 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 1066 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 1067 | "optional": true, 1068 | "dependencies": { 1069 | "braces": "^3.0.3", 1070 | "picomatch": "^2.3.1" 1071 | }, 1072 | "engines": { 1073 | "node": ">=8.6" 1074 | } 1075 | }, 1076 | "node_modules/mime-db": { 1077 | "version": "1.52.0", 1078 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1079 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1080 | "engines": { 1081 | "node": ">= 0.6" 1082 | } 1083 | }, 1084 | "node_modules/mime-types": { 1085 | "version": "2.1.35", 1086 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1087 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1088 | "dependencies": { 1089 | "mime-db": "1.52.0" 1090 | }, 1091 | "engines": { 1092 | "node": ">= 0.6" 1093 | } 1094 | }, 1095 | "node_modules/node-addon-api": { 1096 | "version": "7.1.1", 1097 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", 1098 | "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", 1099 | "optional": true 1100 | }, 1101 | "node_modules/object-assign": { 1102 | "version": "4.1.1", 1103 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1104 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1105 | "engines": { 1106 | "node": ">=0.10.0" 1107 | } 1108 | }, 1109 | "node_modules/picomatch": { 1110 | "version": "2.3.1", 1111 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1112 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1113 | "optional": true, 1114 | "engines": { 1115 | "node": ">=8.6" 1116 | }, 1117 | "funding": { 1118 | "url": "https://github.com/sponsors/jonschlinkert" 1119 | } 1120 | }, 1121 | "node_modules/prop-types": { 1122 | "version": "15.8.1", 1123 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", 1124 | "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", 1125 | "dependencies": { 1126 | "loose-envify": "^1.4.0", 1127 | "object-assign": "^4.1.1", 1128 | "react-is": "^16.13.1" 1129 | } 1130 | }, 1131 | "node_modules/prop-types/node_modules/react-is": { 1132 | "version": "16.13.1", 1133 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", 1134 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" 1135 | }, 1136 | "node_modules/proxy-from-env": { 1137 | "version": "1.1.0", 1138 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1139 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 1140 | }, 1141 | "node_modules/react": { 1142 | "version": "19.1.0", 1143 | "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", 1144 | "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", 1145 | "peer": true, 1146 | "engines": { 1147 | "node": ">=0.10.0" 1148 | } 1149 | }, 1150 | "node_modules/react-dom": { 1151 | "version": "19.1.0", 1152 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", 1153 | "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", 1154 | "peer": true, 1155 | "dependencies": { 1156 | "scheduler": "^0.26.0" 1157 | }, 1158 | "peerDependencies": { 1159 | "react": "^19.1.0" 1160 | } 1161 | }, 1162 | "node_modules/react-is": { 1163 | "version": "19.1.0", 1164 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", 1165 | "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==" 1166 | }, 1167 | "node_modules/react-toastify": { 1168 | "version": "11.0.5", 1169 | "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", 1170 | "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", 1171 | "dependencies": { 1172 | "clsx": "^2.1.1" 1173 | }, 1174 | "peerDependencies": { 1175 | "react": "^18 || ^19", 1176 | "react-dom": "^18 || ^19" 1177 | } 1178 | }, 1179 | "node_modules/react-transition-group": { 1180 | "version": "4.4.5", 1181 | "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", 1182 | "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", 1183 | "dependencies": { 1184 | "@babel/runtime": "^7.5.5", 1185 | "dom-helpers": "^5.0.1", 1186 | "loose-envify": "^1.4.0", 1187 | "prop-types": "^15.6.2" 1188 | }, 1189 | "peerDependencies": { 1190 | "react": ">=16.6.0", 1191 | "react-dom": ">=16.6.0" 1192 | } 1193 | }, 1194 | "node_modules/readdirp": { 1195 | "version": "4.1.2", 1196 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", 1197 | "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", 1198 | "engines": { 1199 | "node": ">= 14.18.0" 1200 | }, 1201 | "funding": { 1202 | "type": "individual", 1203 | "url": "https://paulmillr.com/funding/" 1204 | } 1205 | }, 1206 | "node_modules/reselect": { 1207 | "version": "5.1.1", 1208 | "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", 1209 | "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" 1210 | }, 1211 | "node_modules/sass": { 1212 | "version": "1.88.0", 1213 | "resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz", 1214 | "integrity": "sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==", 1215 | "dependencies": { 1216 | "chokidar": "^4.0.0", 1217 | "immutable": "^5.0.2", 1218 | "source-map-js": ">=0.6.2 <2.0.0" 1219 | }, 1220 | "bin": { 1221 | "sass": "sass.js" 1222 | }, 1223 | "engines": { 1224 | "node": ">=14.0.0" 1225 | }, 1226 | "optionalDependencies": { 1227 | "@parcel/watcher": "^2.4.1" 1228 | } 1229 | }, 1230 | "node_modules/scheduler": { 1231 | "version": "0.26.0", 1232 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", 1233 | "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", 1234 | "peer": true 1235 | }, 1236 | "node_modules/source-map-js": { 1237 | "version": "1.2.1", 1238 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1239 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1240 | "engines": { 1241 | "node": ">=0.10.0" 1242 | } 1243 | }, 1244 | "node_modules/stylis": { 1245 | "version": "4.2.0", 1246 | "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", 1247 | "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" 1248 | }, 1249 | "node_modules/to-regex-range": { 1250 | "version": "5.0.1", 1251 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1252 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1253 | "optional": true, 1254 | "dependencies": { 1255 | "is-number": "^7.0.0" 1256 | }, 1257 | "engines": { 1258 | "node": ">=8.0" 1259 | } 1260 | }, 1261 | "node_modules/undici-types": { 1262 | "version": "6.21.0", 1263 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 1264 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 1265 | "dev": true 1266 | }, 1267 | "node_modules/use-sync-external-store": { 1268 | "version": "1.5.0", 1269 | "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", 1270 | "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", 1271 | "peerDependencies": { 1272 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 1273 | } 1274 | } 1275 | } 1276 | } 1277 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@mui/icons-material": "^7.1.0", 4 | "@mui/material": "^7.1.0", 5 | "@mui/x-data-grid": "^8.3.1", 6 | "@tanstack/react-query": "^5.76.1", 7 | "axios": "^1.9.0", 8 | "react-toastify": "^11.0.5", 9 | "sass": "^1.88.0" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "^22.15.18", 13 | "@types/react": "^19.1.4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /services/GenericService.ts: -------------------------------------------------------------------------------- 1 | 2 | import api from "../api/axios"; 3 | import type { PaginatedResponse } from "../api/responses/PaginatedResponse"; 4 | import type { QueryParams } from "../models/QueryParams"; 5 | import type { IGenericService } from "./IGenericService"; 6 | 7 | export class GenericService implements IGenericService { 8 | constructor(private basePath: string) {} 9 | 10 | getByFilter = async ( 11 | parentId: string | undefined, 12 | parentParameterName: string | undefined, 13 | page: number, 14 | pageSize: number, 15 | search: string 16 | ): Promise> => { 17 | const params: QueryParams = { 18 | page, 19 | pageSize, 20 | search, 21 | }; 22 | 23 | if(page === 0){ 24 | page = page+1; 25 | } 26 | 27 | const query = new URLSearchParams(); 28 | 29 | query.append("pageNumber", page.toString()); 30 | query.append("limit", pageSize.toString()); 31 | query.append("search", search); 32 | 33 | if (parentParameterName) { 34 | query.append(parentParameterName, parentId!); 35 | } 36 | 37 | const response = await api.get>( 38 | `${this.basePath}/pagedList?${query.toString()}`, 39 | { params } 40 | ); 41 | 42 | return response.data; 43 | }; 44 | 45 | remove = (id: string): Promise => { 46 | return api.post(`${this.basePath}/delete`, { id }); 47 | }; 48 | update = async (model: TModel): Promise => { 49 | return await api.post(`${this.basePath}/update`, model); 50 | }; 51 | 52 | getById = async (id: string): Promise => { 53 | const response = await api.get(`${this.basePath}/getbyid/?id=${id}`); 54 | return response.data; 55 | }; 56 | 57 | save = async (model: TModel): Promise => { 58 | return await api.post(`${this.basePath}/create`, model); 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /services/IGenericModel.ts: -------------------------------------------------------------------------------- 1 | export interface IGenericModel { 2 | id: string; 3 | name: string; 4 | } -------------------------------------------------------------------------------- /services/IGenericService.ts: -------------------------------------------------------------------------------- 1 | import type { PaginatedResponse } from "../api/responses/PaginatedResponse"; 2 | 3 | 4 | export interface IGenericService{ 5 | getByFilter:( 6 | parentId: string | undefined, 7 | parentParameterName: string | undefined, 8 | page: number, 9 | pageSize: number, 10 | search: string | "" 11 | ) => Promise>; 12 | 13 | remove: (id: string) => Promise; 14 | update: (model: T) => Promise; 15 | getById: (id: string) => Promise; 16 | save: (model: T) => Promise; 17 | } --------------------------------------------------------------------------------