diff --git a/src/App.tsx b/src/App.tsx index 5cd6b2a..37934b2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,11 @@ const firebaseProvider = new FirebaseAuthProvider({ }); const dataSources = { - settings: new LocalStorageDataSource({ target: 'settings', targetMode: 'document' }), + settings: new LocalStorageDataSource({ + target: 'settings', + targetMode: 'document', + subscribe: true, + }), }; function App() { diff --git a/src/components/default/data-table.tsx b/src/components/default/data-table.tsx new file mode 100644 index 0000000..6f710dd --- /dev/null +++ b/src/components/default/data-table.tsx @@ -0,0 +1,88 @@ +import { + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TableSortLabel, +} from '@mui/material'; +import React, { useState } from 'react'; + +interface DataTableProps { + columns: { + id: string; + accessor: string; + label: string; + render?: (value: any, row: any, rowIndex: number) => any; + }[]; + data: any[]; +} + +const DataTable = ({ columns, data }: DataTableProps) => { + // State to manage sorting + const [order, setOrder] = useState<'asc' | 'desc' | undefined>('asc'); // 'asc' or 'desc' + const [orderBy, setOrderBy] = useState(null); // column to sort by + + // Helper function to handle sorting logic + const handleRequestSort = (property: string) => { + const isAsc = orderBy === property && order === 'asc'; + setOrder(isAsc ? 'desc' : 'asc'); + setOrderBy(property); + }; + + // Function to sort data based on the current sorting state + const sortedData = React.useMemo(() => { + if (orderBy === null) return data; // No sorting + + return [...data].sort((a, b) => { + const aValue = a[orderBy]; + const bValue = b[orderBy]; + if (aValue < bValue) { + return order === 'asc' ? -1 : 1; + } + if (aValue > bValue) { + return order === 'asc' ? 1 : -1; + } + return 0; + }); + }, [data, orderBy, order]); + + return ( + + + + + {columns.map((column) => ( + + handleRequestSort(column.accessor)} + > + {column.label} + + + ))} + + + + {sortedData.map((row, rowIndex) => ( + + {columns.map((column) => ( + + {column.render + ? column.render(row[column.accessor], row, rowIndex) + : row[column.accessor]} + + ))} + + ))} + +
+
+ ); +}; + +export default DataTable; diff --git a/src/components/default/images/image-cropper.tsx b/src/components/default/images/image-cropper.tsx index 5e6fedd..8a5ce58 100644 --- a/src/components/default/images/image-cropper.tsx +++ b/src/components/default/images/image-cropper.tsx @@ -1,5 +1,3 @@ -// @ts-nocheck - import useIsMobile from '@/hooks/use-is-mobile'; import CloseIcon from '@mui/icons-material/Close'; import { @@ -30,7 +28,7 @@ async function convertImageToDataURL(url: string): Promise { const blob = await response.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onload = () => resolve(reader.result); + reader.onload = () => resolve(reader.result as string); reader.onerror = reject; reader.readAsDataURL(blob); }); @@ -69,7 +67,7 @@ const ImageCropper = ({ imageUrl, filename, onSave, dialog, cropperProps }: Imag return path.split('/').pop() || 'unknown-filename.jpg'; }; - const onCropComplete = useCallback((_, croppedAreaPixels: any) => { + const onCropComplete = useCallback((_: any, croppedAreaPixels: any) => { setCroppedAreaPixels(croppedAreaPixels); }, []); @@ -77,7 +75,7 @@ const ImageCropper = ({ imageUrl, filename, onSave, dialog, cropperProps }: Imag if (imageUrl && croppedAreaPixels) { const croppedImageBlob = await getCroppedImg(imageUrl, croppedAreaPixels); - const croppedFile = new File([croppedImageBlob], getFileName(filename), { + const croppedFile = new File([croppedImageBlob as Blob], getFileName(filename), { type: 'image/jpeg', }); const croppedFileUrl = await onSave(croppedFile, filename); diff --git a/src/components/default/images/image-uploader.tsx b/src/components/default/images/image-uploader.tsx index a65da4d..9fff2f0 100644 --- a/src/components/default/images/image-uploader.tsx +++ b/src/components/default/images/image-uploader.tsx @@ -1,5 +1,3 @@ -// @ts-nocheck - import ImageCropper from '@/components/default/images/image-cropper'; import useDialog from '@/hooks/use-dialog'; import { Button } from '@mui/material'; @@ -73,14 +71,14 @@ const ImageUploader = ({ thumbnail, multiple = false, }: ImageUploaderProps) => { - const [imageSrc, setImageSrc] = useState(null); + const [imageSrc, setImageSrc] = useState(null); const dialog = useDialog(); const getFileName = (path: string): string => { return path.split('/').pop() || 'unknown-filename.jpg'; }; - const processFile = async (file) => { + const processFile = async (file: File) => { if (file) { if (file.size > max_size) { console.log( @@ -105,7 +103,7 @@ const ImageUploader = ({ if (thumbnail?.path) { const thumbnailBlob = await createThumbnail(file); - const thumbnailFile = new File([thumbnailBlob], getFileName(thumbnail.path), { + const thumbnailFile = new File([thumbnailBlob as Blob], getFileName(thumbnail.path), { type: 'image/jpeg', }); const uploadFunction = thumbnail.uploadFile || uploadFile; @@ -175,8 +173,8 @@ const ImageUploader = ({ setImageSrc(null) }} cropperProps={cropObject?.props} diff --git a/src/libs/data-sources/DataProvider.tsx b/src/libs/data-sources/DataProvider.tsx index 6080420..70b97e6 100644 --- a/src/libs/data-sources/DataProvider.tsx +++ b/src/libs/data-sources/DataProvider.tsx @@ -1,25 +1,26 @@ import React, { createContext, ReactNode, useCallback, useEffect, useState } from 'react'; -import { DataSource, DataSourceObject } from '.'; - -// interface DataProviderContextProps { -// dataSources: DataSourceObject; -// setDataSource: (key: string, dataSource: DataSource) => void; -// addDataSource: (key: string, dataSource: DataSource) => void; -// data: Record; -// loading: Record; -// error: Record; -// fetchData: (key: string, filter?: object) => Promise; -// subscribeToData: (key: string) => void; -// subscriptions: Record void>; -// add: (key: string, item: any) => Promise; -// update: (key: string, data: any, id: string) => Promise; -// set: (key: string, data: any, id: string) => Promise; -// remove: (key: string, id: string) => Promise; -// } // TODO: implement? +import { DataSourceObject, FilterObject } from '.'; +import BaseDataSource from './data-sources/BaseDataSource'; + +interface DataProviderContextProps { + dataSources: DataSourceObject; + setDataSource: (key: string, dataSource: BaseDataSource) => void; + addDataSource: (key: string, dataSource: BaseDataSource) => void; + data: Record; + loading: Record; + error: Record; + fetchData: (key: string, filter?: object) => Promise; + subscribeToData: (key: string) => void; + subscriptions: Record void>; + add: (key: string, item: any) => Promise; + update: (key: string, data: any, id?: string) => Promise; + set: (key: string, data: any, id?: string) => Promise; + remove: (key: string, id?: string) => Promise; +} // TODO: implement? // Create a context for the data // eslint-disable-next-line react-refresh/only-export-components -export const DataContext = createContext(undefined); +export const DataContext = createContext(undefined); interface DataProviderProps { dataSources: DataSourceObject; @@ -34,14 +35,14 @@ const DataProvider: React.FC = ({ dataSources, children }) => const [dataSourcesState, setDataSourcesState] = useState(dataSources); const addDataSource = useCallback( - (key: string, newDataSource: DataSource) => { + (key: string, newDataSource: BaseDataSource) => { setDataSourcesState((prev) => ({ ...prev, [key]: newDataSource })); }, [setDataSourcesState] ); - const setDataSource = useCallback((key: string, dataSource: DataSource) => { - setDataSourcesState((prev) => ({ ...prev, [key]: { ...prev[key], dataSource } })); + const setDataSource = useCallback((key: string, dataSource: BaseDataSource) => { + setDataSourcesState((prev) => ({ ...prev, [key]: dataSource })); }, []); const getDataSource = useCallback( @@ -54,7 +55,7 @@ const DataProvider: React.FC = ({ dataSources, children }) => ); const fetchData = useCallback( - async (key: string, filter?: object) => { + async (key: string, _filter?: FilterObject) => { if (subscriptions[key]) return; // Skip if there is an active subscription setLoading((prev) => ({ ...prev, [key]: true })); setError((prev) => ({ ...prev, [key]: null })); @@ -62,7 +63,7 @@ const DataProvider: React.FC = ({ dataSources, children }) => const dataSource = getDataSource(key); const result = dataSource.options?.targetMode === 'collection' - ? await dataSource.getAll(filter) + ? await dataSource.getAll() // TODO: add filter : await dataSource.get(); setData((prev) => ({ ...prev, [key]: result })); } catch (err) { @@ -108,7 +109,7 @@ const DataProvider: React.FC = ({ dataSources, children }) => ); const update = useCallback( - async (key: string, data: any, id: string) => { + async (key: string, data: any, id?: string) => { const dataSource = getDataSource(key); const newData = await dataSource.update(data, id); if (!subscriptions[key] && data[key]) { @@ -124,7 +125,7 @@ const DataProvider: React.FC = ({ dataSources, children }) => ); const set = useCallback( - async (key: string, data: any, id: string) => { + async (key: string, data: any, id?: string) => { const dataSource = getDataSource(key); await dataSource.set(data, id); if (!subscriptions[key] && data[key]) { @@ -138,7 +139,7 @@ const DataProvider: React.FC = ({ dataSources, children }) => ); const remove = useCallback( - async (key: string, id: string) => { + async (key: string, id?: string) => { const dataSource = getDataSource(key); await dataSource.delete(id); if (!subscriptions[key] && data[key]) { diff --git a/src/libs/data-sources/data-sources/BaseDataSource.tsx b/src/libs/data-sources/data-sources/BaseDataSource.tsx index 5a2b9fc..0ee968a 100644 --- a/src/libs/data-sources/data-sources/BaseDataSource.tsx +++ b/src/libs/data-sources/data-sources/BaseDataSource.tsx @@ -6,115 +6,63 @@ interface validateOptions extends yup.ValidateOptions { full?: boolean; } -interface ValidationResult { +export interface ValidationResult { valid: boolean; errors?: Record; values: any; } -/** - * BaseDataSource class provides methods to interact with data sources and generate dummy data based on Yup schemas. - * @template T - The type of the data source. - */ -class BaseDataSource { - protected defaultOptions = { +class BaseDataSource { + protected defaultOptions: Partial> = { targetMode: 'collection' as const, subscribe: false, + converter: { + toDatabase: (data: T): any => data, + fromDatabase: (data: any): T => data, + }, + idField: 'id' as keyof T, + }; + options: DataSourceInitOptions; + private converter = { + toDatabase: (data: T): any => data, + fromDatabase: (data: any): T => data, }; - options: DataSourceInitOptions; providerConfig: any; provider: string; + data?: Z | T[] | T; + subscribers: Array<(data: T | null) => void> = []; - /** - * Constructs a new instance of BaseDataSource. - * @param {DataSourceInitOptions} options - Initialization options for the data source. - * @param {any} providerConfig - Configuration for the data provider. - */ - constructor(options: DataSourceInitOptions, providerConfig: any) { - if (new.target === BaseDataSource) { - throw new TypeError('Cannot construct BaseDataSource instances directly'); - } + constructor(options: DataSourceInitOptions, providerConfig?: any) { this.provider = 'Base'; this.providerConfig = providerConfig; this.options = { ...this.defaultOptions, ...options, }; + this.converter = this.options.converter || this.converter!; + if (new.target === BaseDataSource && !this.options?.data) { + throw new TypeError('When constructing BaseDataSource directly, data is required'); + } + if (this.options.data) { + this.data = this.options.data; + } + this.getAll = this.getAll.bind(this); + this.get = this.get.bind(this); + this.add = this.add.bind(this); + this.update = this.update.bind(this); + this.set = this.set.bind(this); + this.delete = this.delete.bind(this); + this.validate = this.validate.bind(this); + this.subscribe = this.subscribe.bind(this); + this.notifySubscribers = this.notifySubscribers.bind(this); + this.#validateYupSchema = this.#validateYupSchema.bind(this); } - // /** - // * Helper function to validate email format. - // * @param {string} email - The email to validate. - // * @returns {boolean} - True if the email is valid, false otherwise. - // */ - // #isValidEmail = (email: string): boolean => { - // const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - // return emailRegex.test(email); - // }; - - // /** - // * Helper function to validate date format. - // * @param {string} date - The date to validate. - // * @returns {boolean} - True if the date is valid, false otherwise. - // */ - // #isValidDate = (date: string): boolean => { - // return !isNaN(new Date(date).getTime()); - // }; - - /** - // * Validates the data against the schema. - // * @param {Partial} data - The data to validate. - // * @param {validateOptions} [_options] - Additional validation options. - // * @throws Will throw an error if validation fails. - // */ - // #validateSchema = async (data: Partial, _options?: validateOptions): Promise => { - // if (this.options.schema) { - // const errors = []; - // for (const [key, rules] of Object.entries(this.options.schema)) { - // const value = (data as { [key: string]: any })[key]; - - // // Check required fields - // if (rules.required && (value === undefined || value === null)) { - // errors.push(`${key} is required.`); - // continue; - // } - - // // Check data type - // if (rules.type) { - // switch (rules.type) { - // case 'string': - // if (typeof value !== 'string') errors.push(`${key} must be a string.`); - // break; - // case 'number': - // if (typeof value !== 'number') errors.push(`${key} must be a number.`); - // break; - // case 'boolean': - // if (typeof value !== 'boolean') errors.push(`${key} must be a boolean.`); - // break; - // case 'date': - // if (!this.#isValidDate(value)) errors.push(`${key} must be a valid date.`); - // break; - // case 'email': - // if (!this.#isValidEmail(value)) errors.push(`${key} must be a valid email.`); - // break; - // default: - // break; - // } - // } - // } - - // if (errors.length > 0) { - // throw new Error(errors.join(' ')); - // } - // } - // }; + generateNanoId = () => { + return faker.string.nanoid(); + }; - /** - * Validates the data against the Yup schema. - * @param {Partial} data - The data to validate. - * @param {validateOptions} [_options] - Additional validation options. - * @throws Will throw an error if validation fails. - */ + // TODO: implement method overloading, T or partial T #validateYupSchema = async ( data: Partial, options?: validateOptions @@ -161,40 +109,25 @@ class BaseDataSource { return returnObject; }; - /** - * Validates the data using both custom and Yup schemas. - * @param {Partial} data - The data to validate. - * @param {validateOptions} [options] - Additional validation options. - * @throws Will throw an error if validation fails. - */ validate = async (data: Partial, options?: validateOptions): Promise => { //await this.#validateSchema(data, options); return await this.#validateYupSchema(data, options); }; - /** - * Cleans the data by removing undefined values. - * @param {Partial} data - The data to clean. - * @returns {Partial} - The cleaned data. - */ - // TODO: finish implementation - cleanValues = (data: Partial): Partial => { - const isObject = ['collection', 'document'].includes(this.options.targetMode || ''); - const cleanedData: any = isObject ? {} : data; - if (isObject) { - for (const [key, value] of Object.entries(data)) { - if (value !== undefined || !this.options.cleanValues?.removeUndefined) { - cleanedData[key] = value; - } - } - } - return cleanedData; - }; + // // TODO: finish implementation OR go to converters + // cleanValues = (data: Partial): Partial => { + // const isObject = ['collection', 'document'].includes(this.options.targetMode || ''); + // const cleanedData: any = isObject ? {} : data; + // if (isObject) { + // for (const [key, value] of Object.entries(data)) { + // if (value !== undefined || !this.options.cleanValues?.removeUndefined) { + // cleanedData[key] = value; + // } + // } + // } + // return cleanedData; + // }; - /** - * Gets the default value based on the target mode. - * @returns {any} - The default value. - */ protected _getDefaultValue = (): any => { let fallback; @@ -221,89 +154,6 @@ class BaseDataSource { return this.options.defaultValue || fallback; }; - /** - * Generates test data based on a Yup schema. - * @template T - The type of the object schema. - * @param {YupSchema} schema - The Yup schema to generate data for. - * @returns {T} - The generated test data. - */ - #generateDummyData = (schema: any): T => { - //const schema = this.options.YupValidationSchema as YupSchema; - const shape = schema.fields; - const data: Partial = {}; - - Object.keys(shape).forEach((key) => { - const field = shape[key]; - if (this.options.mockOptions?.schema?.[key]) { - data[key as keyof T] = this.options.mockOptions?.schema?.[key](); - } else if (field instanceof yup.StringSchema) { - const minLength = (field.spec as any).min ?? 5; - const maxLength = (field.spec as any).max ?? 20; - data[key as keyof T] = faker.lorem.words( - faker.number.int({ min: minLength, max: maxLength }) - ) as any; - } else if (field instanceof yup.NumberSchema) { - const min = (field.spec as any).min ?? 0; - const max = (field.spec as any).max ?? 100; - data[key as keyof T] = faker.number.int({ min, max }) as any; - } else if (field instanceof yup.BooleanSchema) { - data[key as keyof T] = faker.datatype.boolean() as any; - } else if (field instanceof yup.DateSchema) { - const minDate = (field.spec as any).min - ? new Date((field.spec as any).min) - : faker.date.past(); - const maxDate = (field.spec as any).max - ? new Date((field.spec as any).max) - : faker.date.future(); - data[key as keyof T] = faker.date.between({ from: minDate, to: maxDate }) as any; - } else if (field instanceof yup.ArraySchema) { - const itemType = (field as yup.ArraySchema).innerType; - const minItems = (field.spec as any).min ?? 1; - const maxItems = (field.spec as any).max ?? 5; - const length = faker.number.int({ min: minItems, max: maxItems }); - - if (itemType instanceof yup.StringSchema) { - data[key as keyof T] = Array.from({ length }, () => faker.lorem.word()) as any; - } else if (itemType instanceof yup.NumberSchema) { - data[key as keyof T] = Array.from({ length }, () => faker.number.int()) as any; - } - } else if (field instanceof yup.ObjectSchema) { - data[key as keyof T] = this.#generateDummyData(field) as any; - } - }); - - return data as T; - }; - - /** - * Generates dummy data based on the Yup schema. - * @param {number} [count=1] - The number of dummy data objects to generate. - * @returns {T | T[]} - The generated dummy data. - * @throws Will throw an error if YupValidationSchema is not provided or count is less than 1. - */ - getDummyData = (count: number = 1): T | T[] => { - if (!this.options.YupValidationSchema && !this.options.mockOptions?.schema) { - throw new Error( - 'YupValidationSchema OR mockOptionsSchema is required to generate dummy data' - ); - } - if (count === 1) { - return this.#generateDummyData(this.options.YupValidationSchema) as T; - } else if (count > 1) { - return Array.from({ length: count }, () => { - return this.#generateDummyData(this.options.YupValidationSchema) as T; - }); - } else { - throw new Error('Count must be greater than 0'); - } - }; - - /** - * Applies post-filters to the data. - * @param {T[]} data - The data to filter. - * @param {FilterObject} filterConfig - The filter configuration. - * @returns {Partial[]} - The filtered data. - */ _applyPostFilters = (data: T[], filterConfig: FilterObject): Partial[] => { let result = data; const { limit, orderBy, pagination, filters, select } = filterConfig; @@ -350,73 +200,157 @@ class BaseDataSource { } return result; - }; + }; // TODO: apply source filters vs apply class filters - /** - * Parses filters for the data source. - * @throws Will throw an error if the method is not implemented. - */ protected _parseFilters = (): void => { throw new Error("Method '_parseFilters' is not implemented."); }; - /** - * Retrieves a single data item. - * @returns {Promise} - The retrieved data item. - * @throws Will throw an error if the method is not implemented. - */ - async get(): Promise { - throw new Error("Method 'get' must be implemented."); + async get(id?: string): Promise { + if (!this.data && this.provider === 'Base') { + throw new Error('No data found'); + } else if (this.options.targetMode === 'collection' && !id) { + throw new Error('get() requires an ID when using collections'); + } else if (this.provider === 'Base') { + if (this.options.targetMode === 'document') { + return this.data as T; + } else { + return (this.data as T[]).find((item: any) => item[this.options.idField] === id) || null; + } + } + return null; } - /** - * Retrieves all data items. - * @returns {Promise} - The retrieved data items. - * @throws Will throw an error if the method is not implemented. - */ - async getAll(): Promise { - throw new Error("Method 'getAll' must be implemented."); + async getAll(_filter?: FilterObject): Promise { + if (!this.data && this.provider === 'Base') { + throw new Error('No data found'); + } else if (this.options.targetMode === 'document') { + throw new Error('getAll() can only be used with collections'); + } else if (this.provider === 'Base') { + return this.data as T[]; + } + return []; } - /** - * Adds a new data item. - * @param {T} _item - The data item to add. - * @returns {Promise} - The added data item. - * @throws Will throw an error if the method is not implemented. - */ - async add(_item: T): Promise { - throw new Error("Method 'add' must be implemented."); + async add(item: T): Promise { + if (!this.data && this.provider === 'Base') { + throw new Error('No data found'); + } else if (this.options.targetMode === 'document') { + throw new Error('add() can only be used with collections'); + } + // Validate new data + const validateResult = await this.validate(item); + if (!validateResult.valid) { + throw new Error('Validation failed'); + } + // Add data to baseProvider + if (this.provider === 'Base') { + const data = this.converter.toDatabase(item); + if (!data[this.options.idField]) { + data[this.options.idField] = this.generateNanoId(); + } + (this.data as T[]).push(data); + this.notifySubscribers(this.data as Z); + } + } + + async update(data: Partial, id?: string): Promise { + if (!this.data && this.provider === 'Base') { + throw new Error('No data found'); + } else if (this.options.targetMode === 'collection' && !id) { + throw new Error('id must be provided when using collections'); + } + // Validate new data + const validateResult = await this.validate(data); + if (!validateResult.valid) { + throw new Error('Validation failed'); + } + // Update data to baseprovider + if (this.provider === 'Base') { + const saveData = this.converter.toDatabase(data as T); + if (this.options.targetMode === 'document') { + this.data = { + ...this.data, + ...saveData, + }; + } else { + if (Array.isArray(this.data)) { + const itemIndex = this.data.findIndex((item: any) => item[this.options.idField] === id); + if (itemIndex > -1) { + this.data[itemIndex] = { + ...this.data[itemIndex], + ...saveData, + }; + } + } + } + this.notifySubscribers(this.data as Z); + } } - /** - * Updates an existing data item. - * @param {Partial} _data - The data to update. - * @param {string} [_id] - The ID of the data item to update. - * @returns {Promise} - * @throws Will throw an error if the method is not implemented. - */ - async update(_data: Partial, _id?: string): Promise { - throw new Error("Method 'update' must be implemented."); + async set(data: T, id?: string): Promise { + if (!this.data && this.provider === 'Base') { + throw new Error('No data found'); + } else if (this.options.targetMode === 'collection' && !id) { + throw new Error('id must be provided when using collections'); + } + // Validate new data + const validateResult = await this.validate(data); + if (!validateResult.valid) { + throw new Error('Validation failed'); + } + + if (this.provider === 'Base') { + const saveData = this.converter.toDatabase(data as T); + if (this.options.targetMode === 'document') { + this.data = saveData; + } else { + if (Array.isArray(this.data)) { + const itemIndex = this.data.findIndex((item: any) => item[this.options.idField] === id); + if (itemIndex > -1) { + this.data[itemIndex] = saveData; + } + } + } + this.notifySubscribers(this.data as Z); + } } - /** - * Sets a data item. - * @param {T} _data - The data to set. - * @param {string} [_id] - The ID of the data item to set. - * @returns {Promise} - * @throws Will throw an error if the method is not implemented. - */ - async set(_data: T, _id?: string): Promise { - throw new Error("Method 'set' must be implemented."); + subscribe(callback: (data: T | null) => void): () => void { + if (!this.data && this.provider === 'Base') { + throw new Error('No data found'); + } + + this.subscribers.push(callback); + + // Call the callback immediately with the current data + callback(this.data as T | null); + + // Return an unsubscribe function + return () => { + this.subscribers = this.subscribers.filter((sub) => sub !== callback); + }; } - /** - * Deletes a data item. - * @returns {Promise} - * @throws Will throw an error if the method is not implemented. - */ - async delete(): Promise { - throw new Error("Method 'delete' must be implemented."); + private notifySubscribers = (data: Z) => { + this.subscribers.forEach((callback) => callback(data as T | null)); + }; + + async delete(id?: string): Promise { + if (!this.data && this.provider === 'Base') { + throw new Error('No data found'); + } else if (this.options.targetMode === 'collection' && !id) { + throw new Error('id must be provided when using collections'); + } else if (this.provider === 'Base') { + if (this.options.targetMode === 'document') { + this.data = undefined; + } else { + if (Array.isArray(this.data)) { + this.data = (this.data as T[]).filter((item: any) => item[this.options.idField] !== id); + } + } + this.notifySubscribers(this.data as Z); + } } } diff --git a/src/libs/data-sources/data-sources/FirestoreDataSource.tsx b/src/libs/data-sources/data-sources/FirestoreDataSource.tsx index bcd99ea..fcfbaed 100644 --- a/src/libs/data-sources/data-sources/FirestoreDataSource.tsx +++ b/src/libs/data-sources/data-sources/FirestoreDataSource.tsx @@ -32,7 +32,7 @@ interface FirestoreDataSourceProviderConfig { // TODO: getDocFromCache implement -export class FirestoreDataSource extends BaseDataSource { +export class FirestoreDataSource extends BaseDataSource { public firestore: Firestore; public ref: any; private defaultConverter: { @@ -42,30 +42,24 @@ export class FirestoreDataSource extends BaseDataSource { fromFirestore: (snapshot: QueryDocumentSnapshot) => ({ id: snapshot.id, ...snapshot.data() }), toFirestore: (data: T) => data, }; - public converter = this.defaultConverter; + //public converter = this.defaultConverter; constructor( - options: DataSourceInitOptions, + options: DataSourceInitOptions, providerConfig: FirestoreDataSourceProviderConfig ) { super(options, providerConfig); this.provider = 'Firestore'; this.firestore = providerConfig.db; - if (providerConfig.converter) { - this.converter = providerConfig.converter; - } + const converter = providerConfig.converter || this.defaultConverter; if (this.options.targetMode === 'collection') { - this.ref = collection(this.firestore, this.options.target).withConverter(this.converter); + this.ref = collection(this.firestore, this.options.target).withConverter(converter); } else if (this.options.targetMode === 'document') { - this.ref = doc(this.firestore, this.options.target).withConverter(this.converter); + this.ref = doc(this.firestore, this.options.target).withConverter(converter); } } - // #getDoc = () => { - // return this.ref; - // } - // Get document reference #getRef = (id?: string) => { //TODO: implement this @@ -76,48 +70,6 @@ export class FirestoreDataSource extends BaseDataSource { return docRef; }; - public testNewMethod = () => { - return true; - }; - - #clearUndefinedValues = (values: T | Partial): T => { - if ( - typeof values !== 'object' || - values === null || - !this.providerConfig.clearUndefinedValues - ) { - return values as T; - } - - const result = Object.keys(values as object) - .filter((k) => k !== undefined && values[k as keyof T] !== undefined) - .reduce((acc: Partial, key: string) => { - const value = values[key as keyof T]; - if (Array.isArray(value)) { - acc[key as keyof T] = value.filter((item: any) => item !== undefined) as any; - } else if (typeof value === 'object' && value !== null) { - acc[key as keyof T] = this.#clearUndefinedValues(value as T) as any; - } else { - acc[key as keyof T] = value as T[keyof T]; - } - return acc; - }, {} as Partial); - - return result as T; - }; - - // Add created at and updated at dates // TODO: implement - // #addDates = (data: T | Partial) => { - // const now = new Date(); - // if (this.providerConfig.createdAt) { - // data.createdAt = now; - // } - // if (this.providerConfig.updatedAt) { - // data.updatedAt = now; - // } - // return data; - // }; - // Parses filter and returns an object for provider specific filterand and the generic js filtering #parseFilters = (filterObject: FilterObject): FilterReturn => { let q = this.ref; @@ -158,9 +110,10 @@ export class FirestoreDataSource extends BaseDataSource { // Get a single document by ID get = async (id?: string): Promise => { try { - if (!id && this.options.targetMode !== 'document') { - throw new Error('get() requires an ID when using collections'); - } + await super.get(id); + // if (!id && this.options.targetMode !== 'document') { + // throw new Error('get() requires an ID when using collections'); + // } const docRef = this.#getRef(id); const docSnap = await getDoc(docRef); if (docSnap.exists()) { @@ -178,8 +131,9 @@ export class FirestoreDataSource extends BaseDataSource { // Get all documents in the collection, with optional filters getAll = async (filter?: FilterObject): Promise => { try { - if (this.options.targetMode !== 'collection') - throw new Error('getAll() can only be used with collections'); + await super.getAll(filter); + // if (this.options.targetMode !== 'collection') + // throw new Error('getAll() can only be used with collections'); // Parse filter object const filterObject = filter || this.options.targetFilter || {}; @@ -202,11 +156,11 @@ export class FirestoreDataSource extends BaseDataSource { // Add a new document to the collection add = async (item: T): Promise => { try { - if (this.options.targetMode !== 'collection') - throw new Error('add() can only be used with collections'); - // Validate new data - item = this.#clearUndefinedValues(item); - this.validate(item); + await super.add(item); + // if (this.options.targetMode !== 'collection') + // throw new Error('add() can only be used with collections'); + // // Validate new data + // this.validate(item); const docRef = await addDoc(this.ref, item); const newDoc = await getDoc(docRef); return { id: docRef.id, ...newDoc.data() } as T; @@ -219,16 +173,15 @@ export class FirestoreDataSource extends BaseDataSource { // Update an existing document by ID update = async (data: Partial, id?: string): Promise => { try { - if (!id && this.options.targetMode !== 'document') { - throw new Error('update() requires an ID when using collections'); - } + await super.update(data, id); + // if (!id && this.options.targetMode !== 'document') { + // throw new Error('update() requires an ID when using collections'); + // } const docRef = this.#getRef(id); - data = this.#clearUndefinedValues(data); - const validateResult = await this.validate(data, { strict: false }); - if (!validateResult.valid) { - throw new Error('Validation failed'); - } - console.log(docRef, data, id); + // const validateResult = await this.validate(data, { strict: false }); + // if (!validateResult.valid) { + // throw new Error('Validation failed'); + // } await updateDoc(docRef, data as UpdateData>); } catch (error) { console.error('Error updating document:', error); @@ -239,12 +192,12 @@ export class FirestoreDataSource extends BaseDataSource { // Update an existing document by ID set = async (data: T, id?: string): Promise => { try { + await super.set(data, id); // Validate updated data - if (!id && this.options.targetMode !== 'document') { - throw new Error('set() requires an ID when using collections'); - } - data = this.#clearUndefinedValues(data); - this.validate(data); // TODO: fix validation everywhere + // if (!id && this.options.targetMode !== 'document') { + // throw new Error('set() requires an ID when using collections'); + // } + // this.validate(data); // TODO: fix validation everywhere const docRef = this.#getRef(id); await setDoc(docRef, data); } catch (error) { @@ -256,9 +209,10 @@ export class FirestoreDataSource extends BaseDataSource { // Delete a document by ID delete = async (id?: string): Promise => { try { - if (!id && this.options.targetMode !== 'document') { - throw new Error('get() requires an ID when using collections'); - } + await super.delete(id); + // if (!id && this.options.targetMode !== 'document') { + // throw new Error('get() requires an ID when using collections'); + // } const docRef = this.#getRef(id); await deleteDoc(docRef); } catch (error) { @@ -274,7 +228,6 @@ export class FirestoreDataSource extends BaseDataSource { this.options?.targetMode === 'document' ? onSnapshot(provider, (snapshot: DocumentSnapshot) => { const data = snapshot.data(); - console.log(data); if (data) { callback(data); } else { diff --git a/src/libs/data-sources/data-sources/LocalStorageDataSource.tsx b/src/libs/data-sources/data-sources/LocalStorageDataSource.tsx index 954a143..7faabbb 100644 --- a/src/libs/data-sources/data-sources/LocalStorageDataSource.tsx +++ b/src/libs/data-sources/data-sources/LocalStorageDataSource.tsx @@ -1,5 +1,4 @@ // @ts-nocheck - import { DataSourceInitOptions } from '..'; import BaseDataSource from './BaseDataSource'; @@ -7,13 +6,14 @@ interface LocalStorageDataSourceProviderConfig { storageType?: 'localStorage' | 'sessionStorage'; } -export class LocalStorageDataSource extends BaseDataSource { +export class LocalStorageDataSource extends BaseDataSource { storageKey: string; storage: Storage; - subscribers: ((data: any) => void)[] = []; + //subscribers: ((data: any) => void)[] = []; + // subscribers: Array<(data: T | null) => void> = []; constructor( - options: DataSourceInitOptions, + options: DataSourceInitOptions, providerConfig?: LocalStorageDataSourceProviderConfig ) { super(options, providerConfig); @@ -39,50 +39,58 @@ export class LocalStorageDataSource extends BaseDataSource { window.dispatchEvent(new Event('local-storage')); } - // Notify all subscribers with the latest data - private notifySubscribers(data: T): void { - this.subscribers.forEach((callback) => callback(data)); - } + // // Notify all subscribers with the latest data + // private notifySubscribers(data: T): void { + // console.log('notifySubscribers', data); + // this.subscribers.forEach((callback) => callback(data)); + // } // Get a single item by ID async get(id?: string): Promise { + await super.get(id); const data = this.getData(); if (this.options?.targetMode === 'document') { return data; } - if (!id) throw new Error('ID is required for collection types'); + // if (!id) throw new Error('ID is required for collection types'); return data[id] || null; } // Get all items, with optional filters async getAll(filter: { [key: string]: any } = {}): Promise { + await super.getAll(filter); const data = this.getData(); if (this.options?.targetMode === 'document') { return [data]; } const items = Object.keys(data).map((key) => ({ id: key, ...data[key] })); - return items.filter((item) => { - return Object.keys(filter).every((key) => item[key] === filter[key]); - }); + // return items.filter((item) => { + // return Object.keys(filter).every((key) => item[key] === filter[key]); + // }); + return items; } // Add a new item async add(item: T): Promise { - this.validate(item); + await super.add(item); + // this.validate(item); if (this.options?.targetMode === 'document') { this.saveData(item); return item; } const data = this.getData() || []; - const id = new Date().getTime().toString(); // Generate a simple unique ID - data.push({ id, ...item }); + if (!data[this.options.idField]) { + item[this.options.idField] = this.generateNanoId(); + } + data.push(item); this.saveData(data); - return { id, ...item }; + return item; } // Update an existing item by ID async update(data?: T, id: any): Promise { - this.validate(data); + await super.update(data, id); + // this.validate(data); if (this.options?.targetMode === 'document') { const newData = id ? { ...this.getData(), ...id } : { ...this.getData(), ...data }; this.saveData(newData); @@ -100,7 +108,8 @@ export class LocalStorageDataSource extends BaseDataSource { // Set an existing item by ID async set(data?: T, id?: any): Promise { - this.validate(data); + await super.set(data, id); + // this.validate(data); if (this.options?.targetMode === 'document') { this.saveData(data); return; @@ -122,6 +131,7 @@ export class LocalStorageDataSource extends BaseDataSource { // Delete an item by ID async delete(id?: string): Promise { + await super.delete(id); if (this.options?.targetMode === 'document') { this.storage.removeItem(this.storageKey); window.dispatchEvent(new Event('local-storage')); diff --git a/src/libs/data-sources/data-sources/MockDataSource.tsx b/src/libs/data-sources/data-sources/MockDataSource.tsx index f8c7924..323dfd6 100644 --- a/src/libs/data-sources/data-sources/MockDataSource.tsx +++ b/src/libs/data-sources/data-sources/MockDataSource.tsx @@ -1,3 +1,5 @@ +// @ts-nocheck + import { DataSourceInitOptions } from '..'; import BaseDataSource from './BaseDataSource'; @@ -25,7 +27,7 @@ export class MockDataSource extends BaseDataSource { super(options, providerConfig); this.data = [...(providerConfig.data || [])]; if (providerConfig?.count && providerConfig.count > 0) { - this.data.push(...(this.getDummyData(providerConfig.count) as T[])); + //this.data.push(...(this.getDummyData(providerConfig.count) as T[])); } } @@ -111,7 +113,6 @@ export class MockDataSource extends BaseDataSource { * @private */ private notifySubscribers = (data: any): void => { - console.log(this.callbacks); this.callbacks.forEach((callback) => callback(data)); }; } diff --git a/src/libs/data-sources/index.tsx b/src/libs/data-sources/index.tsx index 9cdec78..f4b6d8e 100644 --- a/src/libs/data-sources/index.tsx +++ b/src/libs/data-sources/index.tsx @@ -1,99 +1,66 @@ /* eslint-disable react-refresh/only-export-components */ import { useContext } from 'react'; import * as Yup from 'yup'; +import BaseDataSource, { ValidationResult } from './data-sources/BaseDataSource'; import DataProvider, { DataContext } from './DataProvider'; import useData from './useData'; export type WithOptionalId = Omit & { id?: string }; -export interface DataSourceInitOptions { +export interface DataSourceInitOptions { target: string; targetMode?: 'collection' | 'document' | 'number' | 'string' | 'boolean'; //TODO: implement targetFilter?: FilterObject; subscribe?: boolean; YupValidationSchema?: Yup.AnySchema; - schema?: Record; + idField?: keyof T; + // schema?: Record; defaultValue?: any; - cleanValues?: { - removeUndefined?: boolean; - }; - mock?: boolean; //TODO: implement - mockOptions?: { - count?: number; - schema?: { - [key: string]: () => any; - }; + // cleanValues?: { + // removeUndefined?: boolean; + // }; + // mock?: boolean; //TODO: implement OR delete + // mockOptions?: { + // count?: number; + // schema?: { + // [key: string]: () => any; + // }; + // }; + converter?: { + toDatabase: (data: T) => any; + fromDatabase: (data: any) => T; }; + data?: Z; } export interface DataSourceObject { - [key: string]: any; + [key: string]: BaseDataSource; } -// export interface DataSource { -// key: string; -// dataSource: DataSourceSource; -// } - -export interface DataSourceActions { +export interface DataSourceActions { fetchData: (filter?: FilterObject) => Promise; - getAll: (filter?: FilterObject) => Promise; + getAll: (filter?: FilterObject) => Promise; get: (id?: string) => Promise; add: (item: WithOptionalId) => Promise; - update: (data: Partial>, id?: string) => Promise; + update: (data: Partial>, id?: string) => Promise; // TODO: method overloading set: (data: WithOptionalId, id?: string) => Promise; delete: (id?: string) => Promise; - validate: (data: T) => Promise; - getDummyData: () => T; + validate: (data: Partial) => Promise; } -// export interface UseDataReturn { -// // Options supplied to class constructor -// options?: DataSourceInitOptions; -// providerConfig?: any; -// // Data state -// data: T; -// loading: boolean; -// error: any; -// // Provider name -// provider: string; -// // Actions -// actions: DataSourceActions; -// // Predefined methods -// // getAll: (filter?: object) => Promise; -// // get: (id?: string) => Promise; -// // add: (item: T) => Promise; -// // update: (data: Partial, id?: string) => Promise; -// // set: (data: T, id?: string) => Promise; -// // delete: (id?: string) => Promise; -// // Raw datasource info -// dataSource: any; -// // Custom props -// custom?: { -// [key: string]: any; -// }; -// } - export interface DataSource { //TODO: fix second generic type // Options supplied to class constructor - options?: DataSourceInitOptions; + options?: DataSourceInitOptions; providerConfig?: any; // Data state - data: Z; + data: Z; //T | T[] | Partial | null | string | number | boolean; loading: boolean; error: any; // Provider name provider: string; // Actions - actions: DataSourceActions; - // Predefined methods - // getAll: (filter?: object) => Promise; - // get: (id?: string) => Promise; - // add: (item: T) => Promise; - // update: (data: Partial, id?: string) => Promise; - // set: (data: T, id?: string) => Promise; - // delete: (id?: string) => Promise; + actions: DataSourceActions; // Raw datasource info dataSource: any; // Custom props diff --git a/src/libs/data-sources/useData.tsx b/src/libs/data-sources/useData.tsx index 4e7b93e..2cc6af1 100644 --- a/src/libs/data-sources/useData.tsx +++ b/src/libs/data-sources/useData.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext, useEffect, useMemo } from 'react'; +import { useContext, useEffect, useMemo } from 'react'; import { DataSource, FilterObject, WithOptionalId } from '.'; import { DataContext } from './DataProvider'; @@ -19,10 +19,16 @@ const defaultOptions = { addDatasourceWhenNotAvailable: true, }; -const useData = ( +// Overload signatures + +// function useData(key: string, options?: UseDataPropsOptions): DataSource; +// function useData(key: string, options?: UseDataPropsOptions): DataSource; + +// Implementation +function useData( key: string, options: UseDataPropsOptions = defaultOptions -): DataSource => { +): DataSource { const context = useContext(DataContext); if (!context) { throw new Error('useData must be used within a DataProvider'); @@ -65,6 +71,7 @@ const useData = ( }, [newDataSource, key, addDataSource, context.dataSources, addDatasourceWhenNotAvailable]); const { get, getAll } = dataSource || {}; + const returnData = useMemo(() => data[key], [data, key]); function getClassMethods(obj: { [key: string]: any }): Record any> { const allMethods: Record any> = {}; @@ -115,30 +122,12 @@ const useData = ( return getClassMethods(dataSource); }, [dataSource]); - const find = useCallback( - (id: string) => { - data[key].find((item: any) => item.id === id); - }, - [data, key] - ); - - // const filteredData = useMemo(() => { - // if (options?.find) { - // return data[key].filter( - // (item: T) => - // options.find?.field !== undefined && item[options.find.field] === options.find.value - // ); - // } else { - // return data[key]; - // } - // }, [data, key, options]); // TODO: implement - const returnObject: DataSource = useMemo( () => ({ // Custom methods custom: { ...methods }, // Data state - data: data[key], + data: returnData, loading: loading[key] || false, error: error[key], // Public methods @@ -146,23 +135,12 @@ const useData = ( fetchData: (filter?: FilterObject) => fetchData(key, filter), get: get || (() => {}), getAll: getAll || (() => {}), - find, add: (item: WithOptionalId) => add(key, item), update: (data, id) => update(key, data, id), set: (data, id) => set(key, data, id), delete: (id) => remove(key, id), validate: dataSource?.validate, - getDummyData: dataSource?.getDummyData, }, - // fetchData: (filter?: any) => fetchData(key, filter), - // get: get || (() => {}), - // getAll: getAll || (() => {}), - // add: (item: T) => add(key, item), - // update: (data, id) => update(key, data, id), - // set: (data, id) => set(key, data, id), - // delete: (id) => remove(key, id), - // validate: dataSource?.validate, - // getDummyData: dataSource?.getDummyData, // Raw datasource info dataSource, provider: dataSource?.provider, @@ -170,22 +148,21 @@ const useData = ( [ add, fetchData, - data, dataSource, error, get, getAll, - find, key, loading, methods, remove, set, update, + returnData, ] ); return returnObject; -}; +} export default useData; diff --git a/src/libs/storage-providers/providers/BaseStorageProvider.tsx b/src/libs/storage-providers/providers/BaseStorageProvider.tsx index 1f9ccb9..7bd6a31 100644 --- a/src/libs/storage-providers/providers/BaseStorageProvider.tsx +++ b/src/libs/storage-providers/providers/BaseStorageProvider.tsx @@ -1,4 +1,3 @@ - export interface StorageClassOptions { [key: string]: any; } @@ -11,6 +10,7 @@ export default class BaseStorageProvider { this.options = options; this.providerOptions = providerOptions; this.uploadFile = this.uploadFile.bind(this); + this.listFiles = this.listFiles.bind(this); this.getFile = this.getFile.bind(this); this.deleteFile = this.deleteFile.bind(this); } @@ -55,4 +55,8 @@ export default class BaseStorageProvider { async deleteFile(_fileId: string): Promise { throw new Error('Method not implemented.'); } + + async resizeImage(_file: File, _maxWidth: number, _maxHeight: number): Promise { + throw new Error('Method not implemented.'); + } } diff --git a/src/libs/tabs/index.tsx b/src/libs/tabs/index.tsx index 2a32cf8..62d7fec 100644 --- a/src/libs/tabs/index.tsx +++ b/src/libs/tabs/index.tsx @@ -7,8 +7,6 @@ export interface TabData { component: JSX.Element; } -// eslint-disable-next-line react-refresh/only-export-components -export { default as useCurrentTab } from './use-current-tab'; // eslint-disable-next-line react-refresh/only-export-components export { default as useTabs } from './use-tabs'; diff --git a/src/libs/tabs/tabs.tsx b/src/libs/tabs/tabs.tsx index a660207..f1149fc 100644 --- a/src/libs/tabs/tabs.tsx +++ b/src/libs/tabs/tabs.tsx @@ -1,7 +1,6 @@ import useTabs, { TabOptions } from '@/hooks/use-tabs'; import { Box, BoxProps, TabsProps as DTabsProps, TabProps, useTheme } from '@mui/material'; import { TabData } from '.'; -import { CurrentTabContext } from './context'; import TabPanel from './tab-panel'; import TabsHeader from './tabs-header'; @@ -15,39 +14,37 @@ interface TabsProps { const Tabs = ({ tabs, tabOptions, muiBoxProps, muiTabProps, muiTabsProps }: TabsProps) => { const theme = useTheme(); - const { tab: currentTab, handleTabChange, setTab } = useTabs(tabs, tabOptions); + const { tab: currentTab, handleTabChange } = useTabs(tabs, tabOptions); // const [value, setValue] = React.useState(0); //TODO: version2..... return ( - - - - {tabs?.map((tab, index) => { - return ( - - {tab.value === currentTab && tab.component} - - ); - })} - - + + + {tabs?.map((tab, index) => { + return ( + + {tab.value === currentTab && tab.component} + + ); + })} + ); }; diff --git a/src/libs/tabs/use-current-tab.tsx b/src/libs/tabs/use-current-tab.tsx deleted file mode 100644 index 9a629d9..0000000 --- a/src/libs/tabs/use-current-tab.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useContext } from 'react'; -import { CurrentTabContext } from '.'; - -// Custom hook to use the current tab context -const useCurrentTab = () => { - const context = useContext(CurrentTabContext); - if (!context) { - throw new Error('useCurrentTab must be used within a Tabs component'); - } - return context; -}; - -export default useCurrentTab; diff --git a/src/pages/default/Settings.tsx b/src/pages/default/Settings.tsx index 1a0358a..0b58243 100644 --- a/src/pages/default/Settings.tsx +++ b/src/pages/default/Settings.tsx @@ -1,15 +1,17 @@ import { Box, Button, Paper } from '@mui/material'; // import { useState } from 'react'; import { useData } from '@/libs/data-sources'; +import { Dummy } from '@/schemas/dummy'; import DefaultPaperbasePage from './DefaultPaperbasePage'; -interface Settings { +interface ISettings { test: string; } const Settings = () => { - const settings = useData('settings'); - console.log(settings); + const settings = useData('settings'); + const dummy = useData('calculator_configs'); + console.log(settings, dummy.data.push); return ( diff --git a/src/pages/default/account/components/profile-card.tsx b/src/pages/default/account/components/profile-card.tsx index c3e6065..a49ea28 100644 --- a/src/pages/default/account/components/profile-card.tsx +++ b/src/pages/default/account/components/profile-card.tsx @@ -1,5 +1,14 @@ import { CameraAlt as CameraAltIcon } from '@mui/icons-material'; -import { Avatar, Button, Card, CardContent, CardHeader, Grid, TextField } from '@mui/material'; +import { + Avatar, + Button, + Card, + CardActions, + CardContent, + CardHeader, + Grid, + TextField, +} from '@mui/material'; import { useFormik } from 'formik'; interface ProfileCardProps { @@ -79,6 +88,8 @@ const ProfileCard = ({ profile, setProfile }: ProfileCardProps) => { {...formik.getFieldProps('email')} margin="normal" /> + + - + ); }; diff --git a/src/pages/default/account/index.tsx b/src/pages/default/account/index.tsx index 67f3938..072510d 100644 --- a/src/pages/default/account/index.tsx +++ b/src/pages/default/account/index.tsx @@ -37,7 +37,7 @@ const Account = () => { > await auth.updateProfile(values)} + setProfile={async (values) => await auth.updateProfile?.(values)} /> { { console.log(oldPassword, newPassword); - await auth.updatePassword(oldPassword, newPassword); + await auth.updatePassword?.(oldPassword, newPassword); }} /> diff --git a/src/pages/default/test/data-sources/index.tsx b/src/pages/default/test/data-sources/index.tsx index 260ef91..3b756f0 100644 --- a/src/pages/default/test/data-sources/index.tsx +++ b/src/pages/default/test/data-sources/index.tsx @@ -1,11 +1,11 @@ import JsonEditor from '@/components/default/json-editor'; import { db } from '@/config/firebase'; +import BaseDataSource from '@/libs/data-sources/data-sources/BaseDataSource'; import FirestoreDataSource from '@/libs/data-sources/data-sources/FirestoreDataSource'; import LocalStorageDataSource from '@/libs/data-sources/data-sources/LocalStorageDataSource'; -import MockDataSource from '@/libs/data-sources/data-sources/MockDataSource'; import useData from '@/libs/data-sources/useData'; -import Tabs, { useCurrentTab } from '@/libs/tabs'; -import { Dummy, dummyMockSchema, dummyYupSchema } from '@/schemas/dummy'; +import Tabs from '@/libs/tabs'; +import { createDummySchema, Dummy, dummyYupSchema } from '@/schemas/dummy'; import { Button, Card, CardActions, CardContent, CardHeader, Grid } from '@mui/material'; import DefaultPage from '../../DefaultPage'; @@ -17,7 +17,7 @@ const datasources = { targetMode: 'collection', subscribe: true, YupValidationSchema: dummyYupSchema, - mockOptions: { schema: dummyMockSchema }, + // mockOptions: { schema: dummyMockSchema }, }, { db } ), @@ -31,7 +31,7 @@ const datasources = { targetMode: 'document', subscribe: true, YupValidationSchema: dummyYupSchema, - mockOptions: { schema: dummyMockSchema }, + // mockOptions: { schema: dummyMockSchema }, }, { db } ), @@ -40,7 +40,7 @@ const datasources = { target: 'dummy/01iQznR3TyhzhI5ct5cQ', targetMode: 'document', YupValidationSchema: dummyYupSchema, - mockOptions: { schema: dummyMockSchema }, + // mockOptions: { schema: dummyMockSchema }, }, { db } ), @@ -56,7 +56,7 @@ const datasources = { filters: [{ field: 'boolean', operator: '==', value: false }], pagination: { page: 1, pageSize: 5 }, }, - mockOptions: { schema: dummyMockSchema }, + // mockOptions: { schema: dummyMockSchema }, }, { db } ), @@ -85,26 +85,41 @@ const datasources = { subscribe: true, YupValidationSchema: dummyYupSchema, }), + test2: new LocalStorageDataSource({ + target: 'dummy', + subscribe: true, + YupValidationSchema: dummyYupSchema, + }), Normal: new LocalStorageDataSource({ target: 'dummy', YupValidationSchema: dummyYupSchema }), }, - MockDataSource: { - Normal: new MockDataSource( - { - target: 'dummy', - subscribe: true, - YupValidationSchema: dummyYupSchema, - mockOptions: { schema: dummyMockSchema }, - }, - { count: 10 } - ), + Base: { + Realtime: new BaseDataSource({ + target: 'dummy', + subscribe: true, + YupValidationSchema: dummyYupSchema, + data: createDummySchema().getTestData(5), + }), }, + // MockDataSource: { + // Normal: new MockDataSource( + // { + // target: 'dummy', + // subscribe: true, + // YupValidationSchema: dummyYupSchema, + // mockOptions: { schema: dummyMockSchema }, + // }, + // { count: 10 } + // ), + // }, }; const DataSource = (props: any) => { const { datasourceName, dataSource: newDataSource } = props; const datasource = useData(datasourceName, { datasource: newDataSource }); + const handleAdd = async () => { - const newItem = datasource.actions.getDummyData(); //getDummyTestData(1) as Dummy; + const newItem = createDummySchema().generateTestData(); //getDummyTestData(1) as Dummy; + console.log('newitem', newItem); await datasource.actions.add(newItem); }; @@ -165,11 +180,11 @@ const DataSource = (props: any) => { ); }; -const DataSourceTab = () => { - const { currentTab } = useCurrentTab(); - const dataSources: { - [key: string]: FirestoreDataSource | LocalStorageDataSource | MockDataSource; - } = datasources[currentTab as keyof typeof datasources]; +const DataSourceTab = ({ tab: currentTab }: { tab: string }) => { + //const { currentTab } = useCurrentTab(); + const dataSources = datasources[currentTab as keyof typeof datasources] as { + [key: string]: FirestoreDataSource | LocalStorageDataSource | BaseDataSource; + }; return ( { const DataOperations = () => { const tabsData = Object.keys(datasources).map((key) => { - const component = ; + const component = ; return { label: key, value: key, component }; }); return ( diff --git a/src/pages/default/test/filters-page.tsx b/src/pages/default/test/filters-page.tsx index 5159a72..43d5cb6 100644 --- a/src/pages/default/test/filters-page.tsx +++ b/src/pages/default/test/filters-page.tsx @@ -1,5 +1,5 @@ +import DataTable from '@/components/default/data-table'; import { createDummySchema, Dummy } from '@/schemas/dummy'; -import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; import { useMemo } from 'react'; import DefaultPage from '../DefaultPage'; @@ -21,8 +21,12 @@ const FiltersPage = () => { return ( -
{JSON.stringify(data, null, 2)}
- + {/*
{JSON.stringify(data, null, 2)}
*/} + + {/* @@ -41,7 +45,7 @@ const FiltersPage = () => { ))}
-
+
*/}
); }; diff --git a/src/schemas/dummy.ts b/src/schemas/dummy.ts index 87c2816..0d81912 100644 --- a/src/schemas/dummy.ts +++ b/src/schemas/dummy.ts @@ -1,9 +1,9 @@ -import generateTestData from '@/utils/generate-test-data'; import { faker } from '@faker-js/faker'; import * as Yup from 'yup'; import { createDefaultSchema } from '.'; export const dummyYupSchema = Yup.object().shape({ + id: Yup.string().required('ID is required').min(3, 'Minimum 3 characters'), name: Yup.string().required('Name is required').min(3, 'Minimum 3 characters'), string: Yup.string().optional(), number: Yup.number().optional(), @@ -26,6 +26,13 @@ export type Dummy = Yup.InferType; export const createDummySchema = () => { const defaultSchema = createDefaultSchema(dummyYupSchema); + const generateTestData = () => { + return { + ...defaultSchema.generateTestData(dummyYupSchema), + name: faker.word.verb() + ' ' + faker.word.noun(), + id: defaultSchema.generateNanoId(), + }; + }; return { ...defaultSchema, getTemplate: () => { @@ -34,11 +41,15 @@ export const createDummySchema = () => { name: faker.word.verb() + ' ' + faker.word.noun(), }; }, - generateTestData: () => { - return { - ...generateTestData(dummyYupSchema), - name: faker.word.verb() + ' ' + faker.word.noun(), - }; + generateTestData, + getTestData: (count: number): Dummy[] => { + if (count > 1) { + return Array.from({ length: count }, () => { + return generateTestData(); + }); + } else { + throw new Error('Count must be greater than 0'); + } }, }; }; diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 21ebe12..1944e21 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -139,16 +139,10 @@ export const createDefaultSchema = ( getTestData: (count?: number): T | T[] => { // If number is 1, return a single object if (!count || count === 1) { - return { - ...generateTestData(schema), - name: faker.word.verb() + ' ' + faker.word.noun(), - }; + return generateTestData(schema) as T; } else if (count > 1) { return Array.from({ length: count }, () => { - return { - ...generateTestData(schema), - name: faker.word.verb() + ' ' + faker.word.noun(), - }; + return generateTestData(schema) as T; }); } else { throw new Error('Count must be greater than 0'); @@ -234,174 +228,174 @@ export const createDefaultSchema = ( }; }; -export default class DefaultSchema { - // public getCustomFieldDefinitions?: () => { [key: string]: { [key: string]: any } } | undefined; - public getCustomFieldDefinitions?: () => { [key: string]: Partial } | undefined; - constructor( - public yupSchema: Yup.ObjectSchema - // public getCustomFieldDefinitions?: () => { - // [key: string]: { [key: string]: Partial }; - // } - ) { - // this.getCustomFieldDefinitions = () => customFieldDefinitions; - } - - #generateTestData = (schema: YupSchema): T => { - const shape = schema.fields; - const data: Partial = {}; - - Object.keys(shape).forEach((key) => { - const field = shape[key]; - - if (field instanceof Yup.StringSchema) { - const minLength = (field.spec as any).min ?? 5; - const maxLength = (field.spec as any).max ?? 20; - data[key as keyof T] = faker.lorem.words( - faker.number.int({ min: minLength, max: maxLength }) - ) as any; - } else if (field instanceof Yup.NumberSchema) { - const min = (field.spec as any).min ?? 0; - const max = (field.spec as any).max ?? 100; - data[key as keyof T] = faker.number.int({ min, max }) as any; - } else if (field instanceof Yup.BooleanSchema) { - data[key as keyof T] = faker.datatype.boolean() as any; - } else if (field instanceof Yup.DateSchema) { - const minDate = (field.spec as any).min - ? new Date((field.spec as any).min) - : faker.date.past(); - const maxDate = (field.spec as any).max - ? new Date((field.spec as any).max) - : faker.date.future(); - data[key as keyof T] = faker.date.between({ from: minDate, to: maxDate }) as any; - } else if (field instanceof Yup.ArraySchema) { - const itemType = (field as Yup.ArraySchema).innerType; - const minItems = (field.spec as any).min ?? 1; - const maxItems = (field.spec as any).max ?? 5; - const length = faker.number.int({ min: minItems, max: maxItems }); - - if (itemType instanceof Yup.StringSchema) { - data[key as keyof T] = Array.from({ length }, () => faker.lorem.word()) as any; - } else if (itemType instanceof Yup.NumberSchema) { - data[key as keyof T] = Array.from({ length }, () => faker.number.int()) as any; - } - } else if (field instanceof Yup.ObjectSchema) { - data[key as keyof T] = this.#generateTestData(field) as any; - } - }); +// export default class DefaultSchema { +// // public getCustomFieldDefinitions?: () => { [key: string]: { [key: string]: any } } | undefined; +// public getCustomFieldDefinitions?: () => { [key: string]: Partial } | undefined; +// constructor( +// public yupSchema: Yup.ObjectSchema +// // public getCustomFieldDefinitions?: () => { +// // [key: string]: { [key: string]: Partial }; +// // } +// ) { +// // this.getCustomFieldDefinitions = () => customFieldDefinitions; +// } + +// #generateTestData = (schema: YupSchema): T => { +// const shape = schema.fields; +// const data: Partial = {}; + +// Object.keys(shape).forEach((key) => { +// const field = shape[key]; + +// if (field instanceof Yup.StringSchema) { +// const minLength = (field.spec as any).min ?? 5; +// const maxLength = (field.spec as any).max ?? 20; +// data[key as keyof T] = faker.lorem.words( +// faker.number.int({ min: minLength, max: maxLength }) +// ) as any; +// } else if (field instanceof Yup.NumberSchema) { +// const min = (field.spec as any).min ?? 0; +// const max = (field.spec as any).max ?? 100; +// data[key as keyof T] = faker.number.int({ min, max }) as any; +// } else if (field instanceof Yup.BooleanSchema) { +// data[key as keyof T] = faker.datatype.boolean() as any; +// } else if (field instanceof Yup.DateSchema) { +// const minDate = (field.spec as any).min +// ? new Date((field.spec as any).min) +// : faker.date.past(); +// const maxDate = (field.spec as any).max +// ? new Date((field.spec as any).max) +// : faker.date.future(); +// data[key as keyof T] = faker.date.between({ from: minDate, to: maxDate }) as any; +// } else if (field instanceof Yup.ArraySchema) { +// const itemType = (field as Yup.ArraySchema).innerType; +// const minItems = (field.spec as any).min ?? 1; +// const maxItems = (field.spec as any).max ?? 5; +// const length = faker.number.int({ min: minItems, max: maxItems }); + +// if (itemType instanceof Yup.StringSchema) { +// data[key as keyof T] = Array.from({ length }, () => faker.lorem.word()) as any; +// } else if (itemType instanceof Yup.NumberSchema) { +// data[key as keyof T] = Array.from({ length }, () => faker.number.int()) as any; +// } +// } else if (field instanceof Yup.ObjectSchema) { +// data[key as keyof T] = this.#generateTestData(field) as any; +// } +// }); - return data as T; - }; +// return data as T; +// }; - generateObjectName = () => { - return faker.word.noun(); - }; +// generateObjectName = () => { +// return faker.word.noun(); +// }; - generateName = () => { - const name = faker.word.verb() + ' ' + faker.word.noun(); - // Capatalize first letter of each word - return name.replace(/\b\w/g, (l) => l.toUpperCase()); - }; +// generateName = () => { +// const name = faker.word.verb() + ' ' + faker.word.noun(); +// // Capatalize first letter of each word +// return name.replace(/\b\w/g, (l) => l.toUpperCase()); +// }; - _generateId = () => { - return faker.string.uuid(); - }; +// _generateId = () => { +// return faker.string.uuid(); +// }; - _generateUlid = () => { - return faker.string.ulid(); - }; +// _generateUlid = () => { +// return faker.string.ulid(); +// }; - _generateNanoId = (options?: number | { min: number; max: number }) => { - return faker.string.nanoid(options); - }; +// _generateNanoId = (options?: number | { min: number; max: number }) => { +// return faker.string.nanoid(options); +// }; - _generateUuid = () => { - return faker.string.uuid(); - }; +// _generateUuid = () => { +// return faker.string.uuid(); +// }; - getTemplate(): Partial { - const defaultValues = this.yupSchema.getDefault(); - // Return all non undefined values - return Object.keys(defaultValues).reduce((acc: Partial, key) => { - if (defaultValues[key] !== undefined) { - acc[key as keyof T] = defaultValues[key as keyof T]; - } - return acc; - }, {}); - // return this.yupSchema.getDefault(); - } - - getFieldTemplate(field: string) { - const template: { [key: string]: any } = this.getTemplate(); - return template[field] || undefined; - } - - getTestData = (count?: number): T | T[] => { - // If number is 1, return a single object - if (!count || count === 1) { - return { - ...this.#generateTestData(this.yupSchema), - name: faker.word.verb() + ' ' + faker.word.noun(), - }; - } else if (count > 1) { - return Array.from({ length: count }, () => { - return { - ...this.#generateTestData(this.yupSchema), - name: faker.word.verb() + ' ' + faker.word.noun(), - }; - }); - } else { - throw new Error('Count must be greater than 0'); - } - }; +// getTemplate(): Partial { +// const defaultValues = this.yupSchema.getDefault(); +// // Return all non undefined values +// return Object.keys(defaultValues).reduce((acc: Partial, key) => { +// if (defaultValues[key] !== undefined) { +// acc[key as keyof T] = defaultValues[key as keyof T]; +// } +// return acc; +// }, {}); +// // return this.yupSchema.getDefault(); +// } + +// getFieldTemplate(field: string) { +// const template: { [key: string]: any } = this.getTemplate(); +// return template[field] || undefined; +// } + +// getTestData = (count?: number): T | T[] => { +// // If number is 1, return a single object +// if (!count || count === 1) { +// return { +// ...this.#generateTestData(this.yupSchema), +// name: faker.word.verb() + ' ' + faker.word.noun(), +// }; +// } else if (count > 1) { +// return Array.from({ length: count }, () => { +// return { +// ...this.#generateTestData(this.yupSchema), +// name: faker.word.verb() + ' ' + faker.word.noun(), +// }; +// }); +// } else { +// throw new Error('Count must be greater than 0'); +// } +// }; - getFieldDefinitions(): { - [key: string]: FieldConfig; - } { - // Go through all fields and add them to the return schema. - // Merge with fieldConfig if it exists. - // If there are meta fields, also add them - // If there are nested fields, also add them - - const result: { [key: string]: FieldConfig } = {}; - const extractFields = (schema: Yup.ObjectSchema, basePath?: string) => { - const fields = schema.fields; - Object.keys(fields).forEach((key) => { - const newKey = basePath ? `${basePath}.${key}` : key; - const field = fields[key]; - const description = field.describe(); - const label = 'label' in description ? description.label : key; - const defaultValue = 'default' in description ? description.default : undefined; - const meta = 'meta' in description ? description.meta : {}; - const newField: FieldConfig = { - name: newKey, - id: key, - label, - type: description.type, - default: defaultValue, - ...meta, - }; - - if (field.describe().type === 'object') { - extractFields(field as Yup.ObjectSchema, newField.name); - } else if (field.describe().type === 'array') { - const itemType = (field as Yup.ArraySchema).innerType; - if (itemType instanceof Yup.ObjectSchema) { - extractFields(itemType as Yup.ObjectSchema, newField.name); - } - } - result[newKey] = newField; - }); - }; - extractFields(this.yupSchema); - const customFieldDefinitions = this.getCustomFieldDefinitions - ? this.getCustomFieldDefinitions() - : {}; - const returnFieldDefinitions = _.merge({}, result, customFieldDefinitions); - return returnFieldDefinitions; //defaultFieldDefinitions; - // const merged = merge({}, result, fieldConfig); - // return merged; - } -} +// getFieldDefinitions(): { +// [key: string]: FieldConfig; +// } { +// // Go through all fields and add them to the return schema. +// // Merge with fieldConfig if it exists. +// // If there are meta fields, also add them +// // If there are nested fields, also add them + +// const result: { [key: string]: FieldConfig } = {}; +// const extractFields = (schema: Yup.ObjectSchema, basePath?: string) => { +// const fields = schema.fields; +// Object.keys(fields).forEach((key) => { +// const newKey = basePath ? `${basePath}.${key}` : key; +// const field = fields[key]; +// const description = field.describe(); +// const label = 'label' in description ? description.label : key; +// const defaultValue = 'default' in description ? description.default : undefined; +// const meta = 'meta' in description ? description.meta : {}; +// const newField: FieldConfig = { +// name: newKey, +// id: key, +// label, +// type: description.type, +// default: defaultValue, +// ...meta, +// }; + +// if (field.describe().type === 'object') { +// extractFields(field as Yup.ObjectSchema, newField.name); +// } else if (field.describe().type === 'array') { +// const itemType = (field as Yup.ArraySchema).innerType; +// if (itemType instanceof Yup.ObjectSchema) { +// extractFields(itemType as Yup.ObjectSchema, newField.name); +// } +// } +// result[newKey] = newField; +// }); +// }; +// extractFields(this.yupSchema); +// const customFieldDefinitions = this.getCustomFieldDefinitions +// ? this.getCustomFieldDefinitions() +// : {}; +// const returnFieldDefinitions = _.merge({}, result, customFieldDefinitions); +// return returnFieldDefinitions; //defaultFieldDefinitions; +// // const merged = merge({}, result, fieldConfig); +// // return merged; +// } +// } // export const extractFieldDefinitionFromYupSchema = ( // schema: Yup.ObjectSchema, diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo index 8b253e4..1db25a1 100644 --- a/tsconfig.app.tsbuildinfo +++ b/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/dashboard.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/default/colormodeselect.jsx","./src/components/default/custom-breadcrumbs.tsx","./src/components/default/custom-dialog.tsx","./src/components/default/custom-popover.tsx","./src/components/default/custom-table.jsx","./src/components/default/error-boundary.tsx","./src/components/default/floating-button.tsx","./src/components/default/guest-avatar.tsx","./src/components/default/json-editor.tsx","./src/components/default/notepad.tsx","./src/components/default/scroll-to-top.tsx","./src/components/default/scrollbar.jsx","./src/components/default/seo.jsx","./src/components/default/severity-pill.jsx","./src/components/default/splash-screen.jsx","./src/components/default/tab-section.tsx","./src/components/default/auth/customicons.jsx","./src/components/default/auth/forgotpassword.jsx","./src/components/default/filters/sort-options.tsx","./src/components/default/images/image-cropper.tsx","./src/components/default/images/image-uploader.tsx","./src/components/default/images/image-viewer-dialog.tsx","./src/components/default/images/no-image-available.tsx","./src/components/default/layout/language-switch.tsx","./src/components/default/layout/user-menu.tsx","./src/components/default/ui/grid-layout.tsx","./src/components/default/ui/horizontal-scroll-card-list.tsx","./src/components/default/ui/image-list.tsx","./src/components/default/ui/loading-button.tsx","./src/components/default/ui/search-bar.tsx","./src/config/firebase.tsx","./src/config/index.tsx","./src/config/locales.tsx","./src/config/paths.tsx","./src/config/routes.tsx","./src/config/routing.tsx","./src/config/theme.tsx","./src/data/backend.ts","./src/data/recipe-data.tsx","./src/guards/auth-guard.tsx","./src/guards/guest-guard.jsx","./src/guards/issuer-guard.jsx","./src/hooks/use-deep-compare-memo.ts","./src/hooks/use-dialog.ts","./src/hooks/use-fetch.ts","./src/hooks/use-guid.ts","./src/hooks/use-https-redirect.ts","./src/hooks/use-is-mobile.ts","./src/hooks/use-keyboard-shortcut.ts","./src/hooks/use-mounted.ts","./src/hooks/use-navigation-guard.tsx","./src/hooks/use-param-item.ts","./src/hooks/use-path-router.ts","./src/hooks/use-popover.ts","./src/hooks/use-query-param-action.ts","./src/hooks/use-query-tabs.ts","./src/hooks/use-router.ts","./src/hooks/use-search-params.ts","./src/hooks/use-tabs.ts","./src/layouts/paperbase/content.jsx","./src/layouts/paperbase/header.jsx","./src/layouts/paperbase/layout.jsx","./src/layouts/paperbase/navigator.jsx","./src/layouts/simple/layout.tsx","./src/layouts/simple/theme.tsx","./src/libs/auth/context.tsx","./src/libs/auth/contextstate.ts","./src/libs/auth/index.tsx","./src/libs/auth/permissions-abac.ts","./src/libs/auth/permissions-rbac.ts","./src/libs/auth/use-auth.ts","./src/libs/auth/use-login-form.tsx","./src/libs/auth/auth-providers/compositeauthprovider.jsx","./src/libs/auth/auth-providers/firebaseauthprovider.tsx","./src/libs/auth/auth-providers/iauthprovider.ts","./src/libs/automation/index.tsx","./src/libs/data-sources/dataprovider.tsx","./src/libs/data-sources/index.tsx","./src/libs/data-sources/usedata.tsx","./src/libs/data-sources/data-sources/appwritedatasource.jsx","./src/libs/data-sources/data-sources/basedatasource.tsx","./src/libs/data-sources/data-sources/firebasedatasource.jsx","./src/libs/data-sources/data-sources/firebasedatasourcenorealtime.jsx","./src/libs/data-sources/data-sources/firestoredatasource.tsx","./src/libs/data-sources/data-sources/localstoragedatasource.tsx","./src/libs/data-sources/data-sources/mockdatasource.tsx","./src/libs/data-sources/data-sources/supabasedatasource.jsx","./src/libs/feature-flags/index.ts","./src/libs/feature-flags/murmurhash.ts","./src/libs/filters/index.tsx","./src/libs/filters/use-filter.ts","./src/libs/filters/components/boolean-filter.tsx","./src/libs/filters/components/mult-select-filter.tsx","./src/libs/filters/components/pagination.tsx","./src/libs/filters/components/range-filter.tsx","./src/libs/filters/components/search-bar.tsx","./src/libs/filters/components/select-filter.tsx","./src/libs/filters/components/sort-options.tsx","./src/libs/forms/custom-form.tsx","./src/libs/forms/index.tsx","./src/libs/forms/use-custom-formik.tsx","./src/libs/forms/use-form-button.tsx","./src/libs/forms/use-form-field.tsx","./src/libs/forms/components/autocomplete.tsx","./src/libs/forms/components/autocompletechiplist.tsx","./src/libs/forms/components/cancelbutton.tsx","./src/libs/forms/components/checkboxlist.tsx","./src/libs/forms/components/errors.tsx","./src/libs/forms/components/image.tsx","./src/libs/forms/components/images.tsx","./src/libs/forms/components/imagesform.tsx","./src/libs/forms/components/list.tsx","./src/libs/forms/components/notepad.tsx","./src/libs/forms/components/rating.tsx","./src/libs/forms/components/select.tsx","./src/libs/forms/components/submitbutton.tsx","./src/libs/forms/components/table.tsx","./src/libs/forms/components/textfield.tsx","./src/libs/i18n/index.ts","./src/libs/storage-providers/index.ts","./src/libs/storage-providers/providers/appwritestorageprovider.tsx","./src/libs/storage-providers/providers/basestorageprovider.tsx","./src/libs/storage-providers/providers/firebasestorageprovider.tsx","./src/libs/tabs/context.ts","./src/libs/tabs/index.tsx","./src/libs/tabs/tab-panel.tsx","./src/libs/tabs/tabs-header.tsx","./src/libs/tabs/tabs.tsx","./src/libs/tabs/use-current-tab.tsx","./src/libs/tabs/use-tabs.tsx","./src/pages/default/404.tsx","./src/pages/default/accountold.jsx","./src/pages/default/defaultpage.tsx","./src/pages/default/defaultpaperbasepage.jsx","./src/pages/default/settings.tsx","./src/pages/default/signin.jsx","./src/pages/default/profile.tsx","./src/pages/default/account/index.tsx","./src/pages/default/account/components/debug-card.tsx","./src/pages/default/account/components/password-card.tsx","./src/pages/default/account/components/profile-card.tsx","./src/pages/default/test/filters-page.tsx","./src/pages/default/test/translations.tsx","./src/pages/default/test/auth-providers/index.tsx","./src/pages/default/test/data/index.tsx","./src/pages/default/test/data-sources/index.tsx","./src/pages/default/test/file-uploads/index.tsx","./src/pages/default/test/file-uploads/_components/image-uploader-card.tsx","./src/pages/default/test/file-uploads/_components/simple-cropper.tsx","./src/pages/default/test/forms/index.tsx","./src/routes/approutes.tsx","./src/routes/defaultroutes.tsx","./src/routes/index.ts","./src/schemas/dummy.ts","./src/schemas/external-recipe.ts","./src/schemas/index.ts","./src/schemas/issue.ts","./src/sections/default/issue-dialog.tsx","./src/theme/paperbase/theme.jsx","./src/utils/create-guid.ts","./src/utils/generate-test-data.ts","./src/utils/is-equal.ts","./src/utils/random-name-generator.ts","./src/utils/images/create-thumbnail.ts","./src/utils/images/get-next-filename.ts","./src/utils/images/resize-image.ts","./src/utils/images/save-external-image.ts"],"errors":true,"version":"5.6.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/dashboard.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/default/colormodeselect.jsx","./src/components/default/custom-breadcrumbs.tsx","./src/components/default/custom-dialog.tsx","./src/components/default/custom-popover.tsx","./src/components/default/custom-table.jsx","./src/components/default/data-table.tsx","./src/components/default/error-boundary.tsx","./src/components/default/floating-button.tsx","./src/components/default/guest-avatar.tsx","./src/components/default/json-editor.tsx","./src/components/default/notepad.tsx","./src/components/default/scroll-to-top.tsx","./src/components/default/scrollbar.jsx","./src/components/default/seo.jsx","./src/components/default/severity-pill.jsx","./src/components/default/splash-screen.jsx","./src/components/default/tab-section.tsx","./src/components/default/auth/customicons.jsx","./src/components/default/auth/forgotpassword.jsx","./src/components/default/filters/sort-options.tsx","./src/components/default/images/image-cropper.tsx","./src/components/default/images/image-uploader.tsx","./src/components/default/images/image-viewer-dialog.tsx","./src/components/default/images/no-image-available.tsx","./src/components/default/layout/language-switch.tsx","./src/components/default/layout/user-menu.tsx","./src/components/default/ui/grid-layout.tsx","./src/components/default/ui/horizontal-scroll-card-list.tsx","./src/components/default/ui/image-list.tsx","./src/components/default/ui/loading-button.tsx","./src/components/default/ui/search-bar.tsx","./src/config/firebase.tsx","./src/config/index.tsx","./src/config/locales.tsx","./src/config/paths.tsx","./src/config/routes.tsx","./src/config/routing.tsx","./src/config/theme.tsx","./src/data/backend.ts","./src/data/recipe-data.tsx","./src/guards/auth-guard.tsx","./src/guards/guest-guard.jsx","./src/guards/issuer-guard.jsx","./src/hooks/use-deep-compare-memo.ts","./src/hooks/use-dialog.ts","./src/hooks/use-fetch.ts","./src/hooks/use-guid.ts","./src/hooks/use-https-redirect.ts","./src/hooks/use-is-mobile.ts","./src/hooks/use-keyboard-shortcut.ts","./src/hooks/use-mounted.ts","./src/hooks/use-navigation-guard.tsx","./src/hooks/use-param-item.ts","./src/hooks/use-path-router.ts","./src/hooks/use-popover.ts","./src/hooks/use-query-param-action.ts","./src/hooks/use-query-tabs.ts","./src/hooks/use-router.ts","./src/hooks/use-search-params.ts","./src/hooks/use-tabs.ts","./src/layouts/paperbase/content.jsx","./src/layouts/paperbase/header.jsx","./src/layouts/paperbase/layout.jsx","./src/layouts/paperbase/navigator.jsx","./src/layouts/simple/layout.tsx","./src/layouts/simple/theme.tsx","./src/libs/auth/context.tsx","./src/libs/auth/contextstate.ts","./src/libs/auth/index.tsx","./src/libs/auth/permissions-abac.ts","./src/libs/auth/permissions-rbac.ts","./src/libs/auth/use-auth.ts","./src/libs/auth/use-login-form.tsx","./src/libs/auth/auth-providers/compositeauthprovider.jsx","./src/libs/auth/auth-providers/firebaseauthprovider.tsx","./src/libs/auth/auth-providers/iauthprovider.ts","./src/libs/automation/index.tsx","./src/libs/data-sources/dataprovider.tsx","./src/libs/data-sources/index.tsx","./src/libs/data-sources/usedata.tsx","./src/libs/data-sources/data-sources/appwritedatasource.jsx","./src/libs/data-sources/data-sources/basedatasource.tsx","./src/libs/data-sources/data-sources/firebasedatasource.jsx","./src/libs/data-sources/data-sources/firebasedatasourcenorealtime.jsx","./src/libs/data-sources/data-sources/firestoredatasource.tsx","./src/libs/data-sources/data-sources/localstoragedatasource.tsx","./src/libs/data-sources/data-sources/mockdatasource.tsx","./src/libs/data-sources/data-sources/supabasedatasource.jsx","./src/libs/feature-flags/index.ts","./src/libs/feature-flags/murmurhash.ts","./src/libs/filters/index.tsx","./src/libs/filters/use-filter.ts","./src/libs/filters/components/boolean-filter.tsx","./src/libs/filters/components/mult-select-filter.tsx","./src/libs/filters/components/pagination.tsx","./src/libs/filters/components/range-filter.tsx","./src/libs/filters/components/search-bar.tsx","./src/libs/filters/components/select-filter.tsx","./src/libs/filters/components/sort-options.tsx","./src/libs/forms/custom-form.tsx","./src/libs/forms/index.tsx","./src/libs/forms/use-custom-formik.tsx","./src/libs/forms/use-form-button.tsx","./src/libs/forms/use-form-field.tsx","./src/libs/forms/components/autocomplete.tsx","./src/libs/forms/components/autocompletechiplist.tsx","./src/libs/forms/components/cancelbutton.tsx","./src/libs/forms/components/checkboxlist.tsx","./src/libs/forms/components/errors.tsx","./src/libs/forms/components/image.tsx","./src/libs/forms/components/images.tsx","./src/libs/forms/components/imagesform.tsx","./src/libs/forms/components/list.tsx","./src/libs/forms/components/notepad.tsx","./src/libs/forms/components/rating.tsx","./src/libs/forms/components/select.tsx","./src/libs/forms/components/submitbutton.tsx","./src/libs/forms/components/table.tsx","./src/libs/forms/components/textfield.tsx","./src/libs/i18n/index.ts","./src/libs/storage-providers/index.ts","./src/libs/storage-providers/providers/appwritestorageprovider.tsx","./src/libs/storage-providers/providers/basestorageprovider.tsx","./src/libs/storage-providers/providers/firebasestorageprovider.tsx","./src/libs/tabs/context.ts","./src/libs/tabs/index.tsx","./src/libs/tabs/tab-panel.tsx","./src/libs/tabs/tabs-header.tsx","./src/libs/tabs/tabs.tsx","./src/libs/tabs/use-tabs.tsx","./src/pages/default/404.tsx","./src/pages/default/accountold.jsx","./src/pages/default/defaultpage.tsx","./src/pages/default/defaultpaperbasepage.jsx","./src/pages/default/settings.tsx","./src/pages/default/signin.jsx","./src/pages/default/profile.tsx","./src/pages/default/account/index.tsx","./src/pages/default/account/components/debug-card.tsx","./src/pages/default/account/components/password-card.tsx","./src/pages/default/account/components/profile-card.tsx","./src/pages/default/test/filters-page.tsx","./src/pages/default/test/translations.tsx","./src/pages/default/test/auth-providers/index.tsx","./src/pages/default/test/data/index.tsx","./src/pages/default/test/data-sources/index.tsx","./src/pages/default/test/file-uploads/index.tsx","./src/pages/default/test/file-uploads/_components/image-uploader-card.tsx","./src/pages/default/test/file-uploads/_components/simple-cropper.tsx","./src/pages/default/test/forms/index.tsx","./src/routes/approutes.tsx","./src/routes/defaultroutes.tsx","./src/routes/index.ts","./src/schemas/dummy.ts","./src/schemas/external-recipe.ts","./src/schemas/index.ts","./src/schemas/issue.ts","./src/sections/default/issue-dialog.tsx","./src/theme/paperbase/theme.jsx","./src/utils/create-guid.ts","./src/utils/generate-test-data.ts","./src/utils/is-equal.ts","./src/utils/random-name-generator.ts","./src/utils/images/create-thumbnail.ts","./src/utils/images/get-next-filename.ts","./src/utils/images/resize-image.ts","./src/utils/images/save-external-image.ts"],"version":"5.6.3"} \ No newline at end of file