init
This commit is contained in:
commit
fa35bdb992
12
Flag.tsx
Normal file
12
Flag.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Country } from "@services/accounting-api";
|
||||||
|
import { lazy, Suspense } from 'react';
|
||||||
|
|
||||||
|
export default function Flag({ country, ...props }: { country: Country | "EU" }) {
|
||||||
|
const FlagIcon = lazy(() => import(`country-flag-icons/react/3x2`).then(module => ({ default: module[country] })));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div {...props}>...</div>}>
|
||||||
|
<FlagIcon {...props}/>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
100
Form/Core/AutocompleteSelect.js
Normal file
100
Form/Core/AutocompleteSelect.js
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { Combobox } from "@headlessui/react";
|
||||||
|
|
||||||
|
import { DropDownItem } from "@shared/nej-react-components/Parts/DropDown";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import tw, { styled } from "twin.macro";
|
||||||
|
import "styled-components/macro";
|
||||||
|
import { useField } from "@shared/nej-react-components/Parts/Input";
|
||||||
|
|
||||||
|
export function AutocompleteSelect({ title, titleProps = null, data, nullable = false, ...props }) {
|
||||||
|
const [field, meta, helpers] = useField(props);
|
||||||
|
|
||||||
|
const [query, setQuery] = useState(field.value ?? "");
|
||||||
|
const [internalData, setInternalData] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (typeof data === "function") {
|
||||||
|
//if data is async function await it
|
||||||
|
setInternalData(await data(query));
|
||||||
|
} else if (Array.isArray(data)) {
|
||||||
|
setInternalData(
|
||||||
|
query === ""
|
||||||
|
? data
|
||||||
|
: data.filter((item) => {
|
||||||
|
if (typeof item === "string")
|
||||||
|
return item.toLowerCase().includes(query.toLowerCase());
|
||||||
|
|
||||||
|
return item.label.toLowerCase().includes(query.toLowerCase());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error("data is not an array or function :c");
|
||||||
|
setInternalData([]);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [query, data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Combobox
|
||||||
|
{...props}
|
||||||
|
{...field}
|
||||||
|
value={
|
||||||
|
field.value
|
||||||
|
? internalData?.filter((item) =>
|
||||||
|
typeof item === "string"
|
||||||
|
? item == field.value
|
||||||
|
: item.value === field.value
|
||||||
|
)[0]
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
console.log(value);
|
||||||
|
field.onChange({ target: { value: value, name: field.name } });
|
||||||
|
}}
|
||||||
|
nullable={nullable}
|
||||||
|
>
|
||||||
|
<div css={[title && tw`my-1`, tw`relative`]} >
|
||||||
|
{title && (
|
||||||
|
<label
|
||||||
|
{...titleProps}
|
||||||
|
css={[
|
||||||
|
tw`block text-secondary text-sm font-bold mb-2`,
|
||||||
|
titleProps?.css,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<div tw="relative">
|
||||||
|
<Combobox.Input
|
||||||
|
tw="bg-primary cursor-pointer appearance-none border-2 border-secondary rounded w-full py-2 px-4 text-primary leading-tight focus:outline-none focus:bg-secondary focus:border-accent transition duration-150 "
|
||||||
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
|
displayValue={(val) => (typeof val === "string" ? val : val?.label)}
|
||||||
|
/>
|
||||||
|
<Combobox.Button tw="absolute top-0 right-0 left-0 bottom-0 " />
|
||||||
|
</div>
|
||||||
|
<Combobox.Options tw="absolute z-10 max-h-60 w-full bg-trinary rounded-xl overflow-y-auto overflow-x-hidden">
|
||||||
|
{internalData?.map((val) => (
|
||||||
|
<Combobox.Option
|
||||||
|
tw=""
|
||||||
|
key={typeof val === "string" ? val : val.value}
|
||||||
|
value={typeof val === "string" ? val : val.value}
|
||||||
|
>
|
||||||
|
<DropDownItem>
|
||||||
|
{typeof val === "string" ? val : val.label}
|
||||||
|
</DropDownItem>
|
||||||
|
</Combobox.Option>
|
||||||
|
))}
|
||||||
|
</Combobox.Options>
|
||||||
|
</div>
|
||||||
|
</Combobox>
|
||||||
|
|
||||||
|
{meta?.touched && meta.error ? (
|
||||||
|
<div tw="text-[#c23c3c]">{meta.error}</div>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
Form/Core/DateFields.js
Normal file
22
Form/Core/DateFields.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import tw, { styled } from "twin.macro";
|
||||||
|
import "styled-components/macro";
|
||||||
|
|
||||||
|
import Input, { useField } from "@shared/nej-react-components/Parts/Input";
|
||||||
|
|
||||||
|
export default function DateField(props) {
|
||||||
|
|
||||||
|
const [{onChange, value, ...field}, meta, helpers] = useField(props);
|
||||||
|
|
||||||
|
let realDate = (typeof(value) === "string" ? new Date(Date.parse(value)) : value);
|
||||||
|
if(typeof(realDate) != "object")
|
||||||
|
realDate = null;
|
||||||
|
//check if its ivnalid, if so make it null
|
||||||
|
if(isNaN(realDate))
|
||||||
|
realDate = null;
|
||||||
|
|
||||||
|
const {value: _x, onChange: _y, ...restProps} = props;
|
||||||
|
|
||||||
|
return <Input type="date"
|
||||||
|
value={realDate?.toISOString().slice(0,10)}
|
||||||
|
onChange={(e)=>onChange(new Date(e.target.value))} {...field} {...restProps} />;
|
||||||
|
}
|
||||||
32
Form/Core/DateTimeField.js
Normal file
32
Form/Core/DateTimeField.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import tw, { styled } from "twin.macro";
|
||||||
|
import "styled-components/macro";
|
||||||
|
|
||||||
|
import Input, { useField } from "@shared/nej-react-components/Parts/Input";
|
||||||
|
|
||||||
|
export default function DateTimeField(props) {
|
||||||
|
|
||||||
|
const [{onChange, value, ...field}, meta, helpers] = useField(props);
|
||||||
|
|
||||||
|
//console.log(value)
|
||||||
|
/***
|
||||||
|
* @type {Date}
|
||||||
|
*/
|
||||||
|
let realDate = (typeof(value) === "string" ? new Date(Date.parse( (value.length > 19 || value.length < 10) ? value : value + "Z")) : value);
|
||||||
|
if(typeof(realDate) != "object")
|
||||||
|
realDate = null;
|
||||||
|
//check if its ivnalid, if so make it null
|
||||||
|
if(isNaN(realDate))
|
||||||
|
realDate = null;
|
||||||
|
|
||||||
|
const {value: _x, onChange: _y, ...restProps} = props;
|
||||||
|
// console.log(realDate)
|
||||||
|
// console.log(realDate?.toISOString().slice(0,19))
|
||||||
|
|
||||||
|
return <Input type="datetime-local"
|
||||||
|
value={realDate?.toISOString().slice(0,16)}
|
||||||
|
onChange={(e)=>{
|
||||||
|
const value = e.target.value;
|
||||||
|
console.log( (value.length > 19 || value.length < 10) ? value : value + "Z")
|
||||||
|
onChange(new Date( (value.length > 19 || value.length < 10) ? value : value + "Z"))
|
||||||
|
}} {...field} {...restProps} />;
|
||||||
|
}
|
||||||
31
Form/Core/FileField.js
Normal file
31
Form/Core/FileField.js
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import tw from "twin.macro";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { useApiClient, useCompany } from "@utils/NejManager/NejProvider";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useField } from "formik";
|
||||||
|
import NejDropzone from "@components/File/NejDropzone";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a file input field with dropzone functionality.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The component props.
|
||||||
|
* @param {boolean} props.single - Whether to allow only one file to be uploaded
|
||||||
|
* @param {string | string[]} props.accept - Array of supported file extensions.
|
||||||
|
* @returns {JSX.Element} The rendered file input field.
|
||||||
|
*/
|
||||||
|
export function FileField({ single, accept, ...props }) {
|
||||||
|
const [field, meta, helpers] = useField(props);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the upload of files.
|
||||||
|
*
|
||||||
|
* @param {Array<File>} files - The files to be uploaded.
|
||||||
|
*/
|
||||||
|
function onUpload(files) {
|
||||||
|
console.log(files)
|
||||||
|
field.onChange({ target: { value: single ? files[0] : files, name: field.name } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return <NejDropzone value={field.value} accept={accept} onDrop={onUpload} single={single}/>;
|
||||||
|
}
|
||||||
8
Form/Core/NumberField.js
Normal file
8
Form/Core/NumberField.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import tw, { styled } from "twin.macro";
|
||||||
|
import "styled-components/macro";
|
||||||
|
|
||||||
|
import Input from "@shared/nej-react-components/Parts/Input";
|
||||||
|
|
||||||
|
export default function NumberField(props) {
|
||||||
|
return <Input step="any" lang="cs" type="number" {...props} />;
|
||||||
|
}
|
||||||
49
Form/FormikForm.tsx
Normal file
49
Form/FormikForm.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Form, Formik, FormikConfig, FormikErrors, FormikProps } from "formik";
|
||||||
|
import { Divider } from "@shared/nej-react-components/Parts/Text";
|
||||||
|
import Button from "@shared/nej-react-components/Parts/Button";
|
||||||
|
|
||||||
|
//formik is defined as
|
||||||
|
//export declare function Formik<Values extends FormikValues = FormikValues, ExtraProps = {}>(props: FormikConfig<Values> & ExtraProps): React.JSX.Element;
|
||||||
|
//we just want to proxy it basically
|
||||||
|
export function FormikForm<Values extends object>({
|
||||||
|
data = {} as Values,
|
||||||
|
onSubmit,
|
||||||
|
submitText = "Submit",
|
||||||
|
schema,
|
||||||
|
onDelete = null,
|
||||||
|
closeModal = null,
|
||||||
|
createForm = null,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: Omit<FormikConfig<Values>, "initialValues"> & {
|
||||||
|
data?: Values;
|
||||||
|
submitText?: string;
|
||||||
|
schema?: any;
|
||||||
|
onDelete?: () => void;
|
||||||
|
closeModal?: () => void;
|
||||||
|
createForm?: (values, handleChange, errors, touched) => React.ReactNode;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}): JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <Formik<Values>
|
||||||
|
initialValues={data}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
validationSchema={schema}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{({ values, handleChange, errors, touched, ...other }) => (
|
||||||
|
<Form>
|
||||||
|
{createForm && createForm(values, handleChange, errors, touched)}
|
||||||
|
{children}
|
||||||
|
<Divider />
|
||||||
|
<div tw="flex justify-center items-center">
|
||||||
|
{onDelete && <Button onClick={onDelete} type="error">{t("Delete")}</Button>}
|
||||||
|
{closeModal && <Button onClick={() => closeModal()}>{t("Close")}</Button>}
|
||||||
|
<Button type="submit">{submitText}</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>;
|
||||||
|
}
|
||||||
35
Form/withForm.tsx
Normal file
35
Form/withForm.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { FormikForm } from "@shared/nej-react-utils/Form/FormikForm";
|
||||||
|
import { NejAccountingApiClient, CompanyResponse, CancelablePromise } from "@services/accounting-api";
|
||||||
|
import { useApiClient, useCompany } from "@utils/NejManager/NejProvider";
|
||||||
|
import useMethod from "../../../utils/useMethod.macro";
|
||||||
|
import useQuery from "../../../utils/useQuery.macro";
|
||||||
|
|
||||||
|
type FormProps<T> = {
|
||||||
|
refresh: () => void;
|
||||||
|
initialData?: T;
|
||||||
|
};
|
||||||
|
export type WithFormProps<T, P> = P & FormProps<T>;
|
||||||
|
export function withForm<T extends object, P extends FormProps<T>>(WrappedForm: React.ComponentType, method: (client: NejAccountingApiClient, company: CompanyResponse, props: P) => (body: T) => CancelablePromise<any>, submitText: string) {
|
||||||
|
return function GameFormWrapper({ refresh, initialData, ...props }: P) {
|
||||||
|
const client = useApiClient();
|
||||||
|
const company = useCompany();
|
||||||
|
|
||||||
|
const fnc = method(client, company, { ...props, initialData } as P);
|
||||||
|
console.log(fnc);
|
||||||
|
|
||||||
|
const [submit, { }] = useMethod(
|
||||||
|
fnc,
|
||||||
|
() => refresh()
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormikForm
|
||||||
|
data={initialData || {} as T}
|
||||||
|
onSubmit={(values) => submit(values)}
|
||||||
|
submitText={submitText}
|
||||||
|
>
|
||||||
|
<WrappedForm />
|
||||||
|
</FormikForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
30
Table/NejReactTable.tsx
Normal file
30
Table/NejReactTable.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { MantineReactTable, MRT_RowData, MRT_TableInstance, MRT_TableOptions, useMantineReactTable, Xor } from "mantine-react-table";
|
||||||
|
import { useNejReactTable } from "./useNejReactTable";
|
||||||
|
|
||||||
|
type TableInstanceProp<TData extends MRT_RowData> = {
|
||||||
|
table: MRT_TableInstance<TData>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props<TData extends MRT_RowData> = Xor<
|
||||||
|
TableInstanceProp<TData>,
|
||||||
|
MRT_TableOptions<TData>
|
||||||
|
>;
|
||||||
|
|
||||||
|
const isTableInstanceProp = <TData extends MRT_RowData>(
|
||||||
|
props: Props<TData>,
|
||||||
|
): props is TableInstanceProp<TData> =>
|
||||||
|
(props as TableInstanceProp<TData>).table !== undefined;
|
||||||
|
|
||||||
|
export const NejReactTable = <TData extends MRT_RowData>(
|
||||||
|
props: Props<TData>,
|
||||||
|
) => {
|
||||||
|
let table: MRT_TableInstance<TData>;
|
||||||
|
|
||||||
|
if (isTableInstanceProp(props)) {
|
||||||
|
table = props.table;
|
||||||
|
} else {
|
||||||
|
table = useNejReactTable(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <MantineReactTable {...props} />;
|
||||||
|
};
|
||||||
15
Table/useNejReactTable.ts
Normal file
15
Table/useNejReactTable.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { MRT_RowData, MRT_TableInstance, MRT_TableOptions, useMantineReactTable } from "mantine-react-table";
|
||||||
|
|
||||||
|
|
||||||
|
export const useNejReactTable = <TData extends MRT_RowData>(
|
||||||
|
tableOptions: MRT_TableOptions<TData>,
|
||||||
|
): MRT_TableInstance<TData> => {
|
||||||
|
return useMantineReactTable({
|
||||||
|
enablePagination: false,
|
||||||
|
enableFullScreenToggle: false,
|
||||||
|
enableDensityToggle: false,
|
||||||
|
enableBottomToolbar: false,
|
||||||
|
enableTopToolbar: tableOptions.renderTopToolbarCustomActions != null,
|
||||||
|
...tableOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
75
globalStyle.js
Normal file
75
globalStyle.js
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { createGlobalStyle } from "styled-components"
|
||||||
|
import tw, { theme, GlobalStyles as BaseStyles } from "twin.macro"
|
||||||
|
|
||||||
|
const CustomStyles = createGlobalStyle`
|
||||||
|
body {
|
||||||
|
-webkit-tap-highlight-color: ${theme`colors.accent`};
|
||||||
|
}
|
||||||
|
|
||||||
|
:root{
|
||||||
|
--primary: #3d3d3d;
|
||||||
|
--secondary: #535353;
|
||||||
|
--trinary: #2c2c2c;
|
||||||
|
|
||||||
|
--primary-text: #ffffff;
|
||||||
|
--secondary-text: #a0a0a0;
|
||||||
|
|
||||||
|
--primary-invert: #f3f3f3;
|
||||||
|
--secondary-invert: #dbdbdb;
|
||||||
|
--trinary-invert: #ffffff;
|
||||||
|
|
||||||
|
--primary-invert-text: #3d3d3d;
|
||||||
|
--secondary-invert-text: #535353;
|
||||||
|
|
||||||
|
--accent: #00C800;
|
||||||
|
--accent-dark: #008f00;
|
||||||
|
--accent-light: #00ed00;
|
||||||
|
--accent2: #3080FF;
|
||||||
|
--accent2-dark: #225ab4;
|
||||||
|
--accent3: #804000;
|
||||||
|
--accent3-dark: #472400;
|
||||||
|
--accent4: #F8B02C;
|
||||||
|
--accent4-dark: #bd8724;
|
||||||
|
--accent5: #9E3086;
|
||||||
|
--accent5-dark: #6b215b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light\-theme{
|
||||||
|
--primary: #f3f3f3;
|
||||||
|
--secondary: #dbdbdb;
|
||||||
|
--trinary: #ffffff;
|
||||||
|
--primary-text: #3d3d3d;
|
||||||
|
--secondary-text: #535353;
|
||||||
|
|
||||||
|
|
||||||
|
--primary-invert: #3d3d3d;
|
||||||
|
--secondary-invert: #535353;
|
||||||
|
--trinary-invert: #2c2c2c;
|
||||||
|
--primary-text-invert: #ffffff;
|
||||||
|
--secondary-text-invert: #a0a0a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0.5rem;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--secondary);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--accent-dark);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const GlobalStyles = ({dontApplyBaseStyles}) => {
|
||||||
|
return <>
|
||||||
|
{(!dontApplyBaseStyles) &&<BaseStyles />}
|
||||||
|
<CustomStyles />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GlobalStyles
|
||||||
25
styledRegistry.js
Normal file
25
styledRegistry.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useServerInsertedHTML } from 'next/navigation'
|
||||||
|
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
|
||||||
|
|
||||||
|
export default function StyledComponentsRegistry({ children }) {
|
||||||
|
// Only create stylesheet once with lazy initial state
|
||||||
|
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
|
||||||
|
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
|
||||||
|
|
||||||
|
useServerInsertedHTML(() => {
|
||||||
|
const styles = styledComponentsStyleSheet.getStyleElement()
|
||||||
|
styledComponentsStyleSheet.instance.clearTag()
|
||||||
|
return <>{styles}</>
|
||||||
|
})
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') return <>{children}</>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
|
||||||
|
{children}
|
||||||
|
</StyleSheetManager>
|
||||||
|
)
|
||||||
|
}
|
||||||
16
useModal.tsx
Normal file
16
useModal.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
export default function useModal<T>(
|
||||||
|
Component: React.ComponentType<T>,
|
||||||
|
title: string
|
||||||
|
) {
|
||||||
|
const openModal = useCallback((params: T) => {
|
||||||
|
modals.open({
|
||||||
|
title,
|
||||||
|
children: <Component {...params} />,
|
||||||
|
});
|
||||||
|
}, [Component, title]);
|
||||||
|
|
||||||
|
return openModal;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user