import {
batch,
Component,
FlowComponent,
For,
Show,
useContext,
JSX,
startTransition,
createMemo,
onMount,
onCleanup,
} from "solid-js";
import { createStore, reconcile, unwrap } from "solid-js/store";
import { format, fromUnixTime, getUnixTime } from "date-fns";
import z from "myzod";
import Big from "big.js";
import { generate } from "node-iso11649";
import { customAlphabet } from "nanoid";
import { isValidIBAN } from "ibantools";
import createAccordion from "../Accordion";
import {
Checkbox,
NumberInput,
Select,
TextArea,
TextInput,
UnixDateInput,
} from "../Form";
import { autoAnimate } from "~/directives/autoAnimate";
import {
ADDRESS_LAYOUT_LEFT,
ADDRESS_LAYOUT_RIGHT,
CURRENT_VERSION,
LocalStoreContext,
localStoreSchema,
migrateInfoLog,
migrateLocalState,
migrateState,
POSITION_TYPE_AGILE,
POSITION_TYPE_QUANTITY,
PrintType,
printTypeSelectTitles,
printTypeTitles,
PRINT_TYPE_CONFIRMATION,
PRINT_TYPE_INVOICE,
PRINT_TYPE_LETTER,
PRINT_TYPE_OFFER,
StoreContext,
storeSchema,
UiStoreContext,
} from "~/stores";
import { AddressData, isStructuredAddress } from "../Address";
import PositionsIcon from "~icons/carbon/show-data-cards";
import YouIcon from "~icons/carbon/face-wink";
import DesignIcon from "~icons/carbon/paint-brush";
import PrinterIcon from "~icons/carbon/printer";
import ProjectIcon from "~icons/carbon/product";
import DownloadIcon from "~icons/carbon/download";
import LoadIcon from "~icons/carbon/folder";
import LoadingSpinnerIcon from "~icons/icomoon-free/spinner9";
import ErrorIcon from "~icons/carbon/error";
import SuccessIcon from "~icons/carbon/checkmark-filled";
import CustomerIcon from "~icons/carbon/friendship";
import WarningIcon from "~icons/carbon/warning-alt-filled";
import GenerateIcon from "~icons/carbon/chemistry";
import DeleteIcon from "~icons/carbon/trash-can";
import { saveFile, selectLocalFiles, uploadFile } from "~/client/filesystem";
import { resetInput, sleep } from "~/util";
import { PositionsSettings } from "./Positions";
import Modal, { ModalCloseButton } from "../Modal";
import { createValidation } from "~/hooks/validation";
import { MarkdownHelpLabel } from "../Markdown";
const AccordionItemGrid: FlowComponent = (props) => {
return (
{props.children}
);
};
const AccordionItemEnd: Component = () => {
return
;
};
const AccordionItemDivider: FlowComponent = (props) => {
return {props.children}
;
};
const SettingsOverlay: Component = () => {
const [state, setState] = useContext(StoreContext)!;
const [localState, setLocalState] = useContext(LocalStoreContext)!;
const [loadModal, setLoadModal] = createStore({
open: false,
loading: false,
errors: null as null | {
message?: JSX.Element;
parseErrors: { path: string; message: string }[];
},
});
const [uiState, setUiState] = useContext(UiStoreContext)!;
const [AccordionItem] = createAccordion(null);
autoAnimate;
const [DocumentValidationContext, documentDataForm] = createValidation();
const [YourDataValidationContext, yourDataForm] = createValidation();
const [CustomerValidationContext, customerDataForm] = createValidation();
const AddressInputs: Component<{
namePrefix?: string;
nameRequired?: boolean;
setter: (name: string, value: any) => void;
address: () => AddressData;
}> = (props) => {
const isStructured = createMemo(() => isStructuredAddress(props.address()));
const withPrefix = (name: string) =>
createMemo(
() => `${props.namePrefix && props.namePrefix + "_"}${name}`
)();
return (
<>
props.setter("name", evt.currentTarget.value)}
/>
props.setter("line1", evt.currentTarget.value)}
/>
props.setter("line2", evt.currentTarget.value)}
/>
props.setter(
"zip",
parseInt(evt.currentTarget.value) || undefined
)
}
/>
props.setter("city", evt.currentTarget.value)}
/>
>
);
};
const createCustomerAddressSetter = (alternative = false) => {
const addressField = alternative ? "alternativeAddress" : "debtorAddress";
return (name: any, value: any) => {
setState("customer", addressField, name, value);
};
};
const contactSetter = (name: any, value: any) => {
setLocalState("contact", name, value);
};
const fullWidthLabelWidth = "50%";
const FullWidthAccordionInput: Component[0]> = (
props
) => (
);
const saveProject = () => {
const fileContent = JSON.stringify(
{
state: unwrap(state),
localState: unwrap(localState),
},
null,
" "
);
saveFile(
`rappli-${
state.project.projectNumber.length
? state.project.projectNumber.replaceAll(" ", "-") + "-"
: ""
}${format(fromUnixTime(state.project.date), "yyyy-MM-dd")}.json`,
"application/json",
fileContent
);
setUiState("lastSaved", getUnixTime(new Date()));
};
const saveOnCtrlS = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === "s") {
e.preventDefault();
saveProject();
}
};
onMount(function () {
document.addEventListener("keydown", saveOnCtrlS);
});
onCleanup(function () {
document.removeEventListener("keydown", saveOnCtrlS);
});
const AccordionItemLabel: FlowComponent = (props) => (
{props.children}
);
const AccordionContent: FlowComponent = (props) => (
{props.children}
);
let logoInputEl: HTMLInputElement = undefined!;
return (
<>
{
const files = await selectLocalFiles([
".json",
"application/json",
]);
if (!files[0]) {
return;
}
setLoadModal("loading", true);
setLoadModal("errors", null);
await sleep(200);
setLoadModal("open", true);
const load = async () => {
const results = await Promise.all([
uploadFile(files[0]),
sleep(600),
]);
const content = results[0];
if (!content) {
setLoadModal("errors", {
message:
"Das Dokument ist leer! Bitte lade ein korrektes Dokument hoch.",
});
return;
}
let contentObject: any;
try {
contentObject = JSON.parse(content);
} catch (err) {
setLoadModal("errors", {
message: "Das Dokument hat kein gültiges Format.",
});
return;
}
const schema = z
.object({
state: storeSchema,
localState: localStoreSchema,
})
.collectErrors();
try {
let migrated = false;
const localStateVersion =
contentObject?.localState?.version;
const stateVersion = contentObject?.state?.version;
if (localStateVersion !== CURRENT_VERSION) {
migrateLocalState(contentObject?.localState);
migrated = true;
}
if (stateVersion !== CURRENT_VERSION) {
migrateState(contentObject?.state);
migrated = true;
}
if (migrated) {
migrateInfoLog(
localStateVersion < stateVersion
? localStateVersion
: stateVersion,
CURRENT_VERSION
);
}
schema.parse(contentObject);
} catch (e: any) {
const message = "Das Dokument hat kein gültiges Format.";
let parseErrors = [{ path: ".", message: e.message }];
if (e.collectedErrors) {
parseErrors = Object.values(e.collectedErrors).map(
(error: any) => {
return {
path: error.path.join("."),
message: error.message,
};
}
);
}
setLoadModal("errors", {
message,
parseErrors,
});
return;
}
await startTransition(function () {
batch(() => {
setState(reconcile(contentObject.state));
setLocalState(reconcile(contentObject.localState));
});
});
};
const results = await Promise.all([sleep(1200), load()]);
setLoadModal("loading", false);
if (!loadModal.errors) {
setUiState("lastSaved", getUnixTime(new Date()));
await sleep(1100);
setLoadModal("open", false);
}
}}
>
Laden
Speichern
{
setLoadModal("open", false);
}}
/>
Dokument konnte nicht geladen werden
{loadModal.errors?.message}
JSON-Pfad
Fehler
{(error, idx) => (
{idx() + 1}
{error.path}
{error.message}
)}
{
setLoadModal("open", false);
setLoadModal("errors", null);
}}
>
Schliessen
>
);
};
export default SettingsOverlay;