diff --git a/README.md b/README.md index 6b5968c..474f2c1 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,39 @@

Fixed Asset Management System

-

Nest.js & Angular | Cloud-based Web Application for Asset Management

-

Current version: V1.0 Branch

+

Nest.js & Angular SSR | Cloud-based Web Application for Asset Management

+

Current version: V1.5 Branch

+

Next version function: Unit test and Excel Data Export

+

If you find this project helpful, please click ⭐ Star! This helps more people discover it.

🌟 Project Overview

-

This is a full-stack Fixed Asset Management System built with Spring Boot and Vue.js, designed for managing fixed assets within facilities. The system includes:

+

This is a full-stack Fixed Asset Management System built with Nest.js and Angular SSR, designed for managing fixed assets within facilities. The system includes:

The system is cloud-based, significantly reducing IT infrastructure costs and improving usability, with no installation required.

- + +

Tax Information

+Dashboard Overview + +

Asset Form View

+Asset List View + +

User Info

+Maintenance Records + +

Dashboard

+Write-Off Management +

🚀 Key Features

📈 Business Impact

diff --git a/frontend/src/app/layout/menu/menu.component.ts b/frontend/src/app/layout/menu/menu.component.ts index cd729b1..e126b73 100644 --- a/frontend/src/app/layout/menu/menu.component.ts +++ b/frontend/src/app/layout/menu/menu.component.ts @@ -41,6 +41,8 @@ export class MenuComponent implements OnInit{ }) } + year: number = new Date().getFullYear() + activeParent: any = null activeChild: any = null expandedMenus: any[] = [] diff --git a/frontend/src/app/state/CartsStoreService.ts b/frontend/src/app/state/CartsStoreService.ts deleted file mode 100644 index 3e91f1a..0000000 --- a/frontend/src/app/state/CartsStoreService.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Injectable } from '@angular/core' -import { BehaviorSubject } from 'rxjs' -import { CartStateFace, Promotion, CartFace } from './interfaceType' - -@Injectable({ - providedIn: 'root' -}) -export class CartsStoreService { - private initialState: CartStateFace = { list: [] } - private cartSubject = new BehaviorSubject< CartStateFace>(this.initialState) - carts$ = this.cartSubject.asObservable() - - get carts(): CartFace[] { - return this.cartSubject.value.list - } - - setPromotions(carts: CartFace[]): void { - this.cartSubject.next({ list: carts }) - } - - addPromotion(promotion: CartFace): void { - const updatedList = [...this.carts, promotion] - this.cartSubject.next({ list: updatedList }) - } - - updatePromotion(productId: number, cartSubject: Partial): void { - const updatedList = this.carts.map(promo => - promo.productId === productId ? { ...promo, ...cartSubject } : promo - ) - this.cartSubject.next({ list: updatedList }) - } - - deletePromotion(productId: number): void { - const updatedList = this.carts.filter(promo => promo.productId !== productId); - this.cartSubject.next({ list: updatedList }) - } -} \ No newline at end of file diff --git a/frontend/src/app/state/PromotionStoreService.ts b/frontend/src/app/state/PromotionStoreService.ts deleted file mode 100644 index b9f619c..0000000 --- a/frontend/src/app/state/PromotionStoreService.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Injectable } from '@angular/core' -import { BehaviorSubject } from 'rxjs' -import { Promotion, PromotionStateFace } from './interfaceType' -import { getApi } from '../tool/http/httpRequest'; - -@Injectable({ - providedIn: 'root' -}) -export class PromotionStoreService { - private initialState: PromotionStateFace = { list: [] } - - private promotionsSubject = new BehaviorSubject(this.initialState); - promotions$ = this.promotionsSubject.asObservable() - - get promotions(): Promotion[] { - return this.promotionsSubject.value.list - } - - setPromotions(promotions: Promotion[]): void { - this.promotionsSubject.next({ list: promotions }) - } - - addPromotion(promotion: Promotion): void { - const updatedList = [...this.promotions, promotion] - this.promotionsSubject.next({ list: updatedList }) - } - - deletePromotion(id: number): void { - const updatedList = this.promotions.filter(promo => promo.id !== id); - this.promotionsSubject.next({ list: updatedList }) - } - - async loadPromotionData(): Promise { - const res = await getApi('/base/promotion/store/list') - this.setPromotions(res.data) - } -} \ No newline at end of file diff --git a/frontend/src/app/state/interfaceType.ts b/frontend/src/app/state/interfaceType.ts deleted file mode 100644 index bbbd409..0000000 --- a/frontend/src/app/state/interfaceType.ts +++ /dev/null @@ -1,82 +0,0 @@ -export interface PromotionDepartmentItems { - id: number - promotionId: number - deptId: number - deptIdCode: string - deptIdName: string - promotionName: string - promotionCode: string - discountAmount: number - discountType: string -} - -export interface PromotionTypeItems { - id: number - promotionId: number - typeId: number - typeCode: string - typeName: string - promotionName: string - promotionCode: string - discountAmount: number - discountType: string -} - -export interface Promotion { - id: number - promotionName: string - promotionCode: string - promotionType: string - description: string - periodStart: string - periodEnd: string - online: number - inStore: number - member: number - afterBeforeTax: number - discount: number - discountType: string - allOneDiscount: number - couponRequest: number - couponMainCode: string - remark: string - promotionDepartmentItems: PromotionDepartmentItems[] - promotionTypeItems: PromotionTypeItems[] -} - -export interface PromotionStateFace { - list: Promotion[] -} - -export const promotionState: PromotionStateFace = { - list: [] -} - - -// Carts - -export interface CartFace { - productId: number - productName: string - productCode: string - qty: number - price: string - taxRate: number - taxAmount: number - finalPrice: number - typeId: number - typeName: string - deptId: number - deptName: string - unit: string - itemCode: string - brandName: string -} - -export interface CartStateFace { - list: CartFace[] -} - -export const cartState: CartStateFace = { - list: [] -} diff --git a/frontend/src/state/AccessGuard.ts b/frontend/src/state/AccessGuard.ts new file mode 100644 index 0000000..a0a1db0 --- /dev/null +++ b/frontend/src/state/AccessGuard.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { UserStoreService } from './user.service'; +import { findMenuItemByPath } from '../app/component/tool-function'; +import { Observable } from 'rxjs'; +import { map, take } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class AccessGuard implements CanActivate { + constructor( + private userStoreService: UserStoreService, + private router: Router + ) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ) { + if (!this.userStoreService.isAuthenticated()) { + this.router.navigate(['/login']) + return false + } + + const urlPath = route.url[0]?.path || '' + + return this.userStoreService.menuRole$.pipe( + take(1), // Only take one + map((menuData: any) => { + const hasAccess = !!findMenuItemByPath(menuData, urlPath); + if (!hasAccess) { + this.router.navigate(['/']) + } + return hasAccess + }) + ) + } +} \ No newline at end of file diff --git a/frontend/src/state/globalData.service.ts b/frontend/src/state/globalData.service.ts new file mode 100644 index 0000000..232cb62 --- /dev/null +++ b/frontend/src/state/globalData.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core' +import { BehaviorSubject } from 'rxjs' +import { getApiWithAuth } from '../tool/httpRequest-auth' + +@Injectable({ providedIn: 'root' }) +export class GlobalDataService { + private placeListData: any[] = [] + private placeListDataSubject = new BehaviorSubject(this.placeListData) + placeListData$ = this.placeListDataSubject.asObservable() + + setPlaceListData(data: any[]) { + this.placeListDataSubject.next(data) + } + + async loadPlaceListData() { + const dada = await getApiWithAuth('/base/location/getAll') + this.setPlaceListData(dada) + } + + async loadAllGlobalData() { + await this.loadPlaceListData() + } +} \ No newline at end of file diff --git a/frontend/src/state/interface.ts b/frontend/src/state/interface.ts index 6a760d5..e790700 100644 --- a/frontend/src/state/interface.ts +++ b/frontend/src/state/interface.ts @@ -2,7 +2,7 @@ export interface UserInfo { _id?: string username: string accessToken: string - roleIds?: number[], + roles?: number[], deptId?: number, avatarBase64?: string email: string diff --git a/frontend/src/state/LocalStorageService.ts b/frontend/src/state/localStorage.service.ts similarity index 100% rename from frontend/src/state/LocalStorageService.ts rename to frontend/src/state/localStorage.service.ts diff --git a/frontend/src/state/user.service.ts b/frontend/src/state/user.service.ts index 5c20b05..52efab8 100644 --- a/frontend/src/state/user.service.ts +++ b/frontend/src/state/user.service.ts @@ -2,18 +2,15 @@ import { inject, Injectable } from '@angular/core' import { BehaviorSubject } from 'rxjs' import { UserInfo } from './interface' import { Router } from '@angular/router' -import { LocalStorageService } from './LocalStorageService' -import { HttpService } from '../tool/HttpService' +import { LocalStorageService } from './localStorage.service' import { getApiWithAuth, postApiWithAuth } from '../tool/httpRequest-auth' -// import { CartStateFace, Promotion, CartFace } from './interfaceType' @Injectable({ providedIn: 'root' }) export class UserStoreService { constructor( - private localStorageService: LocalStorageService, - private httpService: HttpService + private localStorageService: LocalStorageService ) {} router = inject(Router) @@ -24,18 +21,21 @@ export class UserStoreService { accessToken: '', avatarBase64: '', deptId: 0, - roleIds: [], + roles: [], roleLists: [], email: '', loginRecords: [] } private userMenu: any[] = [] + private userMenuRole: any[] = [] private tokenSubject = new BehaviorSubject(this.accessToken) private userSubject = new BehaviorSubject(this.initialState) private menuSubject = new BehaviorSubject(this.userMenu) + private menuRoleSubject = new BehaviorSubject(this.userMenu) user$ = this.userSubject.asObservable() token$ = this.tokenSubject.asObservable() menu$ = this.menuSubject.asObservable() + menuRole$ = this.menuRoleSubject.asObservable() get user(): UserInfo { return this.userSubject.value @@ -72,6 +72,10 @@ export class UserStoreService { setMenu(menu: any[]): void { this.menuSubject.next(menu) } + + setMenuRole(menuRole: any[]): void { + this.menuRoleSubject.next(menuRole) + } logout(): void { @@ -81,12 +85,14 @@ export class UserStoreService { accessToken: '', avatarBase64: '', deptId: 0, - roleIds: [], + roles: [], roleLists: [], email: '', loginRecords: [] }) + this.menuSubject.next([]) + this.menuRoleSubject.next([]) localStorage.clear() this.tokenSubject.next('') this.toLoginPage() @@ -122,4 +128,12 @@ export class UserStoreService { }) } + + async loadMenuRoles() { + this.user$.subscribe(async user => { + const data = await postApiWithAuth('/sys/role/list-permission', { roleIds: user.roles }) + this.setMenuRole(data) + }) + } + } \ No newline at end of file diff --git a/frontend/src/styles.css b/frontend/src/styles.css index d68a203..302891e 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1,2 +1,9 @@ /* You can add global styles to this file, and also import other style files */ -@import "tailwindcss"; \ No newline at end of file +@import "tailwindcss"; + +html, body { + margin: 0; + padding: 0; + height: 100%; + background-color: #ededed; /* Match the background color of the outer div */ +} \ No newline at end of file diff --git a/frontend/src/tool/HttpService.ts b/frontend/src/tool/HttpService.ts index 4dd2cf5..2fd6ead 100644 --- a/frontend/src/tool/HttpService.ts +++ b/frontend/src/tool/HttpService.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core' import { environment } from '../environments/environment.development' -import { LocalStorageService } from '../state/LocalStorageService' +import { LocalStorageService } from '../state/localStorage.service' import { resolve } from 'path' import { rejects } from 'assert' @Injectable({ diff --git a/frontend/src/tool/excel-helper.ts b/frontend/src/tool/excel-helper.ts new file mode 100644 index 0000000..ff449df --- /dev/null +++ b/frontend/src/tool/excel-helper.ts @@ -0,0 +1,148 @@ +// import * as XLSXS from 'xlsx-style' +import * as XLSX from 'xlsx' +import { saveAs } from 'file-saver' +import { NzUploadFile } from 'ng-zorro-antd/upload' + +export function readExcelFile(file: any) { + return new Promise((resolve: any, reject: any) => { + // check if real file + const realFile: File | undefined = file.originFileObj || file.raw || file + if (!(realFile instanceof Blob)) { + return reject(new Error('Invalid file format. File must be a Blob or File.')) + } + + // get file type / sub name + const extension = realFile.name.split('.').pop()?.toLowerCase() + const allowedTypes = ['xlsx', 'xls', 'csv'] + + if (!allowedTypes.includes(extension!)) { + return reject({ message: 'Format not supported! Only Excel files are allowed.' }); + } + + const reader: FileReader = new FileReader() + const result: any = [] + + reader.onload = (e: any) => { + const data = e.target?.result + const wb = XLSX.read(data, { type: 'array' }) + + wb.SheetNames.forEach((sheetName: string) => { + result.push({ + sheetName, + sheet: XLSX.utils.sheet_to_json(wb.Sheets[sheetName]) + }) + }) + + resolve(result.length > 1 ? result[0] : result[0].sheet) + }; + + reader.onerror = (error: any) => reject(error) + + // reading doc + reader.readAsArrayBuffer(realFile) + }) +} + + +export function formatJson (header: any, filterVal: any, jsonData: any) { + const updatedArray = jsonData.map((obj: any) => { + const newObj: any = {} + + header.forEach((oldKey: string, index: number) => { + + const newKey = filterVal[index] + console.log(obj[oldKey]) + if (obj[oldKey] !== undefined) { + newObj[newKey] = obj[oldKey] + } + }) + return newObj + }) + + return updatedArray + + /* return jsonData.map((v: any)=> { + const obj: any = {} + header.forEach((h, i) => { + const anyD: any = [filterVal[i]] + + const newData: any = v[h] + console.log(newData, 'data') + obj[anyD] = newData + + }) + return obj + }) */ +} + +export function formatJsonToSheet(filterVal: any, jsonData: any) { + return jsonData.map((v: any) => filterVal.map((j: any) => { + return v[j] + })) +} + + +export function downloadTempExcelFile( + excelHeader: any, + fileName: string, + excelStyle?: any, + headerColSeetting? : any + ) { + const ws = XLSX.utils.aoa_to_sheet([excelHeader]) + if (excelStyle) { + if (excelStyle) { + for (const [key] of Object.entries(ws)) { + if (key !== '!cols' && key !== '!ref') { + ws[key].s = excelStyle + } + } + } + if(headerColSeetting) { + ws['!cols'] = headerColSeetting['!cols'] + } + } + XLSX.utils.sheet_add_aoa(ws, [], { origin: 'A2' }) + let wb = XLSX.utils.book_new() + XLSX.utils.book_append_sheet(wb, ws, 'Sheet1') + const wopts: any = { bookType: 'xlsx', bookSST: false , type: 'binary' } + const fileEx = XLSX.write(wb, { ...wopts }) + saveAs(new Blob([s2ab(fileEx)],{type:""}), fileName) + } + export function saveJsonToExcel( + headers: any, + data: any, + excelHeader: any, + fileName: string, + excelStyle?: any, + headerColSeetting? : any + ) { + let dataSet = formatJsonToSheet(headers, data) + const ws = XLSX.utils.aoa_to_sheet([excelHeader]) + + if (excelStyle) { + if (excelStyle) { + for (const [key] of Object.entries(ws)) { + if (key !== '!cols' && key !== '!ref') { + ws[key].s = excelStyle + } + } + } + if(headerColSeetting) { + ws['!cols'] = headerColSeetting['!cols'] + } + } + console.log(ws) + XLSX.utils.sheet_add_aoa(ws, dataSet, { origin: 'A2' }) + let wb = XLSX.utils.book_new() + XLSX.utils.book_append_sheet(wb, ws, 'Sheet1') + const wopts: any = { bookType: 'xlsx', bookSST: false , type: 'binary' } + const fileEx = XLSX.write(wb, { ...wopts }) + saveAs(new Blob([s2ab(fileEx)],{type:""}), fileName) +} + +function s2ab(s: any) { + var buf = new ArrayBuffer(s.length) + var view = new Uint8Array(buf) + for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF + return buf +} \ No newline at end of file diff --git a/frontend/src/tool/httpRequest-auth.ts b/frontend/src/tool/httpRequest-auth.ts index 0dc8669..c7d2331 100644 --- a/frontend/src/tool/httpRequest-auth.ts +++ b/frontend/src/tool/httpRequest-auth.ts @@ -1,14 +1,22 @@ import { environment } from "../environments/environment.development" - const contentType = 'application/json;charset=UTF-8' +// localStorage on read in browser +const getAccessToken = () => { + if (typeof window !== 'undefined' && localStorage) { + return localStorage.getItem('accessToken') || '' + } + return '' +} + export const postApiWithAuth = async (url: string, data: any) => { const requestHeaders = new Headers() requestHeaders.append('Content-Type', contentType) - if (localStorage && localStorage.getItem('accessToken')) { - const accessToken = localStorage.getItem('accessToken') - requestHeaders.append('Authorization', accessToken || '') + + const accessToken = getAccessToken() + if (accessToken) { + requestHeaders.append('Authorization', accessToken) } const finalUrl = `${environment.apiUrl}${url}` @@ -17,62 +25,57 @@ export const postApiWithAuth = async (url: string, data: any) => { headers: requestHeaders, body: JSON.stringify(data) }) - const content: any = await rawResponse.json() - return content + return rawResponse.json() } export const getApiWithAuth = async (url: string) => { - const finalUrl = `${environment.apiUrl}${url}` - const requestHeaders = new Headers() requestHeaders.append('Content-Type', contentType) - if (localStorage && localStorage.getItem('accessToken')) { - const accessToken = localStorage.getItem('accessToken') - requestHeaders.append('Authorization', accessToken || '') + + const accessToken = getAccessToken() + if (accessToken) { + requestHeaders.append('Authorization', accessToken) } + const finalUrl = `${environment.apiUrl}${url}` const rawResponse = await fetch(finalUrl, { method: 'GET', headers: requestHeaders }) - const content: any = await rawResponse.json() - return content + return rawResponse.json() } export const deleteApiWithAuth = async (url: string) => { - const finalUrl = `${environment.apiUrl}${url}` - const requestHeaders = new Headers() requestHeaders.append('Content-Type', contentType) - if (localStorage && localStorage.getItem('accessToken')) { - const accessToken = localStorage.getItem('accessToken') - requestHeaders.append('Authorization', accessToken || '') + + const accessToken = getAccessToken() + if (accessToken) { + requestHeaders.append('Authorization', accessToken) } + const finalUrl = `${environment.apiUrl}${url}` const rawResponse = await fetch(finalUrl, { method: 'DELETE', headers: requestHeaders }) - const content: any = await rawResponse.json() - return content + return rawResponse.json() } export const putApiWithAuth = async (url: string, data: any) => { const requestHeaders = new Headers() requestHeaders.append('Content-Type', contentType) - if (localStorage && localStorage.getItem('accessToken')) { - const accessToken = localStorage.getItem('accessToken') - requestHeaders.append('Content-Type', accessToken || '') + + const accessToken = getAccessToken() + if (accessToken) { + requestHeaders.append('Authorization', accessToken) } const finalUrl = `${environment.apiUrl}${url}` const rawResponse = await fetch(finalUrl, { method: 'PUT', - headers: { - 'Content-Type': contentType - }, + headers: requestHeaders, body: JSON.stringify(data) }) - const content: any = await rawResponse.json() - return content -} \ No newline at end of file + return rawResponse.json() +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ca80f18..4e5aa82 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2089,6 +2089,11 @@ resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz" integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== +"@sphinxxxx/color-conversion@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz#03ecc29279e3c0c832f6185a5bfa3497858ac8ca" + integrity sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw== + "@tailwindcss/node@4.0.12": version "4.0.12" resolved "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.12.tgz" @@ -2306,6 +2311,11 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/file-saver@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.7.tgz#8dbb2f24bdc7486c54aa854eb414940bbd056f7d" + integrity sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A== + "@types/http-errors@*": version "2.0.4" resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz" @@ -2578,6 +2588,11 @@ accepts@~1.3.4, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +ace-builds@^1.36.2: + version "1.39.1" + resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.39.1.tgz#28af141e58fe1a0f945b98f56fe9f8aaa6245f73" + integrity sha512-HcJbBzx8qY66t9gZo/sQu7pi0wO/CFLdYn1LxQO1WQTfIkMfyc7LRnBpsp/oNCSSU/LL83jXHN1fqyOTuIhUjg== + acorn@^8.14.0, acorn@^8.8.2: version "8.14.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" @@ -2591,6 +2606,11 @@ adjust-sourcemap-loader@^4.0.0: loader-utils "^2.0.0" regex-parser "^2.2.11" +adler-32@, adler-32@~1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.3.1.tgz#1dbf0b36dda0012189a32b3679061932df1821e2" + integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -2634,6 +2654,23 @@ ajv@8.17.1, ajv@^8.0.0, ajv@^8.9.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" +ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ang-jsoneditor@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/ang-jsoneditor/-/ang-jsoneditor-4.0.1.tgz#74b0769e38e9be1a90e109f526d2f7c5272b98ba" + integrity sha512-WNO5Q7UWbkBo7q6u22PqWrUBMxFKmfg2x+LgBKGpcX2Ki7x2IlIpQIiUjSVDamscyo0bEk+a5Xv4zk5vigFdQg== + dependencies: + tslib "^2.3.0" + angular-plotly.js@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/angular-plotly.js/-/angular-plotly.js-6.0.0.tgz#1152d11b08c5daece38e3aa19d1668e8b4f981fd" @@ -2960,6 +2997,14 @@ caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz" integrity sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw== +cfb@>=0.10.0, cfb@~1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cfb/-/cfb-1.2.2.tgz#94e687628c700e5155436dac05f74e08df23bc44" + integrity sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA== + dependencies: + adler-32 "~1.3.0" + crc-32 "~1.2.0" + chalk@^4.1.0: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" @@ -3090,6 +3135,20 @@ clone@^1.0.2: resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +codepage@~1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab" + integrity sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA== + +codepage@~1.3.6: + version "1.3.8" + resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.3.8.tgz#4f2e5d7c0975de28f88498058dcb5afcab6a5f71" + integrity sha512-cjAoQW5L/TCKWRbzt/xGBvhwJKQFhcIVO0jWQtpKQx4gr9qvXNkpRfq6gSmjjA8dB2Is/DPOb7gNwqQXP7UgTQ== + dependencies: + commander "" + concat-stream "" + voc "" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" @@ -3107,6 +3166,11 @@ colorette@^2.0.10, colorette@^2.0.20: resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +colors@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" + integrity sha512-OsSVtHK8Ir8r3+Fxw/b4jS1ZLPXkV6ZxDRJQzeD7qo0SqMXWrHDM71DgYzPMHY8SFJ0Ao+nNU2p1MmwdzKqPrw== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3114,6 +3178,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@: + version "13.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" + integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + commander@^2.20.0: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" @@ -3149,6 +3218,16 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concat-stream@: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + connect-history-api-fallback@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz" @@ -3250,6 +3329,11 @@ cosmiconfig@^9.0.0: js-yaml "^4.1.0" parse-json "^5.2.0" +crc-32@, crc-32@~1.2.0, crc-32@~1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + critters@0.0.20: version "0.0.20" resolved "https://registry.yarnpkg.com/critters/-/critters-0.0.20.tgz#08ddb961550ab7b3a59370537e4f01df208f7646" @@ -3825,7 +3909,7 @@ external-editor@^3.1.0: iconv-lite "^0.4.24" tmp "^0.0.33" -fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -3841,6 +3925,11 @@ fast-glob@3.3.3, fast-glob@^3.3.2, fast-glob@^3.3.3: merge2 "^1.3.0" micromatch "^4.0.8" +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + fast-uri@^3.0.1: version "3.0.6" resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz" @@ -3860,6 +3949,11 @@ faye-websocket@^0.11.3: dependencies: websocket-driver ">=0.5.1" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" @@ -3955,6 +4049,16 @@ forwarded@0.2.0: resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +frac@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/frac/-/frac-0.3.1.tgz#577677b7fdcbe6faf7c461f1801d34137cda4354" + integrity sha512-1Lzf2jOjhIkRaa013KlxNOn2D9FemmQNeYUDpEIyPeFXmpLvbZXJOlaayMBT6JKXx+afQFgQ1QJ4kaF7Z07QFQ== + +frac@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b" + integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== + fraction.js@^4.3.7: version "4.3.7" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" @@ -4617,6 +4721,11 @@ jasmine-core@~5.6.0: resolved "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.6.0.tgz" integrity sha512-niVlkeYVRwKFpmfWg6suo6H9CrNnydfBLEqefM5UjibYS+UoTjZdmvPJSiuyrRLGnFj1eYRhFd/ch+5hSlsFVA== +javascript-natural-sort@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== + jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" @@ -4636,6 +4745,11 @@ jiti@^2.4.2: resolved "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz" integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== +jmespath@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -4702,11 +4816,21 @@ json-parse-even-better-errors@^4.0.0: resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz" integrity sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/json-source-map/-/json-source-map-0.6.1.tgz#e0b1f6f4ce13a9ad57e2ae165a24d06e62c79a0f" + integrity sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg== + json5@^2.1.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" @@ -4717,6 +4841,20 @@ jsonc-parser@3.3.1: resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz" integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== +jsoneditor@^10.1.3: + version "10.1.3" + resolved "https://registry.yarnpkg.com/jsoneditor/-/jsoneditor-10.1.3.tgz#ddb5994e0ca9a74bc4332991529fc88a8d84064e" + integrity sha512-zvbkiduFR19vLMJN1sSvBs9baGDdQRJGmKy6+/vQzDFhx//oEd6WAkrmmTeU4NNk9MAo+ZirENuwbtJXvS9M5g== + dependencies: + ace-builds "^1.36.2" + ajv "^6.12.6" + javascript-natural-sort "^0.7.1" + jmespath "^0.16.0" + json-source-map "^0.6.1" + jsonrepair "^3.8.1" + picomodal "^3.0.0" + vanilla-picker "^2.12.3" + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" @@ -4729,6 +4867,18 @@ jsonparse@^1.3.1: resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +jsonrepair@^3.8.1: + version "3.12.0" + resolved "https://registry.yarnpkg.com/jsonrepair/-/jsonrepair-3.12.0.tgz#a0c9f97f5628156a44b78597fc8cdaf3561db751" + integrity sha512-SWfjz8SuQ0wZjwsxtSJ3Zy8vvLg6aO/kxcp9TWNPGwJKgTZVfhNEQBMk/vPOpYCDFWRxD6QWuI6IHR1t615f0w== + +jszip@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-2.4.0.tgz#487a93b76c3bffa6cb085cd61eb934eabe2d294f" + integrity sha512-m+yvNmYfRCaf1gr5YFT5e3fnSqLnE9McbNyRd0fNycsT0HltS19NKc18fh3Lvl/AIW/ovL6/MQ1JnfFg4G3o4A== + dependencies: + pako "~0.2.5" + karma-chrome-launcher@~3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz" @@ -5695,6 +5845,11 @@ pacote@20.0.0: ssri "^12.0.0" tar "^6.1.11" +pako@~0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -5803,6 +5958,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomodal@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/picomodal/-/picomodal-3.0.0.tgz#facd30f4fbf34a809c1e04ea525f004f399c0b82" + integrity sha512-FoR3TDfuLlqUvcEeK5ifpKSVVns6B4BQvc8SDF6THVMuadya6LLtji0QgUDSStw0ZR2J7I6UGi5V2V23rnPWTw== + pify@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" @@ -5962,7 +6122,7 @@ punycode@^1.4.1: resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== -punycode@^2.1.1, punycode@^2.3.0, punycode@^2.3.1: +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -6033,7 +6193,7 @@ readable-stream@^2.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.4.0: +readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.4.0: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -6674,6 +6834,22 @@ sprintf-js@^1.1.3: resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== +ssf@~0.11.2: + version "0.11.2" + resolved "https://registry.yarnpkg.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c" + integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g== + dependencies: + frac "~1.1.2" + +ssf@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/ssf/-/ssf-0.8.2.tgz#b9d4dc6a1c1bcf76f8abfa96d7d7656fb2abecd6" + integrity sha512-+ZkFDAG+ImJ48DcZvabx6YTrZ67DKkM0kbyOOtH73mbUEvNhQWWgRZrHC8+k7GuGKWQnACYLi7bj0eCt1jmosQ== + dependencies: + colors "0.6.2" + frac "0.3.1" + voc "" + ssri@^12.0.0: version "12.0.0" resolved "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz" @@ -6958,6 +7134,11 @@ typed-assert@^1.0.8: resolved "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz" integrity sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg== +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + typescript@~5.8.2: version "5.8.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4" @@ -7043,6 +7224,13 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.1" +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" @@ -7079,6 +7267,13 @@ validate-npm-package-name@^6.0.0: resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.0.tgz" integrity sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg== +vanilla-picker@^2.12.3: + version "2.12.3" + resolved "https://registry.yarnpkg.com/vanilla-picker/-/vanilla-picker-2.12.3.tgz#1cc47b641a2b9c9afc5ac3a9a02febace0f1b17a" + integrity sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ== + dependencies: + "@sphinxxxx/color-conversion" "^2.2.2" + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" @@ -7095,6 +7290,11 @@ vite@6.2.0: optionalDependencies: fsevents "~2.3.3" +voc@: + version "1.2.0" + resolved "https://registry.yarnpkg.com/voc/-/voc-1.2.0.tgz#c459024531d71067c09e2c0c2bda6c2b13af32d8" + integrity sha512-BOuDjFFYvJdZO6e/N65AlaDItXo2TgyLjeyRYcqgAPkXpp5yTJcvkL2n+syO1r9Qc5g96tfBD2tuiMhYDmaGcA== + void-elements@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz" @@ -7299,6 +7499,16 @@ wildcard@^2.0.1: resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz" integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== +wmf@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wmf/-/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da" + integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw== + +word@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961" + integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" @@ -7364,6 +7574,32 @@ xhr2@^0.2.0: resolved "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz" integrity sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw== +xlsx-style@^0.8.13: + version "0.8.13" + resolved "https://registry.yarnpkg.com/xlsx-style/-/xlsx-style-0.8.13.tgz#ed238d6b8c0562f9447c2906abbded2d339e0486" + integrity sha512-Cj3pGUvzrP2q9oowpLP8GyujovTaBGjBRRUlCKPitNvHWj9JDD5+FDPZIM5QQggGb995ZhkuBSsMZOSd5TzIWg== + dependencies: + adler-32 "" + cfb ">=0.10.0" + codepage "~1.3.6" + commander "" + crc-32 "" + jszip "2.4.0" + ssf "~0.8.1" + +xlsx@^0.18.5: + version "0.18.5" + resolved "https://registry.yarnpkg.com/xlsx/-/xlsx-0.18.5.tgz#16711b9113c848076b8a177022799ad356eba7d0" + integrity sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ== + dependencies: + adler-32 "~1.3.0" + cfb "~1.2.1" + codepage "~1.15.0" + crc-32 "~1.2.1" + ssf "~0.11.2" + wmf "~1.0.1" + word "~0.3.0" + xml-name-validator@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" diff --git a/image/fixedasset-1.png b/image/fixedasset-1.png new file mode 100644 index 0000000..6fb5785 Binary files /dev/null and b/image/fixedasset-1.png differ diff --git a/image/fixedasset-2.png b/image/fixedasset-2.png new file mode 100644 index 0000000..2205f28 Binary files /dev/null and b/image/fixedasset-2.png differ diff --git a/image/fixedasset-3.png b/image/fixedasset-3.png new file mode 100644 index 0000000..847e441 Binary files /dev/null and b/image/fixedasset-3.png differ diff --git a/image/fixedasset-4.png b/image/fixedasset-4.png new file mode 100644 index 0000000..97f7b3c Binary files /dev/null and b/image/fixedasset-4.png differ diff --git a/license.txt b/license.txt index afbd71c..9637866 100644 --- a/license.txt +++ b/license.txt @@ -1 +1,14 @@ -NCPL(Non-Commercial Public License) \ No newline at end of file +NCPL(Non-Commercial Public License) +This software is licensed under the No-Cost Public License (NCPL). The terms are as follows: + +- Free for personal and non-commercial use. +- For commercial purposes (including enterprise or organization internal use), a commercial license is required. + +To obtain a commercial license or customized services, please contact: + +Email: felix9611.ca@gmail.com + +About the Developer: +I am a full-stack developer specializing in management solutions. I offer customized services, including report generation, system integration, and more. Companies are welcome to collaborate with me to tailor this tool to your specific needs! + +For any questions, feedback, or collaboration inquiries, feel free to reach out. \ No newline at end of file