move some misc functions

This commit is contained in:
honzapatCZ 2024-10-18 19:19:18 +02:00
parent 4c892816ba
commit 5639551dd5
9 changed files with 448 additions and 2 deletions

View File

@ -1,8 +1,8 @@
import { FormikForm } from "@shared/nej-react-utils/Form/FormikForm"; import { FormikForm } from "@shared/nej-react-utils/Form/FormikForm";
import { NejAccountingApiClient, CompanyResponse, CancelablePromise } from "@services/accounting-api"; import { NejAccountingApiClient, CompanyResponse, CancelablePromise } from "@services/accounting-api";
import { useApiClient, useCompany } from "@utils/NejManager/NejProvider"; import { useApiClient, useCompany } from "@utils/NejManager/NejProvider";
import useMethod from "../../../utils/useMethod.macro"; import useMethod from "../macros/useMethod.macro";
import useQuery from "../../../utils/useQuery.macro"; import useQuery from "../macros/useQuery.macro";
type FormProps<T> = { type FormProps<T> = {
refresh: () => void; refresh: () => void;

5
macros/useMethod.macro.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { CancelablePromise, Error } from "src/services/accounting-api";
export default function useMethod<T, Arg>(method: (...args: Arg) => CancelablePromise<T>,
onDone?: (data: T) => void,
onError?: (error: Error) => void = null): [(...args: Arg) => void, { data: T, loading: boolean, error: Error }];

167
macros/useMethod.macro.js Normal file
View File

@ -0,0 +1,167 @@
const { createMacro, MacroError } = require("babel-plugin-macros")
const { addNamed } = require("@babel/helper-module-imports")
const path = require("path")
// `createMacro` is simply a function that ensures your macro is only
// called in the context of a babel transpilation and will throw an
// error with a helpful message if someone does not have babel-plugin-macros
// configured correctly
module.exports = createMacro(useMethod)
function useMethod({ references, state, babel }) {
// state is the second argument you're passed to a visitor in a
// normal babel plugin. `babel` is the `babel-plugin-macros` module.
// do whatever you like to the AST paths you find in `references`
// read more below...
const { default: defaultImport = [] } = references
defaultImport.forEach(referencePath => {
if (referencePath.parentPath.type != "CallExpression") {
throw new MacroError(
"useQuery should be called with a function",
)
}
const ourArguments = referencePath.parentPath.get("arguments")
if (ourArguments.length < 1) {
throw new MacroError(
"There are not enough arguments",
)
}
else if (ourArguments.length > 3) {
throw new MacroError(
"There are too many arguments",
)
}
if (ourArguments[0].has("callee")) {
throw new MacroError(
"function used in useMethod can not be a function call",
)
}
else if (ourArguments.length > 1 && !ourArguments[1].isFunction()) {
throw new MacroError(
"useMethods 2nd argument must be a callable function",
)
}
else if (ourArguments.length > 2 && !ourArguments[2].isFunction()) {
throw new MacroError(
"useMethods 3rd argument must be a callable function",
)
}
let declPath = referencePath.parentPath.parentPath;
let call = ourArguments[0].node
let nop = babel.types.arrowFunctionExpression([babel.types.identifier("e")], babel.types.blockStatement([]))
let onDone = ourArguments[1] ? ourArguments[1].node : nop;
let onError = ourArguments[2] ? ourArguments[2].node : null;
let parsWithSet = referencePath.parentPath.parentPath.get("id").get("elements")
if (!parsWithSet[0].isIdentifier())
throw new MacroError(
"first result of useMethod must be a function name",
)
let runMethod = parsWithSet[0].node
if (!parsWithSet[1].isObjectPattern())
throw new MacroError(
"second result of useMethod must be the returned data object",
)
let pars = parsWithSet[1].node
let setStateIdentifier = declPath.scope.generateUidIdentifier("setStateInternalMagic")
let variableArgsId = declPath.scope.generateUidIdentifier("variableArgsInternalMagic")
let useStateHookImport = addNamed(declPath, "useState", "react");
let useCallbackHookImport = addNamed(declPath, "useCallback", "react");
let servicePath = "./" + path.relative(state.file.opts.filename, path.join(__dirname, "../services/accounting-api")).trim();
let apiErrorImport = addNamed(declPath, "ApiError", servicePath);
let nejServicesPath = "./" + path.relative(path.dirname(state.file.opts.filename), path.join(__dirname, "NejManager/NejProvider")).trim();
let emptyErrorIdentifier = declPath.scope.generateUidIdentifier("emptyErrorInternalMagic")
let onErrorIdentifier = declPath.scope.generateUidIdentifier("onErrorInternalMagic")
let onSuccessIdentifier = declPath.scope.generateUidIdentifier("onSuccessInternalMagic")
let onStartLoadingIdentifier = declPath.scope.generateUidIdentifier("onErrorInternalMagic")
let onStopLoadingIdentifier = declPath.scope.generateUidIdentifier("onErrorInternalMagic")
let useOnErrorImport = addNamed(declPath, "useOnError", nejServicesPath);
let useOnSuccessImport = addNamed(declPath, "useOnSuccess", nejServicesPath);
let useStartLoadingImport = addNamed(declPath, "useStartLoading", nejServicesPath);
let useStopLoadingImport = addNamed(declPath, "useStopLoading", nejServicesPath);
let useStateSrc = babel.template("[DATA, CALL_SET] = USE_STATE({data: null, error: null, loading: false})")({
DATA: pars,
CALL_SET: setStateIdentifier,
USE_STATE: useStateHookImport
})
declPath.replaceWith(useStateSrc)
let src = babel.template(`
const ON_ERROR_IDENTIFIER = USE_ERROR_IMPORT();
const ON_SUCCESS_IDENTIFIER = USE_SUCCESS_IMPORT();
const ON_START_LOADING_IDENTIFIER = USE_START_LOADING_IMPORT();
const ON_STOP_LOADING_IDENTIFIER = USE_STOP_LOADING_IMPORT();
function EMPTY_ERROR(x){
return null
}
const RUN_NAME = USE_CALLBACK(async (...ARGS) => {
let notifKey = "";
try{
notifKey = ON_START_LOADING_IDENTIFIER();
CALL_SET({ data: null, error: null, loading: true });
let ret = await IMPORT_NAME(...ARGS);
CALL_SET({ data: ret, error: null, loading: false });
ON_STOP_LOADING_IDENTIFIER(notifKey);
ON_SUCCESS_IDENTIFIER();
DONE(ret);
return ret;
}
catch(e){
if(e instanceof ERROR_CLASS){
CALL_SET({data: null, error: e.body, loading: false})
ON_STOP_LOADING_IDENTIFIER(notifKey);
ON_ERROR_IDENTIFIER(e.body);
ERROR(e.body)
return
}
CALL_SET({data: null, error: e, loading: false})
ON_STOP_LOADING_IDENTIFIER(notifKey);
ON_ERROR_IDENTIFIER(e);
ERROR(e)
return null;
}
}, [])
`)({
IMPORT_NAME: call,
CALL_SET: setStateIdentifier,
RUN_NAME: runMethod,
ARGS: variableArgsId,
USE_CALLBACK: useCallbackHookImport,
ERROR_CLASS: apiErrorImport,
DONE: onDone,
//if its null create an empty function to call
ERROR: onError != null ? onError : emptyErrorIdentifier,
EMPTY_ERROR: emptyErrorIdentifier,
USE_ERROR_IMPORT: useOnErrorImport,
USE_SUCCESS_IMPORT: useOnSuccessImport,
USE_START_LOADING_IMPORT: useStartLoadingImport,
USE_STOP_LOADING_IMPORT: useStopLoadingImport,
ON_SUCCESS_IDENTIFIER: onSuccessIdentifier,
ON_START_LOADING_IDENTIFIER: onStartLoadingIdentifier,
ON_STOP_LOADING_IDENTIFIER: onStopLoadingIdentifier,
ON_ERROR_IDENTIFIER: onErrorIdentifier,
})
declPath.parentPath.insertAfter(src)
})
}

15
macros/useQuery.macro.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
import { CancelablePromise, Error } from "src/services/accounting-api";
//default export is a function
/*
export default function useQuery<T>(query: CancelablePromise<Error | T>): [{
data: T;
loading: boolean;
error: Error;
}, () => void];
*/
export default function useQuery<T>(query: CancelablePromise<T>): [{
data: T;
loading: boolean;
error: Error;
}, () => void];

134
macros/useQuery.macro.js Normal file
View File

@ -0,0 +1,134 @@
const { createMacro, MacroError } = require("babel-plugin-macros")
const { addNamed } = require("@babel/helper-module-imports")
const path = require("path")
const Babel = require("@babel/core")
// `createMacro` is simply a function that ensures your macro is only
// called in the context of a babel transpilation and will throw an
// error with a helpful message if someone does not have babel-plugin-macros
// configured correctly
module.exports = createMacro(useQuery);
//jsdoc
/**
* @param {Object} param0
* @param {import("@babel/types").ArrayExpression} param0.references
* @param {Object} param0.state
* @param {Babel} param0.babel
*/
function useQuery({ references, state, babel }) {
// state is the second argument you're passed to a visitor in a
// normal babel plugin. `babel` is the `babel-plugin-macros` module.
// do whatever you like to the AST paths you find in `references`
// read more below...
const { default: defaultImport = [] } = references
defaultImport.forEach(referencePath => {
if (referencePath.parentPath.type != "CallExpression") {
throw new MacroError(
"useQuery should be called with a function",
)
}
const ourArguments = referencePath.parentPath.get("arguments")
if (ourArguments.length < 1) {
throw new MacroError(
"There are not enough arguments",
)
}
else if (ourArguments.length > 1) {
throw new MacroError(
"There are too many arguments",
)
}
//jsdoc
/**
* @type {import("@babel/types").CallExpression}
* */
let call = ourArguments[0].node
let declPath = referencePath.parentPath.parentPath;
let setStateIdentifier = declPath.scope.generateUidIdentifier("setStateInternalMagic")
let refreshFuncName = declPath.scope.generateUidIdentifier("refreshInternalMagic")
let queryObjName = declPath.scope.generateUidIdentifier("queryInternalMagic");
let assignParsPath = referencePath.parentPath.parentPath.get("id");
let pars = assignParsPath.node
if (!assignParsPath.isObjectPattern()) {
if (!assignParsPath.isArrayPattern() || !assignParsPath.get("elements")[0].isObjectPattern())
throw new MacroError(
"Return is either just the object or array of object and refresh method",
)
pars = assignParsPath.get("elements")[0].node
if (assignParsPath.get("elements").length > 1) {
if (!assignParsPath.get("elements")[1].isIdentifier())
throw new MacroError(
"There is second argument to array return, but its surprisingly not a function identifier",
)
refreshFuncName = assignParsPath.get("elements")[1].node
}
}
let useStateHookImport = addNamed(declPath, "useState", "react");
let useCallbackHookImport = addNamed(declPath, "useCallback", "react");
let useEffectHookImport = addNamed(declPath, "useEffect", "react");
let useRQuery = addNamed(declPath, "useQuery", "@tanstack/react-query");
//create a string literal for identification
//reduce the callee's MemberExpression object and property to astring with member.member.member.etc...
var reduceCallee = (callee) => {
if (callee.type == "Identifier") return callee.name
return reduceCallee(callee.object) + "." + reduceCallee(callee.property)
}
let fullCallPathString = reduceCallee(call.callee)
let servicePath = "./" + path.relative(state.file.opts.filename, path.join(__dirname, "../services/accounting-api")).trim();
let apiErrorImport = addNamed(declPath, "ApiError", servicePath);
let useStateSrc = babel.template("[DATA, CALL_SET] = USE_STATE({data: null, error: null, loading: true})")({
DATA: pars,
CALL_SET: setStateIdentifier,
USE_STATE: useStateHookImport
})
declPath.replaceWith(useStateSrc)
let src = babel.template(`
const Q_UNAME = USE_RQUERY({queryKey: ["Q_KEY", ...(new Array(DEPS))], queryFn: async () => IMPORT_NAME})
USE_EFFECT(() => {
if(Q_UNAME.error instanceof ERROR_CLASS){
CALL_SET({...Q_UNAME, error: Q_UNAME.error.body, loading: Q_UNAME.isLoading})
return
}
CALL_SET({...Q_UNAME, loading: Q_UNAME.isLoading})
}, [Q_UNAME.data, Q_UNAME.error, Q_UNAME.isLoading])
const REFRESH = USE_CALLBACK(async () =>{
await Q_UNAME.refetch()
}, [Q_UNAME.data, Q_UNAME.error, Q_UNAME.isLoading])
`)({
IMPORT_NAME: call,
DEPS: call.arguments,
CALL_SET: setStateIdentifier,
USE_EFFECT: useEffectHookImport,
USE_CALLBACK: useCallbackHookImport,
ERROR_CLASS: apiErrorImport,
REFRESH: refreshFuncName,
Q_UNAME: queryObjName,
Q_KEY: fullCallPathString,
USE_RQUERY: useRQuery
})
declPath.parentPath.insertAfter(src)
})
}

31
misc/downloadBlob.js Normal file
View File

@ -0,0 +1,31 @@
import axios from "axios";
export default function downloadAsBlob(data, name) {
// Create blob link to download
const url = window.URL.createObjectURL(
new Blob([data]),
);
console.log(url);
downloadInteral(url, name);
}
export async function downloadUrl(url, name){
let res = await axios.get(url, {responseType: "blob"});
downloadAsBlob(res.data, name);
}
function downloadInteral(url, name){
const link = document.createElement("a");
link.href = url;
link.download = name;
link.target = "_blank";
// Append to html link element page
document.body.appendChild(link);
// Start download
link.click();
// Clean up and remove the link
link.parentNode.removeChild(link);
}

18
misc/getDate.ts Normal file
View File

@ -0,0 +1,18 @@
export default function getInputDate(date: Date | string) {
if (!date)
return date;
if (typeof (date) === "string") {
date = new Date(date);
}
try {
return new Date(date.getTime() - new Date().getTimezoneOffset() * 60000).toISOString().substring(0, 19)
} catch {
return new Date(new Date().getTimezoneOffset() * 60000).toISOString().substring(0, 19)
}
}

52
misc/pdf.js Normal file
View File

@ -0,0 +1,52 @@
import { Document, Page } from "react-pdf";
import "react-pdf/dist/esm/Page/AnnotationLayer.css";
import 'react-pdf/dist/esm/Page/TextLayer.css';
import Pagination from "@shared/nej-react-components/Parts/Pagination";
import tw, { styled } from "twin.macro"
import "styled-components/macro"
import { useState } from "react";
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url,
).toString();
export default function PDF({ data }) {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
return (
<div tw="w-full flex flex-col items-start">
<Document file={data} onLoadSuccess={onDocumentLoadSuccess}>
<Page pageNumber={pageNumber} />
</Document>
{/*
<div tw=" w-[21cm] h-[29.7cm] mt-20 overflow-hidden">
<BalanceDocument />
</div>
<div tw=" w-[80mm] mt-20 overflow-hidden">
<ReceiptDocument />
</div>
<div tw=" w-[21cm] mt-10 overflow-hidden h-[29.7cm]">
<CorrectureDocument data={data} />
</div>
<div tw=" w-[21cm] mt-20 overflow-hidden">
<TransactionDocument data={data}/>
</div> */}
<p tw="mt-4">
Page {pageNumber} of {numPages}
</p>
<Pagination maxPages={numPages} currentPage={pageNumber} setPage={(p) => setPageNumber(p)} />
</div>
);
}

24
misc/printPage.js Normal file
View File

@ -0,0 +1,24 @@
function closePrint() {
document.body.removeChild(this.__container__);
}
function setPrint() {
this.contentWindow.__container__ = this;
this.contentWindow.onbeforeunload = closePrint;
this.contentWindow.onafterprint = closePrint;
this.contentWindow.focus(); // Required for IE
this.contentWindow.print();
}
export default function printPage(sURL) {
const hideFrame = document.createElement("iframe");
hideFrame.onload = setPrint;
hideFrame.style.position = "fixed";
hideFrame.style.right = "0";
hideFrame.style.bottom = "0";
hideFrame.style.width = "0";
hideFrame.style.height = "0";
hideFrame.style.border = "0";
hideFrame.src = sURL;
document.body.appendChild(hideFrame);
}