Compare commits

..

No commits in common. "364c67f3ad52c61f728a32dd299f7eafd5324b18" and "4e1ab7e9c2ef30f70d7e92cb624cc2bbe2a2ec4c" have entirely different histories.

12 changed files with 79 additions and 456 deletions

View File

@ -21,7 +21,6 @@
"apexcharts": "^4.5.0",
"dayjs": "^1.11.13",
"es-toolkit": "^1.34.1",
"lodash.debounce": "^4.0.8",
"minimal-shared": "^1.0.7",
"react": "^19.1.0",
"react-apexcharts": "^1.7.0",
@ -31,7 +30,6 @@
},
"devDependencies": {
"@eslint/js": "^9.23.0",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^22.14.0",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.1",
@ -2197,23 +2195,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash.debounce": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz",
"integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/node": {
"version": "22.14.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
@ -5144,12 +5125,6 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

View File

@ -39,7 +39,6 @@
"apexcharts": "^4.5.0",
"dayjs": "^1.11.13",
"es-toolkit": "^1.34.1",
"lodash.debounce": "^4.0.8",
"minimal-shared": "^1.0.7",
"react": "^19.1.0",
"react-apexcharts": "^1.7.0",
@ -49,7 +48,6 @@
},
"devDependencies": {
"@eslint/js": "^9.23.0",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^22.14.0",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.1",

View File

@ -1,230 +0,0 @@
[
{
"id": 1,
"name": "example1.pdf",
"addedWhen": "2024-01-12T10:00:00.000Z",
"addedWho": "TekH",
"attributes": [
{
"name": "invoiceNumber",
"serilizedValue": "INV-20250714-001"
},
{
"name": "customerName",
"serilizedValue": "John Doe"
},
{
"name": "startDate",
"serilizedValue": "2025-07-01"
},
{
"name": "endDate",
"serilizedValue": "2025-07-14"
},
{
"name": "minAmount",
"serilizedValue": "100.50"
},
{
"name": "maxAmount",
"serilizedValue": "1000.00"
},
{
"name": "taxIncluded",
"serilizedValue": "true"
},
{
"name": "createdAt",
"serilizedValue": "2025-07-10"
},
{
"name": "deliveryTime",
"serilizedValue": "15:30:00"
},
{
"name": "lastUpdated",
"serilizedValue": "2025-07-14T10:45:00"
}
]
},
{
"id": 2,
"name": "example2.pdf",
"addedWhen": "2024-02-03T09:30:00.000Z",
"addedWho": "bob",
"changedWhen": "2024-03-15T12:00:00.000Z",
"changedWho": "KammM",
"attributes": [
{
"name": "invoiceNumber",
"serilizedValue": "INV-20250701-007"
},
{
"name": "customerName",
"serilizedValue": "Jane Smith"
},
{
"name": "startDate",
"serilizedValue": "2025-06-01"
},
{
"name": "endDate",
"serilizedValue": "2025-06-30"
},
{
"name": "minAmount",
"serilizedValue": "250.00"
},
{
"name": "maxAmount",
"serilizedValue": "850.75"
},
{
"name": "taxIncluded",
"serilizedValue": "false"
},
{
"name": "createdAt",
"serilizedValue": "2025-06-15"
},
{
"name": "deliveryTime",
"serilizedValue": "09:00:00"
},
{
"name": "lastUpdated",
"serilizedValue": "2025-06-30T14:15:00"
}
]
},
{
"id": 3,
"name": "document1.docx",
"addedWhen": "2023-12-20T14:45:00.000Z",
"addedWho": "SchreiberM",
"attributes": [
{
"name": "invoiceNumber",
"serilizedValue": "INV-20250620-043"
},
{
"name": "customerName",
"serilizedValue": "Max Mustermann"
},
{
"name": "startDate",
"serilizedValue": "2025-05-01"
},
{
"name": "endDate",
"serilizedValue": "2025-05-15"
},
{
"name": "maxAmount",
"serilizedValue": "600.00"
},
{
"name": "taxIncluded",
"serilizedValue": "true"
},
{
"name": "createdAt",
"serilizedValue": "2025-05-02"
},
{
"name": "lastUpdated",
"serilizedValue": "2025-05-15T11:00:00"
}
]
},
{
"id": 4,
"name": "spreadsheet1.xlsx",
"addedWhen": "2024-05-01T08:15:00.000Z",
"addedWho": "KammM",
"changedWhen": "2024-06-10T16:20:00.000Z",
"changedWho": "OlgunR",
"attributes": [
{
"name": "invoiceNumber",
"serilizedValue": "INV-20250410-021"
},
{
"name": "startDate",
"serilizedValue": "2025-04-01"
},
{
"name": "endDate",
"serilizedValue": "2025-04-30"
},
{
"name": "minAmount",
"serilizedValue": "150.99"
},
{
"name": "maxAmount",
"serilizedValue": "999.99"
},
{
"name": "createdAt",
"serilizedValue": "2025-04-15"
},
{
"name": "deliveryTime",
"serilizedValue": "17:45:00"
},
{
"name": "lastUpdated",
"serilizedValue": "2025-04-30T18:30:00"
}
]
},
{
"id": 5,
"name": "report.docx",
"addedWhen": "2024-04-17T11:25:00.000Z",
"addedWho": "SchreiberM",
"attributes": [
{
"name": "invoiceNumber",
"serilizedValue": "INV-20250305-099"
},
{
"name": "customerName",
"serilizedValue": "Ali Veli"
},
{
"name": "startDate",
"serilizedValue": "2025-03-01"
},
{
"name": "endDate",
"serilizedValue": "2025-03-20"
},
{
"name": "minAmount",
"serilizedValue": "75.00"
},
{
"name": "maxAmount",
"serilizedValue": "500.00"
},
{
"name": "taxIncluded",
"serilizedValue": "false"
},
{
"name": "createdAt",
"serilizedValue": "2025-03-02"
},
{
"name": "deliveryTime",
"serilizedValue": "08:20:00"
},
{
"name": "lastUpdated",
"serilizedValue": "2025-03-20T09:30:00"
}
]
}
]

View File

@ -249,10 +249,23 @@ export const _attributes: Attribute[] = [
// ----------------------------------------------------------------------
function base64ToUint8Array(base64: string): Uint8Array {
const binaryString = atob(base64); // Decode base64 to binary string
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
export const _documents: Doc[] = [
{
id: 1,
name: "example1.pdf",
data: base64ToUint8Array(_base64.example1_pdf),
addedWhen: new Date("2024-01-12T10:00:00Z"),
addedWho: "TekH",
attributes: [
@ -307,10 +320,12 @@ export const _documents: Doc[] = [
type: 'DATETIME'
}
]
},
{
id: 2,
name: "example2.pdf",
data: base64ToUint8Array(_base64.example2_pdf),
addedWhen: new Date("2024-02-03T09:30:00Z"),
addedWho: "bob",
changedWhen: new Date("2024-03-15T12:00:00Z"),
@ -371,6 +386,7 @@ export const _documents: Doc[] = [
{
id: 3,
name: "document1.docx",
data: base64ToUint8Array(_base64.document1_docx),
addedWhen: new Date("2023-12-20T14:45:00Z"),
addedWho: "SchreiberM",
attributes: [
@ -419,6 +435,7 @@ export const _documents: Doc[] = [
{
id: 4,
name: "spreadsheet1.xlsx",
data: base64ToUint8Array(_base64.spreadsheet1_xlsx),
addedWhen: new Date("2024-05-01T08:15:00Z"),
addedWho: "KammM",
changedWhen: new Date("2024-06-10T16:20:00Z"),
@ -469,6 +486,7 @@ export const _documents: Doc[] = [
{
id: 5,
name: "report.docx",
data: base64ToUint8Array(_base64.report_docx),
addedWhen: new Date("2024-04-17T11:25:00Z"),
addedWho: "SchreiberM",
attributes: [
@ -533,32 +551,3 @@ export const _documents: Doc[] = [
}))
}))
.map(doc => Doc.map(doc));
function base64ToUint8Array(base64: string): Uint8Array {
const binaryString = atob(base64); // Decode base64 to binary string
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
export function _genFile(name: string): Uint8Array | undefined {
switch (name) {
case "example1.pdf":
return base64ToUint8Array(_base64.example1_pdf);
case "example2.pdf":
return base64ToUint8Array(_base64.example2_pdf);
case "document1.docx":
return base64ToUint8Array(_base64.document1_docx);
case "spreadsheet1.xlsx":
return base64ToUint8Array(_base64.spreadsheet1_xlsx);
case "report.docx":
return base64ToUint8Array(_base64.report_docx);
default:
return undefined;
}
}

View File

@ -1,17 +0,0 @@
type JsonViewerProps = {
data: any;
};
export function JsonViewer({ data }: JsonViewerProps) {
return (
<pre style={{
backgroundColor: '#f4f4f4',
padding: '1rem',
borderRadius: '5px',
width: '100%',
height: '100%'
}}>
{JSON.stringify(data)}
</pre>
);
}

View File

@ -1,15 +1,27 @@
import { useEffect, useState } from 'react';
import { _posts } from 'src/_mock';
import { CONFIG } from 'src/config-global';
import { Doc, getDocuments } from 'src/services/document-service';
import { DocSearchView } from 'src/sections/document/view';
// ----------------------------------------------------------------------
export default function Page() {
const [docs, setDocs] = useState<Doc[]>([]);
useEffect(() => {
getDocuments({}).then((res) => {
setDocs(res);
});
}, []);
return (
<>
<title>{`Document Search - ${CONFIG.appName}`}</title>
<DocSearchView />
<DocSearchView docs={docs} />
</>
);
}

View File

@ -1,42 +1,27 @@
import debounce from 'lodash.debounce';
import { useState, useMemo } from 'react';
import { useState } from 'react';
import TextField from '@mui/material/TextField';
// ----------------------------------------------------------------------
type NumFilterProps = {
type BoolFilterProps = {
label: string;
onChange?: (value: string) => void;
}
const isNumbers = (str: string) => /^[0-9]*$/.test(str);
export function IntFilter({ label, onChange }: NumFilterProps) {
export function IntFilter({ label }: BoolFilterProps) {
const [val, setVal] = useState("");
const onInputChange = (event: any) => {
const value = event.target.value;
if (isNumbers(value)) {
setVal(value);
debouncedChange(value);
}
};
const debouncedChange = useMemo(() =>
debounce((value: string) => {
onChange?.(value);
}, 1000)
, [onChange]);
return <TextField label={label} value={val} onChange={onInputChange} variant="filled" />;
}
export function DecimalFilter({ label, onChange }: NumFilterProps) {
const debouncedChange = useMemo(() =>
debounce((value: string) => {
onChange?.(value);
}, 1000)
, [onChange]);
return <TextField type="number" label={label} variant="filled" onChange={e => debouncedChange(e.target.value)} />;
export function DecimalFilter({ label }: BoolFilterProps) {
return <TextField type="number" label={label} variant="filled" />;
}

View File

@ -1,20 +1,10 @@
import { useMemo } from 'react';
import debounce from 'lodash.debounce';
import TextField from '@mui/material/TextField';
// ----------------------------------------------------------------------
type TextFilterProps = {
label: string;
onChange?: (value: string) => void;
}
export function TextFilter({ label, onChange }: TextFilterProps) {
const debouncedChange = useMemo(() =>
debounce((value: string) => {
onChange?.(value);
}, 1000)
, [onChange]);
return <TextField label={label} variant="filled" onChange={e => debouncedChange(e.target.value)} />;
export function TextFilter({ label }: TextFilterProps) {
return <TextField label={label} variant="filled" />;
}

View File

@ -26,8 +26,8 @@ const getMimeType = (format: FileFormat): string => {
};
type DocFullViewProps = {
data: Uint8Array | Promise<Uint8Array>;
format: FileFormat;
data: Uint8Array;
format?: FileFormat;
open: boolean;
handleClose: () => void;
}
@ -46,31 +46,16 @@ export default function DocFullView({ data, format, open, handleClose }: DocFull
const [objectUrl, setObjectUrl] = useState<string | null>(null);
useEffect(() => {
let isMounted = true;
let url: string | null = null;
if (!data || !format)
return undefined;
const processData = async () => {
try {
const resolvedData = await data;
const mimeType = getMimeType(format);
const blob = new Blob([resolvedData], { type: mimeType });
url = URL.createObjectURL(blob);
if (isMounted) {
const blob = new Blob([data], { type: mimeType });
const url = URL.createObjectURL(blob);
setObjectUrl(url);
}
} catch (err) {
console.error('Data resolution error:', err);
}
};
processData();
return () => {
isMounted = false;
if (url) {
URL.revokeObjectURL(url);
}
};
}, [data, format]);

View File

@ -6,8 +6,8 @@ import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Pagination from '@mui/material/Pagination';
import { Doc } from 'src/services/document-service';
import { DashboardContent } from 'src/layouts/dashboard';
import docService, { Doc } from 'src/services/document-service';
import { Attribute, getAttributes } from 'src/services/attribute-service';
import { Iconify } from 'src/components/iconify';
@ -20,7 +20,11 @@ import { DecimalFilter, IntFilter } from '../num-filter';
import { DateFilter, DateTimeFilter, TimeFilter } from '../date-filter';
// ----------------------------------------------------------------------
export function DocSearchView() {
type Props = {
docs: Doc[];
};
export function DocSearchView({ docs }: Props) {
const [sortBy, setSortBy] = useState('latest');
const [filters, setFilters] = useState<Attribute[]>([])
@ -37,43 +41,6 @@ export function DocSearchView() {
const [openCreateFilterModal, setOpenCreateFilterModal] = useState(false);
const [docs, setDocs] = useState<Doc[]>([]);
useEffect(() => {
docService.get({}).then((res) => {
setDocs(res);
});
}, []);
//#region attributes
const [attributes, setAttributes] = useState<Record<string, string>>({});
useEffect(() => {
docService.getByAttribute(attributes).then(setDocs);
}, [attributes]);
function setAttribute(name: string, serilizedValue: string) {
setAttributes(prev => ({
...prev,
[name]: serilizedValue
}));
}
function removeAttribute(name: string) {
setAttributes(prev => {
const { [name]: _, ...rest } = prev;
return rest;
});
}
function updateAttribute(name: string, serilizedValue?: string | null) {
if (serilizedValue)
setAttribute(name, serilizedValue);
else
removeAttribute(name);
}
//#endregion
//#region example components
// <Box
// sx={{
@ -131,13 +98,13 @@ export function DocSearchView() {
filterComp = <BoolFilter label={filter.label ?? filter.name} />
break;
case 'INTEGER':
filterComp = <IntFilter label={filter.label ?? filter.name} onChange={value => updateAttribute(filter.name, value)} />
filterComp = <IntFilter label={filter.label ?? filter.name} />
break;
case 'DECIMAL':
filterComp = <DecimalFilter label={filter.label ?? filter.name} onChange={value => updateAttribute(filter.name, value)} />
filterComp = <DecimalFilter label={filter.label ?? filter.name} />
break;
case 'VARCHAR':
filterComp = <TextFilter label={filter.label ?? filter.name} onChange={value => updateAttribute(filter.name, value)} />
filterComp = <TextFilter label={filter.label ?? filter.name} />
break;
case 'DATE':
filterComp = <DateFilter label={filter.label ?? filter.name} />

View File

@ -1,4 +1,6 @@
import { _documents, _genFile } from "src/_mock"
import { _documents } from "src/_mock"
import { Type } from "./attribute-service";
export type FileFormat =
| 'pdf'
@ -25,6 +27,7 @@ const validExtensions: FileFormat[] = [
type DocAttribute = {
name: string;
serilizedValue: string;
type: Type;
}
export class Doc {
@ -37,16 +40,13 @@ export class Doc {
id!: number;
name!: string;
data!: Uint8Array;
addedWhen!: Date;
addedWho!: string;
changedWhen?: Date;
changedWho?: string;
attributes: Array<DocAttribute> = [];
get data(): Promise<Uint8Array> {
return Promise.resolve(_genFile(this.name)!);
}
getChangedInfo(separator: string = " | "): string | null {
const who = this.changedWho?.trim();
const when = this.changedWhen?.toLocaleDateString('de-DE');
@ -58,7 +58,7 @@ export class Doc {
return [who, when].filter(Boolean).join(separator);
}
get extension(): FileFormat {
get extension(): FileFormat | undefined {
const parts = this.name.split('.');
if (parts.length > 1 && parts[parts.length - 1].trim() !== '') {
const ext = parts[parts.length - 1].toLowerCase();
@ -66,7 +66,7 @@ export class Doc {
if (validExtensions.includes(ext as FileFormat))
return ext as FileFormat;
}
throw new Error(`Invalid or missing file extension in filename: "${this.name}". Supported extensions are: ${validExtensions.join(', ')}.`);
return undefined;
}
get iconSrc(): string {
@ -76,12 +76,10 @@ export class Doc {
export type DocQuery = {
id?: number | undefined,
name?: string | undefined,
attributes?: Record<string, string>
name?: string | undefined
}
class DocService {
get(query: DocQuery | undefined = undefined): Promise<Doc[]> {
export function getDocuments(query: DocQuery | undefined = undefined): Promise<Doc[]> {
let documents = _documents;
if (query?.id)
@ -90,25 +88,13 @@ class DocService {
if (query?.name)
documents = documents.filter(d => d.name === query.name);
for (const name in query?.attributes) {
const attr = query.attributes[name];
documents = documents.filter(d => d.attributes.find(a => a.name === name)?.serilizedValue.toLowerCase().includes(attr.toLowerCase()));
}
return Promise.resolve(documents);
}
getById(id: number): Promise<Doc[]> {
return this.get({ id: id });
export function getDocumentById(id: number): Promise<Doc[]> {
return getDocuments({ id: id });
}
getByName(name: string): Promise<Doc[]> {
return this.get({ name: name });
export function getDocumentByName(name: string): Promise<Doc[]> {
return getDocuments({ name: name });
}
getByAttribute(attributes: Record<string, string>): Promise<Doc[]> {
return this.get({ attributes: attributes });
}
}
export default new DocService();

View File

@ -586,18 +586,6 @@
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/lodash.debounce@^4.0.9":
version "4.0.9"
resolved "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz"
integrity sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.17.20"
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz"
integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==
"@types/node@^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node@^22.14.0":
version "22.14.0"
resolved "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz"
@ -2033,11 +2021,6 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"