import { createContext } from "solid-js"; import { createStore as createStore_ } from "solid-js/store"; import { addressSchema, createAddress } from "./components/Address"; import { createLocalStore as createLocalStore_ } from "./localStore"; import { getUnixTime } from "date-fns"; import z, { Infer } from "myzod"; export const POSITION_TYPE_QUANTITY = "QUANTITY"; export const POSITION_TYPE_AGILE = "AGILE"; export const positionSchema = z.object({ id: z.number(), name: z.string(), enabled: z.boolean(), type: z.literals(POSITION_TYPE_QUANTITY, POSITION_TYPE_AGILE), agilePointsMin: z.number().optional(), agilePointsMax: z.number().optional(), agileRiskFactor: z.number().optional(), number: z.string().optional(), description: z.string().optional(), quantity: z.number(), fixedDiscountPrice: z.number().optional(), itemPrice: z.number().optional(), }); export type Position = Infer; export const PRINT_TYPE_OFFER = "OFFER"; export const PRINT_TYPE_CONFIRMATION = "CONFIRMATION"; export const PRINT_TYPE_INVOICE = "INVOICE"; export type PrintType = | typeof PRINT_TYPE_OFFER | typeof PRINT_TYPE_CONFIRMATION | typeof PRINT_TYPE_INVOICE; export const printTypeTitles = { [PRINT_TYPE_OFFER]: "Offerte", [PRINT_TYPE_CONFIRMATION]: "Bestätigung", [PRINT_TYPE_INVOICE]: "Rechnung", }; export const createUiStore = () => createStore_({ lastSaved: 0, printType: PRINT_TYPE_INVOICE as PrintType, selectedPosition: undefined as undefined | number, }); export type UiStore = ReturnType; export const UiStoreContext = createContext(); export const ADDRESS_LAYOUT_LEFT = "LEFT"; export const ADDRESS_LAYOUT_RIGHT = "RIGHT"; export type AddressLayout = | typeof ADDRESS_LAYOUT_LEFT | typeof ADDRESS_LAYOUT_RIGHT; export const storeSchema = z.object({ version: z.number(), project: z.object({ orderNumber: z.string(), projectNumber: z.string(), deliveryNumber: z.string(), deliveryDate: z.number().optional(), date: z.number(), preface: z.string().optional(), conclusion: z.string().optional(), }), invoice: z.object({ reference: z.string(), message: z.string(), }), fullWidthInvoice: z.boolean(), useCustomerAlternativeAddress: z.boolean(), customer: z.object({ customerNumber: z.string(), vatNumber: z.string(), alternativeAddress: addressSchema, debtorAddress: addressSchema, }), defaultItemPrice: z.number(), agileRiskFactor: z.number(), agileHoursPerStoryPoint: z.number(), defaultPositionType: z.literals(POSITION_TYPE_QUANTITY, POSITION_TYPE_AGILE), positions: z.array(positionSchema), }); export type StoreObject = Infer; export const CURRENT_VERSION = 1.3; export const migrateState = (state: Record) => { state.version = CURRENT_VERSION; }; export const createStore = () => createStore_({ version: CURRENT_VERSION, project: { orderNumber: "", projectNumber: "", deliveryNumber: "", date: getUnixTime(new Date()), }, invoice: { reference: "", message: "", }, fullWidthInvoice: true, useCustomerAlternativeAddress: false, customer: { customerNumber: "", vatNumber: "", alternativeAddress: createAddress(), debtorAddress: createAddress(), }, defaultPositionType: POSITION_TYPE_QUANTITY, defaultItemPrice: 0, agileRiskFactor: 0.7, agileHoursPerStoryPoint: 4, positions: [] as Position[], }); export type Store = ReturnType; export const StoreContext = createContext(); export const localStoreSchema = z.object({ // TODO: Move this to a cookie (advantage: ssr) showWelcome: z.boolean(), version: z.number(), vatNumber: z.string(), vatRate: z.number(), paymentTerms: z.string().optional(), iban: z.string(), creditor: addressSchema, addressLayout: z.literals(ADDRESS_LAYOUT_LEFT, ADDRESS_LAYOUT_RIGHT), customAddress: addressSchema, contact: z.object({ name: z.string(), phone: z.string(), email: z.string(), }), showLufraiWatermark: z.boolean(), // TODO: Signature Image logo: z .object({ type: z.string(), url: z.string(), width: z.number(), height: z.number(), }) .optional(), useCustomAddress: z.boolean(), }); export type LocalStoreObject = Infer; export const migrateLocalState = (state: Record) => { if (state.version === 1) { state.addressLayout = ADDRESS_LAYOUT_LEFT; state.version = 1.3; } state.version = CURRENT_VERSION; }; export const migrateInfoLog = (oldVersion: number, newVersion: number) => console.info( `Migrated document schema from version ${oldVersion} to ${newVersion}` ); export const createLocalStore = () => createLocalStore_( { version: CURRENT_VERSION, showWelcome: true, vatNumber: "", vatRate: 0.0, paymentTerms: undefined, creditor: createAddress(), addressLayout: ADDRESS_LAYOUT_RIGHT, customAddress: createAddress(), contact: { name: "", phone: "", email: "", }, logo: undefined, showLufraiWatermark: true, useCustomAddress: false, iban: "", }, { prefix: "invoice-app", migrate: (getState) => { const version = JSON.parse( localStorage.getItem("invoice-app-version") || "-1" ); if (version == -1) return false; if (version !== CURRENT_VERSION) { migrateLocalState(getState()); migrateInfoLog(version, CURRENT_VERSION); return true; } return false; }, } ); export type LocalStore = ReturnType; export const LocalStoreContext = createContext();