Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
907ee574e6 |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.33",
|
"version": "2.4.34",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.33",
|
"version": "2.4.34",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.102.0",
|
"@anthropic-ai/sdk": "^0.102.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.33",
|
"version": "2.4.34",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/server.js",
|
"main": "dist/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -391,12 +391,12 @@ async function main() {
|
|||||||
console.log(` Admin role now has all ${allPerms.length} permissions`);
|
console.log(` Admin role now has all ${allPerms.length} permissions`);
|
||||||
|
|
||||||
// 4. Ensure admin user exists
|
// 4. Ensure admin user exists
|
||||||
let adminUser = await prisma.users.findFirst({
|
const adminUser = await prisma.users.findFirst({
|
||||||
where: { username: "admin" },
|
where: { username: "admin" },
|
||||||
});
|
});
|
||||||
if (!adminUser) {
|
if (!adminUser) {
|
||||||
const hash = bcrypt.hashSync("admin", 10);
|
const hash = bcrypt.hashSync("admin", 10);
|
||||||
adminUser = await prisma.users.create({
|
await prisma.users.create({
|
||||||
data: {
|
data: {
|
||||||
username: "admin",
|
username: "admin",
|
||||||
email: "admin@boha.local",
|
email: "admin@boha.local",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ async function main() {
|
|||||||
// fails we KEEP file_data and report the row as failed.
|
// fails we KEEP file_data and report the row as failed.
|
||||||
const expectedSize = rec.file_data.length;
|
const expectedSize = rec.file_data.length;
|
||||||
const readBack = nasFinancialsManager.readReceivedInvoice(result.filePath);
|
const readBack = nasFinancialsManager.readReceivedInvoice(result.filePath);
|
||||||
let writtenSize = -1;
|
let writtenSize: number;
|
||||||
if (readBack) {
|
if (readBack) {
|
||||||
writtenSize = readBack.data.length;
|
writtenSize = readBack.data.length;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1090,26 +1090,6 @@ async function authPost(path: string, token: string, body: unknown) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function authPatch(path: string, token: string, body: unknown) {
|
|
||||||
return app.inject({
|
|
||||||
method: "PATCH",
|
|
||||||
url: path,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
payload: body as object,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function authDelete(path: string, token: string) {
|
|
||||||
return app.inject({
|
|
||||||
method: "DELETE",
|
|
||||||
url: path,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await buildApp();
|
app = await buildApp();
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ const TrashIcon = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default function PlanCellModal(props: Props) {
|
export default function PlanCellModal(props: Props) {
|
||||||
const { open, mode, onClose } = props;
|
const { open, mode } = props;
|
||||||
if (!open || mode.kind === "closed") return null;
|
if (!open || mode.kind === "closed") return null;
|
||||||
if (mode.kind === "view") return <ViewModal {...props} />;
|
if (mode.kind === "view") return <ViewModal {...props} />;
|
||||||
if (mode.kind === "day-in-range")
|
if (mode.kind === "day-in-range")
|
||||||
|
|||||||
@@ -234,7 +234,6 @@ function computeUserTotals(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const holidayDates = holidaysInMonth(yr, mo + 1);
|
const holidayDates = holidaysInMonth(yr, mo + 1);
|
||||||
const holidayCount = holidayDates.length;
|
|
||||||
|
|
||||||
for (const uid of Object.keys(totals)) {
|
for (const uid of Object.keys(totals)) {
|
||||||
const t = totals[uid];
|
const t = totals[uid];
|
||||||
@@ -476,14 +475,12 @@ function buildUserSectionHtml(
|
|||||||
// Sum of vacation/sick/unpaid hours (in hours, not minutes). The
|
// Sum of vacation/sick/unpaid hours (in hours, not minutes). The
|
||||||
// "holiday" leave_type is auto-computed from Czech holidays further down
|
// "holiday" leave_type is auto-computed from Czech holidays further down
|
||||||
// and no longer selectable in the UI, so it's deliberately omitted here.
|
// and no longer selectable in the UI, so it's deliberately omitted here.
|
||||||
let vacationHours = 0,
|
let vacationHours = 0;
|
||||||
sickHours = 0;
|
|
||||||
for (const r of userRecords) {
|
for (const r of userRecords) {
|
||||||
const lt = r.leave_type || "work";
|
const lt = r.leave_type || "work";
|
||||||
if (lt === "work") continue;
|
if (lt === "work") continue;
|
||||||
const h = Number(r.leave_hours) || 8;
|
const h = Number(r.leave_hours) || 8;
|
||||||
if (lt === "vacation") vacationHours += h;
|
if (lt === "vacation") vacationHours += h;
|
||||||
else if (lt === "sick") sickHours += h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Odpracováno: floor(worked / 8) × 8.
|
// Odpracováno: floor(worked / 8) × 8.
|
||||||
@@ -492,7 +489,6 @@ function buildUserSectionHtml(
|
|||||||
|
|
||||||
// Free Svátek: 8h per holiday date the user did not work.
|
// Free Svátek: 8h per holiday date the user did not work.
|
||||||
const holidayDates = holidaysInMonth(yr, mo);
|
const holidayDates = holidaysInMonth(yr, mo);
|
||||||
const holidayCount = holidayDates.length;
|
|
||||||
const workedDates = new Set(
|
const workedDates = new Set(
|
||||||
userRecords
|
userRecords
|
||||||
.filter((r) => (r.leave_type || "work") === "work")
|
.filter((r) => (r.leave_type || "work") === "work")
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ async function performMutation<TIn, TOut>(opts: {
|
|||||||
return result.data as TOut;
|
return result.data as TOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiMutationOptions<TIn, TOut> {
|
export interface ApiMutationOptions<TIn> {
|
||||||
url: string | ((input: TIn) => string);
|
url: string | ((input: TIn) => string);
|
||||||
method: HttpMethod | ((input: TIn) => HttpMethod);
|
method: HttpMethod | ((input: TIn) => HttpMethod);
|
||||||
/** Query-key prefixes to invalidate on success. Broad per CLAUDE.md. */
|
/** Query-key prefixes to invalidate on success. Broad per CLAUDE.md. */
|
||||||
@@ -122,7 +122,7 @@ export interface ApiMutationOptions<TIn, TOut> {
|
|||||||
* as `ApiError` (with `.status` attached) so callers can branch on HTTP code.
|
* as `ApiError` (with `.status` attached) so callers can branch on HTTP code.
|
||||||
*/
|
*/
|
||||||
export function useApiMutation<TIn, TOut>(
|
export function useApiMutation<TIn, TOut>(
|
||||||
opts: ApiMutationOptions<TIn, TOut> &
|
opts: ApiMutationOptions<TIn> &
|
||||||
Omit<UseMutationOptions<TOut, ApiError, TIn>, "mutationFn">,
|
Omit<UseMutationOptions<TOut, ApiError, TIn>, "mutationFn">,
|
||||||
) {
|
) {
|
||||||
const { url, method, invalidate, envelope, onSuccess, ...rest } = opts;
|
const { url, method, invalidate, envelope, onSuccess, ...rest } = opts;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { queryOptions } from "@tanstack/react-query";
|
import { queryOptions } from "@tanstack/react-query";
|
||||||
import apiFetch from "../../utils/api";
|
|
||||||
import { jsonQuery } from "../apiAdapter";
|
import { jsonQuery } from "../apiAdapter";
|
||||||
|
|
||||||
export interface PlanUser {
|
export interface PlanUser {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useMemo, useRef } from "react";
|
import { useState, useMemo, useRef } from "react";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
@@ -9,7 +9,6 @@ import { iconBadgeSx } from "../theme";
|
|||||||
import {
|
import {
|
||||||
attendanceHistoryOptions,
|
attendanceHistoryOptions,
|
||||||
type AttendanceRecord,
|
type AttendanceRecord,
|
||||||
type ProjectLogEntry,
|
|
||||||
} from "../lib/queries/attendance";
|
} from "../lib/queries/attendance";
|
||||||
import {
|
import {
|
||||||
formatDate,
|
formatDate,
|
||||||
@@ -201,7 +200,6 @@ const renderProjectCell = (record: AttendanceRecord) => {
|
|||||||
|
|
||||||
export default function AttendanceHistory() {
|
export default function AttendanceHistory() {
|
||||||
const { user, hasPermission } = useAuth();
|
const { user, hasPermission } = useAuth();
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { data: companySettings } = useQuery(companySettingsOptions());
|
const { data: companySettings } = useQuery(companySettingsOptions());
|
||||||
const companyName = companySettings?.company_name || "";
|
const companyName = companySettings?.company_name || "";
|
||||||
const printRef = useRef<HTMLDivElement>(null);
|
const printRef = useRef<HTMLDivElement>(null);
|
||||||
|
|||||||
@@ -26,10 +26,7 @@ const LocationMap = styled("div")(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
import { formatDate, formatTime } from "../utils/attendanceHelpers";
|
import { formatDate, formatTime } from "../utils/attendanceHelpers";
|
||||||
import {
|
import { attendanceLocationOptions } from "../lib/queries/attendance";
|
||||||
attendanceLocationOptions,
|
|
||||||
type LocationRecord,
|
|
||||||
} from "../lib/queries/attendance";
|
|
||||||
import { Button, Card, LoadingState, PageEnter, PageHeader } from "../ui";
|
import { Button, Card, LoadingState, PageEnter, PageHeader } from "../ui";
|
||||||
|
|
||||||
const BackIcon = (
|
const BackIcon = (
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { useAlert } from "../context/AlertContext";
|
import { useAlert } from "../context/AlertContext";
|
||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
import Forbidden from "../components/Forbidden";
|
import Forbidden from "../components/Forbidden";
|
||||||
import {
|
import { companySettingsOptions } from "../lib/queries/settings";
|
||||||
companySettingsOptions,
|
|
||||||
type CompanySettingsData,
|
|
||||||
} from "../lib/queries/settings";
|
|
||||||
import { bankAccountsOptions, type BankAccount } from "../lib/queries/common";
|
import { bankAccountsOptions, type BankAccount } from "../lib/queries/common";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
@@ -23,7 +20,6 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
Select,
|
Select,
|
||||||
CheckboxField,
|
CheckboxField,
|
||||||
StatusChip,
|
|
||||||
FileUpload,
|
FileUpload,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
type DataColumn,
|
type DataColumn,
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import { useState, useEffect, useMemo, useRef } from "react";
|
import { useState, useEffect, useMemo, useRef } from "react";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import { orderDetailOptions, type OrderItem } from "../lib/queries/orders";
|
||||||
orderDetailOptions,
|
|
||||||
type OrderData,
|
|
||||||
type OrderItem,
|
|
||||||
type OrderSection,
|
|
||||||
type OrderInvoice,
|
|
||||||
type OrderProject,
|
|
||||||
} from "../lib/queries/orders";
|
|
||||||
import { useApiMutation } from "../lib/queries/mutations";
|
import { useApiMutation } from "../lib/queries/mutations";
|
||||||
import { useAlert } from "../context/AlertContext";
|
import { useAlert } from "../context/AlertContext";
|
||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import MenuItem from "@mui/material/MenuItem";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
projectDetailOptions,
|
projectDetailOptions,
|
||||||
type ProjectData,
|
|
||||||
type ProjectNote,
|
type ProjectNote,
|
||||||
} from "../lib/queries/projects";
|
} from "../lib/queries/projects";
|
||||||
import { userListOptions, type User as ApiUser } from "../lib/queries/users";
|
import { userListOptions, type User as ApiUser } from "../lib/queries/users";
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export default function Trips() {
|
|||||||
TripForm,
|
TripForm,
|
||||||
{ message?: string; error?: string }
|
{ message?: string; error?: string }
|
||||||
>({
|
>({
|
||||||
url: (formData) =>
|
url: () =>
|
||||||
editingTrip ? `${API_BASE}/trips/${editingTrip.id}` : `${API_BASE}/trips`,
|
editingTrip ? `${API_BASE}/trips/${editingTrip.id}` : `${API_BASE}/trips`,
|
||||||
method: () => (editingTrip ? "PUT" : "POST"),
|
method: () => (editingTrip ? "PUT" : "POST"),
|
||||||
invalidate: ["trips", "vehicles"],
|
invalidate: ["trips", "vehicles"],
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface InventoryItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseDecimal(raw: string): number {
|
function parseDecimal(raw: string): number {
|
||||||
const cleaned = raw.replace(/[eE+\-]/g, "");
|
const cleaned = raw.replace(/[eE+-]/g, "");
|
||||||
const n = Number(cleaned);
|
const n = Number(cleaned);
|
||||||
return Number.isFinite(n) ? n : 0;
|
return Number.isFinite(n) ? n : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ interface BatchesResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseDecimal(raw: string): number {
|
function parseDecimal(raw: string): number {
|
||||||
const cleaned = raw.replace(/[eE+\-]/g, "");
|
const cleaned = raw.replace(/[eE+-]/g, "");
|
||||||
const n = Number(cleaned);
|
const n = Number(cleaned);
|
||||||
return Number.isFinite(n) ? n : 0;
|
return Number.isFinite(n) ? n : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ interface MovementItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseDecimal(raw: string): number {
|
function parseDecimal(raw: string): number {
|
||||||
const cleaned = raw.replace(/[eE+\-]/g, "");
|
const cleaned = raw.replace(/[eE+-]/g, "");
|
||||||
const n = Number(cleaned);
|
const n = Number(cleaned);
|
||||||
return Number.isFinite(n) ? n : 0;
|
return Number.isFinite(n) ? n : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { FastifyRequest, FastifyReply } from "fastify";
|
import { FastifyRequest, FastifyReply } from "fastify";
|
||||||
import { verifyAccessToken } from "../services/auth";
|
import { verifyAccessToken } from "../services/auth";
|
||||||
import { error } from "../utils/response";
|
import { error } from "../utils/response";
|
||||||
import { AuthData } from "../types";
|
|
||||||
|
|
||||||
export async function requireAuth(
|
export async function requireAuth(
|
||||||
request: FastifyRequest,
|
request: FastifyRequest,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FastifyInstance } from "fastify";
|
import { FastifyInstance } from "fastify";
|
||||||
import prisma from "../../config/database";
|
import prisma from "../../config/database";
|
||||||
import { requireAuth, requirePermission } from "../../middleware/auth";
|
import { requirePermission } from "../../middleware/auth";
|
||||||
import { logAudit } from "../../services/audit";
|
import { logAudit } from "../../services/audit";
|
||||||
import { success, error, parseId, paginated } from "../../utils/response";
|
import { success, error, parseId, paginated } from "../../utils/response";
|
||||||
import { parsePagination, buildPaginationMeta } from "../../utils/pagination";
|
import { parsePagination, buildPaginationMeta } from "../../utils/pagination";
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
markOverdueInvoices,
|
markOverdueInvoices,
|
||||||
listInvoices,
|
listInvoices,
|
||||||
getInvoiceListTotals,
|
getInvoiceListTotals,
|
||||||
getNextInvoiceNumberFormatted,
|
|
||||||
getNextInvoiceNumberPreview,
|
getNextInvoiceNumberPreview,
|
||||||
getInvoiceStats,
|
getInvoiceStats,
|
||||||
getOrderDataForInvoice,
|
getOrderDataForInvoice,
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export default async function projectFilesRoutes(
|
|||||||
|
|
||||||
const subPath = parsedQuery.data.path || "";
|
const subPath = parsedQuery.data.path || "";
|
||||||
const rawFileName = file.filename;
|
const rawFileName = file.filename;
|
||||||
const fileName = rawFileName.replace(/[\/\\:*?"<>|]/g, "_");
|
const fileName = rawFileName.replace(/[/\\:*?"<>|]/g, "_");
|
||||||
|
|
||||||
const buffer = await file.toBuffer();
|
const buffer = await file.toBuffer();
|
||||||
const err = await fm.uploadFile(
|
const err = await fm.uploadFile(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { FastifyInstance } from "fastify";
|
|||||||
import multipart from "@fastify/multipart";
|
import multipart from "@fastify/multipart";
|
||||||
import prisma from "../../config/database";
|
import prisma from "../../config/database";
|
||||||
import { config } from "../../config/env";
|
import { config } from "../../config/env";
|
||||||
import { requireAuth, requirePermission } from "../../middleware/auth";
|
import { requirePermission } from "../../middleware/auth";
|
||||||
import { logAudit } from "../../services/audit";
|
import { logAudit } from "../../services/audit";
|
||||||
import { success, error, parseId, paginated } from "../../utils/response";
|
import { success, error, parseId, paginated } from "../../utils/response";
|
||||||
import { contentDisposition } from "../../utils/content-disposition";
|
import { contentDisposition } from "../../utils/content-disposition";
|
||||||
@@ -2209,7 +2209,7 @@ export default async function warehouseRoutes(
|
|||||||
// Build items with auto-filled system_qty and calculated difference
|
// Build items with auto-filled system_qty and calculated difference
|
||||||
const itemsData = await Promise.all(
|
const itemsData = await Promise.all(
|
||||||
body.items.map(async (item) => {
|
body.items.map(async (item) => {
|
||||||
let systemQty = 0;
|
let systemQty: number;
|
||||||
|
|
||||||
if (item.location_id != null) {
|
if (item.location_id != null) {
|
||||||
// Get stock at specific location
|
// Get stock at specific location
|
||||||
@@ -2318,7 +2318,7 @@ export default async function warehouseRoutes(
|
|||||||
if (body.items) {
|
if (body.items) {
|
||||||
itemsData = await Promise.all(
|
itemsData = await Promise.all(
|
||||||
body.items.map(async (item) => {
|
body.items.map(async (item) => {
|
||||||
let systemQty = 0;
|
let systemQty: number;
|
||||||
|
|
||||||
if (item.location_id != null) {
|
if (item.location_id != null) {
|
||||||
const loc = await prisma.sklad_item_locations.findUnique({
|
const loc = await prisma.sklad_item_locations.findUnique({
|
||||||
|
|||||||
@@ -791,7 +791,7 @@ export async function getProjectReport(year: number) {
|
|||||||
|
|
||||||
// Use detailed project logs (started_at/ended_at or hours/minutes)
|
// Use detailed project logs (started_at/ended_at or hours/minutes)
|
||||||
for (const log of rec.attendance_project_logs) {
|
for (const log of rec.attendance_project_logs) {
|
||||||
let hours = 0;
|
let hours: number;
|
||||||
if (log.hours != null || log.minutes != null) {
|
if (log.hours != null || log.minutes != null) {
|
||||||
hours = (Number(log.hours) || 0) + (Number(log.minutes) || 0) / 60;
|
hours = (Number(log.hours) || 0) + (Number(log.minutes) || 0) / 60;
|
||||||
} else if (log.started_at && log.ended_at) {
|
} else if (log.started_at && log.ended_at) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import bcrypt from "bcryptjs";
|
import bcrypt from "bcryptjs";
|
||||||
import { FastifyRequest, FastifyReply } from "fastify";
|
import { FastifyRequest } from "fastify";
|
||||||
import prisma from "../config/database";
|
import prisma from "../config/database";
|
||||||
import { config } from "../config/env";
|
import { config } from "../config/env";
|
||||||
import { AuthData, JwtPayload } from "../types";
|
import { AuthData, JwtPayload } from "../types";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PaginationQuery, PaginationMeta } from "../types";
|
import { PaginationMeta } from "../types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse common pagination/sort/search query params.
|
* Parse common pagination/sort/search query params.
|
||||||
|
|||||||
Reference in New Issue
Block a user