diff --git a/angular.json b/angular.json index 47bed04..fd2a032 100644 --- a/angular.json +++ b/angular.json @@ -25,6 +25,7 @@ ], "tsConfig": "tsconfig.app.json", "assets": [ + "public/images/favicon.ico", { "glob": "**/*", "input": "public" @@ -85,6 +86,7 @@ ], "tsConfig": "tsconfig.spec.json", "assets": [ + "public/favicon.ico", { "glob": "**/*", "input": "public" diff --git a/package-lock.json b/package-lock.json index 684c66a..ea0952b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ng-tailadmin", - "version": "1.0.2", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ng-tailadmin", - "version": "1.0.2", + "version": "1.1.0", "dependencies": { "@amcharts/amcharts5": "^5.13.5", "@amcharts/amcharts5-geodata": "^5.1.4", @@ -23,11 +23,15 @@ "@fullcalendar/daygrid": "^6.1.20", "@fullcalendar/interaction": "^6.1.20", "@fullcalendar/timegrid": "^6.1.20", + "@ngx-translate/core": "^17.0.0", + "@ngx-translate/http-loader": "^17.0.0", "@popperjs/core": "^2.11.8", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/postcss": "^4.1.11", "@types/prismjs": "^1.26.5", "apexcharts": "^5.3.2", "flatpickr": "^4.6.13", + "lightningcss-linux-x64-gnu": "1.30.1", "ng-apexcharts": "^2.0.4", "ng-otp-input": "^2.0.9", "postcss": "^8.5.6", @@ -995,7 +999,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1007,7 +1010,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1018,7 +1020,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2536,7 +2537,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2549,6 +2549,32 @@ "url": "https://github.com/sponsors/Brooooooklyn" } }, + "node_modules/@ngx-translate/core": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-17.0.0.tgz", + "integrity": "sha512-Rft2D5ns2pq4orLZjEtx1uhNuEBerUdpFUG1IcqtGuipj6SavgB8SkxtNQALNDA+EVlvsNCCjC2ewZVtUeN6rg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=16", + "@angular/core": ">=16" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-17.0.0.tgz", + "integrity": "sha512-hgS8sa0ARjH9ll3PhkLTufeVXNI2DNR2uFKDhBgq13siUXzzVr/a31M6zgecrtwbA34iaBV01hsTMbMS8V7iIw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=16", + "@angular/core": ">=16" + } + }, "node_modules/@npmcli/agent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", @@ -3919,6 +3945,22 @@ "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" } }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz", + "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@tailwindcss/oxide-darwin-arm64": { "version": "4.1.12", "cpu": [ @@ -3933,6 +3975,194 @@ "node": ">= 10" } }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz", + "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz", + "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz", + "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz", + "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz", + "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz", + "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz", + "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@emnapi/wasi-threads": "^1.0.4", + "@napi-rs/wasm-runtime": "^0.2.12", + "@tybys/wasm-util": "^0.10.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz", + "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz", + "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz", + "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@tailwindcss/postcss": { "version": "4.1.12", "license": "MIT", @@ -4011,7 +4241,6 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -7499,6 +7728,25 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/listr2": { "version": "9.0.5", "dev": true, diff --git a/package.json b/package.json index 505dbeb..a24648c 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "@fullcalendar/daygrid": "^6.1.20", "@fullcalendar/interaction": "^6.1.20", "@fullcalendar/timegrid": "^6.1.20", + "@ngx-translate/core": "^17.0.0", + "@ngx-translate/http-loader": "^17.0.0", "@popperjs/core": "^2.11.8", "@tailwindcss/postcss": "^4.1.11", "@types/prismjs": "^1.26.5", @@ -38,7 +40,9 @@ "swiper": "^11.2.10", "tailwindcss": "^4.1.11", "tslib": "^2.3.0", - "zone.js": "~0.15.0" + "zone.js": "~0.15.0", + "lightningcss-linux-x64-gnu": "1.30.1" , + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11" }, "devDependencies": { "@angular/build": "^21.2.2", diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 57614f9..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/images/favicon.ico b/public/images/favicon.ico index cf3128a..2a90e20 100644 Binary files a/public/images/favicon.ico and b/public/images/favicon.ico differ diff --git a/public/images/logo/logo-small.png b/public/images/logo/logo-small.png new file mode 100644 index 0000000..b8c3e01 Binary files /dev/null and b/public/images/logo/logo-small.png differ diff --git a/public/images/logo/logo.png b/public/images/logo/logo.png new file mode 100644 index 0000000..e494b4b Binary files /dev/null and b/public/images/logo/logo.png differ diff --git a/public/images/logo/logo2.png b/public/images/logo/logo2.png new file mode 100644 index 0000000..9006cdd Binary files /dev/null and b/public/images/logo/logo2.png differ diff --git a/public/images/user/johng.jpg b/public/images/user/johng.jpg new file mode 100644 index 0000000..5761a7e Binary files /dev/null and b/public/images/user/johng.jpg differ diff --git a/public/images/user/owner.jpg b/public/images/user/owner.jpg index 55985fe..5761a7e 100644 Binary files a/public/images/user/owner.jpg and b/public/images/user/owner.jpg differ diff --git a/public/images/user/owner.png b/public/images/user/owner.png index 810cf07..5761a7e 100644 Binary files a/public/images/user/owner.png and b/public/images/user/owner.png differ diff --git a/public/images/user/owner1.jpg b/public/images/user/owner1.jpg new file mode 100644 index 0000000..55985fe Binary files /dev/null and b/public/images/user/owner1.jpg differ diff --git a/public/images/user/owner1.png b/public/images/user/owner1.png new file mode 100644 index 0000000..810cf07 Binary files /dev/null and b/public/images/user/owner1.png differ diff --git a/src/app/app.config.ts b/src/app/app.config.ts index a1e7d6f..7e36ba2 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,8 +1,145 @@ -import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core'; import { provideRouter } from '@angular/router'; - +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { Observable, of } from 'rxjs'; import { routes } from './app.routes'; +import { TranslateService } from '@ngx-translate/core'; +import { provideZonelessChangeDetection, APP_INITIALIZER } from '@angular/core'; -export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] + +// Master dictionary for the entire app +// Master dictionary for the entire app +const GLOBAL_TEXT: any = { + en: { + SIDEBAR: { + MENU_LABEL: "Menu", // Added for sidebar section header + OTHERS_LABEL: "Others", // Added for sidebar section header + DASHBOARD: "Dashboard", + ECOMMERCE: "Ecommerce", + CALENDAR: "Calendar", + PROFILE: "User Profile", + FORMS: "Forms", + FORM_ELEMENTS: "Form Elements", + TABLES: "Tables", + BASIC_TABLES: "Basic Tables", + PAGES: "Pages", + BLANK_PAGE: "Blank Page", + ERROR_404: "404 Error", + CHARTS: "Charts", + LINE_CHART: "Line Chart", + BAR_CHART: "Bar Chart", + UI_ELEMENTS: "UI Elements", + ALERTS: "Alerts", + AVATAR: "Avatar", + BADGE: "Badge", + BUTTONS: "Buttons", + IMAGES: "Images", + VIDEOS: "Videos", + AUTHENTICATION: "Authentication", + SIGN_IN: "Sign In", + SIGN_UP: "Sign Up" + }, + HEADER: { SEARCH: "Search or type command..." }, + CARDS: { + CUSTOMERS: "Customers", + ORDERS: "Orders", + MONTHLY_TARGET: "Monthly Target" + }, + COMMON: { + NEW: "New" // Added for "new" badges in menu + } + }, + zh: { + SIDEBAR: { + MENU_LABEL: "菜单", // Added for sidebar section header + OTHERS_LABEL: "其他", // Added for sidebar section header + DASHBOARD: "仪表板", + ECOMMERCE: "电子商务", + CALENDAR: "日历", + PROFILE: "个人资料", + FORMS: "表单", + FORM_ELEMENTS: "表单元素", + TABLES: "表格", + BASIC_TABLES: "基础表格", + PAGES: "页面", + BLANK_PAGE: "空白页面", + ERROR_404: "404 错误", + CHARTS: "图表", + LINE_CHART: "折线图", + BAR_CHART: "柱状图", + UI_ELEMENTS: "UI 元素", + ALERTS: "警告", + AVATAR: "头像", + BADGE: "徽章", + BUTTONS: "按钮", + IMAGES: "图片", + VIDEOS: "视频", + AUTHENTICATION: "身份验证", + SIGN_IN: "登录", + SIGN_UP: "注册" + }, + HEADER: { SEARCH: "搜索或输入命令..." }, + CARDS: { + CUSTOMERS: "客户", + ORDERS: "订单", + MONTHLY_TARGET: "每月目标" + }, + COMMON: { + NEW: "新" // Added for "new" badges in menu + } + } }; +// This loader replaces TranslateHttpLoader and fixes the "got 3 arguments" error +export class InlineLoader implements TranslateLoader { + getTranslation(lang: string): Observable { + return of(GLOBAL_TEXT[lang] || GLOBAL_TEXT['en']); + } +} + +export function initializeTranslation(translate: TranslateService): () => Promise { + return () => { + // 1. Set the default fallback + translate.setDefaultLang('en'); + + // 2. Determine which language to use (stored vs default) + const savedLang = localStorage.getItem('language') || 'en'; + + // 3. Return as a promise to block the app's initial render + return translate.use(savedLang).toPromise(); + }; +} + +export const appConfig: ApplicationConfig = { + providers: [ + // 1. Switch to Zoneless (Matching your Angular 21 architecture) + provideZonelessChangeDetection(), + + provideRouter(routes), + + // 2. Translation Module Setup + importProvidersFrom( + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: InlineLoader + }, + defaultLanguage: 'en', + }) + ), + + // 3. The "Force Load" Initializer + { + provide: APP_INITIALIZER, + useFactory: (translate: TranslateService) => { + return () => { + // Check localStorage for a saved preference, otherwise default to 'en' + const lang = localStorage.getItem('selectedLanguage') || 'en'; + translate.setDefaultLang('en'); + return translate.use(lang); + }; + }, + deps: [TranslateService], + multi: true, + }, + ] +}; \ No newline at end of file diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index a0cb85a..96f4d33 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -19,6 +19,11 @@ import { SignInComponent } from './pages/auth-pages/sign-in/sign-in.component'; import { SignUpComponent } from './pages/auth-pages/sign-up/sign-up.component'; import { CalenderComponent } from './pages/calender/calender.component'; +import { ProductListTableComponent } from './shared/components/ecommerce/product-list-table/product-list-table.component'; +import { AddProductFormComponent } from './shared/components/ecommerce/add-product-form/add-product-form.component'; + + + export const routes: Routes = [ { path:'', @@ -31,6 +36,28 @@ export const routes: Routes = [ title: 'Angular Ecommerce Dashboard | TailAdmin - Angular Admin Dashboard Template', }, + { + path:'products', + component: ProductListTableComponent, + pathMatch: 'full', + title: + 'Angular Ecommerce Dashboard | TailAdmin - Angular Admin Dashboard Template', + }, + + { + path:'add-product', + component: AddProductFormComponent, + pathMatch: 'full', + title: + 'Angular Ecommerce Dashboard | TailAdmin - Angular Admin Dashboard Template', + }, + { + path:'invoices', + component: InvoicesComponent, + pathMatch: 'full', + title: + 'Angular Ecommerce Dashboard | TailAdmin - Angular Admin Dashboard Template', + }, { path:'calendar', component:CalenderComponent, @@ -41,6 +68,11 @@ export const routes: Routes = [ component:ProfileComponent, title:'Angular Profile Dashboard | TailAdmin - Angular Admin Dashboard Template' }, + { + path: 'product-list', // The URL path + component: ProductListTableComponent, + title: 'Product List | TailAdmin' + }, { path:'form-elements', component:FormElementsComponent, diff --git a/src/app/pages/invoices/invoices.component.html b/src/app/pages/invoices/invoices.component.html index 35c74f7..26a92d4 100644 --- a/src/app/pages/invoices/invoices.component.html +++ b/src/app/pages/invoices/invoices.component.html @@ -1,12 +1,8 @@
-
- - - - - - - +
+ +
+
\ No newline at end of file diff --git a/src/app/pages/invoices/invoices.component.ts b/src/app/pages/invoices/invoices.component.ts index 2b78f6e..f6453f3 100644 --- a/src/app/pages/invoices/invoices.component.ts +++ b/src/app/pages/invoices/invoices.component.ts @@ -1,14 +1,20 @@ import { Component } from '@angular/core'; import { PageBreadcrumbComponent } from '../../shared/components/common/page-breadcrumb/page-breadcrumb.component'; -import { InvoiceSidebarComponent } from '../../shared/components/invoice/invoice-sidebar/invoice-sidebar.component'; -import { InvoiceMainComponent } from '../../shared/components/invoice/invoice-main/invoice-main.component'; + + + +import { InvoiceMetricsComponent } from '../../shared/components/invoice/invoice-metrics/invoice-metrics.component'; +import { InvoiceListComponent } from '../../shared/components/invoice/invoice-list/invoice-list.component'; + + + @Component({ selector: 'app-invoices', imports: [ PageBreadcrumbComponent, - InvoiceSidebarComponent, - InvoiceMainComponent + InvoiceMetricsComponent, + InvoiceListComponent ], templateUrl: './invoices.component.html', styles: `` diff --git a/src/app/shared/components/common/language/language-selector.component.ts b/src/app/shared/components/common/language/language-selector.component.ts new file mode 100644 index 0000000..ffef6a7 --- /dev/null +++ b/src/app/shared/components/common/language/language-selector.component.ts @@ -0,0 +1,55 @@ +import { Component, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'app-language-selector', + standalone: true, + imports: [CommonModule], + template: ` +
+ + + @if (isOpen()) { +
+ @for (lang of languages; track lang.code) { + + } +
+ } +
+ ` +}) +export class LanguageSelectorComponent { + isOpen = signal(false); + currentLang = signal('EN'); + + languages = [ + { code: 'EN', label: 'English' }, + { code: 'ZH', label: '中文' } + ]; + + constructor(private translate: TranslateService) {} + + toggleDropdown() { + this.isOpen.update(v => !v); + } + + selectLanguage(code: string) { + // This is the trigger that automatically updates the entire app + this.translate.use(code.toLowerCase()); + this.currentLang.set(code); + this.isOpen.set(false); + } +} \ No newline at end of file diff --git a/src/app/shared/components/ecommerce/billing/billing-info/billing-info.component.html b/src/app/shared/components/ecommerce/billing/billing-info/billing-info.component.html index a6e47e3..a6416fd 100644 --- a/src/app/shared/components/ecommerce/billing/billing-info/billing-info.component.html +++ b/src/app/shared/components/ecommerce/billing/billing-info/billing-info.component.html @@ -11,7 +11,7 @@

Name - Mushafrof Chowdhury + Mushafrof Gonzales
  • @@ -102,7 +102,7 @@

    -

  • diff --git a/src/app/shared/components/header/user-dropdown/user-dropdown.component.html b/src/app/shared/components/header/user-dropdown/user-dropdown.component.html index 65b8d8f..d27c86f 100644 --- a/src/app/shared/components/header/user-dropdown/user-dropdown.component.html +++ b/src/app/shared/components/header/user-dropdown/user-dropdown.component.html @@ -1,9 +1,9 @@
    - - +
    +
    +
    +
    +

    Inventory Management

    +

    Manage stock and prepare sales

    -
    -
    -
    - - - - - - - - - - - - - - @for (invoice of paginatedInvoices; track $index) { - - + + + } + +
    -
    -
    - -

    Invoice Number

    -
    -
    -
    -
    -

    Customer

    - - - - - - - - -
    -
    -
    -

    Creation Date

    - - - - - - - - -
    -
    -
    -

    Due Date

    - - - - - - - - -
    -
    TotalStatus -
    - Action -
    -
    -
    - -

    {{invoice.number}}

    + +
    + + + + + + + + + + + + + @for (product of paginatedProducts(); track product.id) { + + + + - - - - + } + +
    + Product @if(sortColumn() === 'name') { {{sortDirection() === 'asc' ? '↑' : '↓'}} } + + Stock Level @if(sortColumn() === 'stock') { {{sortDirection() === 'asc' ? '↑' : '↓'}} } + + Stock Updated @if(sortColumn() === 'lastUpdated') { {{sortDirection() === 'asc' ? '↑' : '↓'}} } + + Unit Price @if(sortColumn() === 'price') { {{sortDirection() === 'asc' ? '↑' : '↓'}} } + Qty to SellSubtotal
    +

    {{product.name}}

    +

    {{product.sku}}

    +
    + @if (isEditing()) { + + } @else { +
    + {{ product.stock }} total + Available: {{ product.stock - product.toSell }} +
    + } +
    +
    + {{ product.lastUpdated | date:'MMM d, yyyy' }} + {{ product.lastUpdated | date:'shortTime' }}
    - {{invoice.customer}} + + @if (isEditing()) { +
    + + +
    + } @else { +
    + {{ product.price | currency:'PHP':'symbol-narrow' }} + Updated: {{ product.priceLastUpdated | date:'MM/dd/yy h:mm a' }} +
    + }
    -

    {{invoice.creationDate}}

    +
    +
    + + + +
    -

    {{invoice.dueDate}}

    +
    + {{ (product.price * product.toSell) | currency:'PHP':'symbol-narrow' }} -

    ${{invoice.total}}

    +
    +
    + +
    +
    + Rows per page: + +
    +
    + Page {{ currentPage() }} of {{ Math.ceil(filteredProducts().length / pageSize()) || 1 }} +
    + + +
    +
    +
    +
    +
    Items in transaction: {{ selectedCount() }}
    +
    +
    +

    Expected Revenue

    +

    {{ totalToSell() | currency:'PHP':'symbol-narrow' }}

    +
    + +
    +
    +
    +
    +
    +
    +

    Transaction Revenue History

    +

    Review processed operations log and real revenue records

    +
    + +
    + Total Accumulated Revenue + + {{ cumulativeRevenue() | currency:'PHP':'symbol-narrow' }} + +
    +
    + +
    + + + + + + + + + + + + @if (completedTransactions().length === 0) { + + - + } + @for (tx of completedTransactions(); track tx.id) { + + + - - - } - -
    Transaction IDDate & TimeBreakdown ItemsTotal UnitsTotal Revenue
    + No transactions submitted yet. Process items above to populate logs. - - {{invoice.status}} - +
    + {{ tx.id }} + + {{ tx.timestamp | date:'medium' }} -
    - -
    - -
    -
    - - -
    -
    +
    +
    + @for (detail of tx.details; track detail.productName) { + + {{ detail.productName }} (x{{ detail.qtySold }}) + + }
    -
    -
    -
    - - Showing {{startEntry}} to {{endEntry}} of {{sortedInvoices.length}} - -
    -
    - - - Page {{currentPage}} of {{totalPages}} - - - +
    + {{ tx.itemsCount }} + + {{ tx.totalRevenue | currency:'PHP':'symbol-narrow' }} +
    -
    \ No newline at end of file +
    \ No newline at end of file diff --git a/src/app/shared/components/invoice/invoice-list/invoice-list.component.ts b/src/app/shared/components/invoice/invoice-list/invoice-list.component.ts index 222d29d..71ebd5f 100644 --- a/src/app/shared/components/invoice/invoice-list/invoice-list.component.ts +++ b/src/app/shared/components/invoice/invoice-list/invoice-list.component.ts @@ -1,391 +1,146 @@ +import { Component, signal, computed, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { Component, ElementRef, HostListener } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { TableDropdownComponent } from '../../common/table-dropdown/table-dropdown.component'; - -interface Invoice { - id: number; - number: string; - customer: string; - creationDate: string; - dueDate: string; - total: number; - status: "Paid" | "Unpaid" | "Draft"; -} -interface SortState { - sortBy: "number" | "customer" | "creationDate" | "dueDate" | "total"; - sortDirection: "asc" | "desc"; +import { ProductService, Product } from '../../../services/product.service'; + +interface Transaction { + id: string; + timestamp: Date; + itemsCount: number; + totalRevenue: number; + details: { productName: string; qtySold: number; subtotal: number }[]; } + @Component({ selector: 'app-invoice-list', - imports: [ - CommonModule, - FormsModule, - TableDropdownComponent, - ], + standalone: true, + imports: [CommonModule, FormsModule], templateUrl: './invoice-list.component.html', - styles: `` }) export class InvoiceListComponent { + private productService = inject(ProductService); + + // Expose Math to the template for page calculations + public Math = Math; + + public isEditing = signal(false); + public searchTerm = signal(''); + public sortColumn = signal(null); + public sortDirection = signal<'asc' | 'desc'>('asc'); + + // Pagination signals + public currentPage = signal(1); + public pageSize = signal(5); + + public products = this.productService.allProducts; + public completedTransactions = signal([]); + + // Filtering & Sorting Logic + public filteredProducts = computed(() => { + const term = this.searchTerm().toLowerCase(); + let data = [...this.products().filter(p => + p.name.toLowerCase().includes(term) || p.sku.toLowerCase().includes(term) + )]; + + const col = this.sortColumn(); + if (col) { + data.sort((a, b) => { + let valA: any = a[col]; + let valB: any = b[col]; + if (valA instanceof Date) valA = valA.getTime(); + if (valB instanceof Date) valB = valB.getTime(); + return this.sortDirection() === 'asc' ? (valA > valB ? 1 : -1) : (valA < valB ? 1 : -1); + }); + } + return data; + }); - invoices: Invoice[] = [ - { - id: 1, - number: "#323534", - customer: "Lindsey Curtis", - creationDate: "August 7, 2028", - dueDate: "February 28, 2028", - total: 999, - status: "Paid", - }, - { - id: 2, - number: "#323535", - customer: "John Doe", - creationDate: "July 1, 2028", - dueDate: "January 1, 2029", - total: 1200, - status: "Unpaid", - }, - { - id: 3, - number: "#323536", - customer: "Jane Smith", - creationDate: "June 15, 2028", - dueDate: "December 15, 2028", - total: 850, - status: "Draft", - }, - { - id: 4, - number: "#323537", - customer: "Michael Brown", - creationDate: "May 10, 2028", - dueDate: "November 10, 2028", - total: 1500, - status: "Paid", - }, - { - id: 5, - number: "#323538", - customer: "Emily Davis", - creationDate: "April 5, 2028", - dueDate: "October 5, 2028", - total: 700, - status: "Unpaid", - }, - { - id: 6, - number: "#323539", - customer: "Chris Wilson", - creationDate: "March 1, 2028", - dueDate: "September 1, 2028", - total: 1100, - status: "Paid", - }, - { - id: 7, - number: "#323540", - customer: "Jessica Lee", - creationDate: "February 20, 2028", - dueDate: "August 20, 2028", - total: 950, - status: "Draft", - }, - { - id: 8, - number: "#323541", - customer: "David Kim", - creationDate: "January 15, 2028", - dueDate: "July 15, 2028", - total: 1300, - status: "Paid", - }, - { - id: 9, - number: "#323542", - customer: "Sarah Clark", - creationDate: "December 10, 2027", - dueDate: "June 10, 2028", - total: 800, - status: "Unpaid", - }, - { - id: 10, - number: "#323543", - customer: "Matthew Lewis", - creationDate: "November 5, 2027", - dueDate: "May 5, 2028", - total: 1400, - status: "Paid", - }, - { - id: 11, - number: "#323544", - customer: "Olivia Walker", - creationDate: "October 1, 2027", - dueDate: "April 1, 2028", - total: 1200, - status: "Draft", - }, - { - id: 12, - number: "#323545", - customer: "Daniel Hall", - creationDate: "September 20, 2027", - dueDate: "March 20, 2028", - total: 1000, - status: "Paid", - }, - { - id: 13, - number: "#323546", - customer: "Sophia Allen", - creationDate: "August 15, 2027", - dueDate: "February 15, 2028", - total: 900, - status: "Unpaid", - }, - { - id: 14, - number: "#323547", - customer: "James Young", - creationDate: "July 10, 2027", - dueDate: "January 10, 2028", - total: 1600, - status: "Paid", - }, - { - id: 15, - number: "#323548", - customer: "Ava Hernandez", - creationDate: "June 5, 2027", - dueDate: "December 5, 2027", - total: 1050, - status: "Draft", - }, - { - id: 16, - number: "#323549", - customer: "William King", - creationDate: "May 1, 2027", - dueDate: "November 1, 2027", - total: 1150, - status: "Paid", - }, - { - id: 17, - number: "#323550", - customer: "Mia Wright", - creationDate: "April 20, 2027", - dueDate: "October 20, 2027", - total: 980, - status: "Unpaid", - }, - { - id: 18, - number: "#323551", - customer: "Benjamin Lopez", - creationDate: "March 15, 2027", - dueDate: "September 15, 2027", - total: 1250, - status: "Paid", - }, - { - id: 19, - number: "#323552", - customer: "Charlotte Hill", - creationDate: "February 10, 2027", - dueDate: "August 10, 2027", - total: 890, - status: "Draft", - }, - { - id: 20, - number: "#323553", - customer: "Elijah Scott", - creationDate: "January 5, 2027", - dueDate: "July 5, 2027", - total: 1350, - status: "Paid", - }, - { - id: 21, - number: "#323554", - customer: "Amelia Green", - creationDate: "December 1, 2026", - dueDate: "June 1, 2027", - total: 1020, - status: "Unpaid", - }, - { - id: 22, - number: "#323555", - customer: "Lucas Adams", - creationDate: "November 20, 2026", - dueDate: "May 20, 2027", - total: 1120, - status: "Paid", - }, - { - id: 23, - number: "#323556", - customer: "Harper Nelson", - creationDate: "October 15, 2026", - dueDate: "April 15, 2027", - total: 970, - status: "Draft", - }, - { - id: 24, - number: "#323557", - customer: "Henry Carter", - creationDate: "September 10, 2026", - dueDate: "March 10, 2027", - total: 1280, - status: "Paid", - }, - { - id: 25, - number: "#323558", - customer: "Evelyn Mitchell", - creationDate: "August 5, 2026", - dueDate: "February 5, 2027", - total: 1080, - status: "Unpaid", - }, - ]; - + // Paginated View + public paginatedProducts = computed(() => { + return this.productService.getPagedData(this.filteredProducts(), this.currentPage(), this.pageSize()); + }); - selected: number[] = []; - sort: SortState = { sortBy: 'number', sortDirection: 'asc' }; - currentPage: number = 1; - filterStatus: 'All' | 'Unpaid' | 'Draft' | 'Paid' = 'All'; - search: string = ''; - showFilter: boolean = false; - itemsPerPage: number = 10; + // Revenue computations + public totalToSell = computed(() => this.products().reduce((acc, p) => acc + (p.price * p.toSell), 0)); + public selectedCount = computed(() => this.products().filter(p => p.toSell > 0).length); + public cumulativeRevenue = computed(() => this.completedTransactions().reduce((acc, tx) => acc + tx.totalRevenue, 0)); - constructor(private elementRef: ElementRef) {} + // Methods + toggleEdit() { this.isEditing.set(!this.isEditing()); } - @HostListener('document:click', ['$event']) - handleClickOutside(event: MouseEvent) { - const filterDropdown = this.elementRef.nativeElement.querySelector('.filter-dropdown'); - if (filterDropdown && !filterDropdown.contains(event.target as Node)) { - this.showFilter = false; + setSort(column: keyof Product) { + if (this.sortColumn() === column) { + this.sortDirection.set(this.sortDirection() === 'asc' ? 'desc' : 'asc'); + } else { + this.sortColumn.set(column); + this.sortDirection.set('asc'); } } - get filteredInvoices(): Invoice[] { - return this.filterStatus === 'All' - ? this.invoices - : this.invoices.filter(invoice => invoice.status === this.filterStatus); - } + // Pagination Controls + changePage(page: number) { this.currentPage.set(page); } - get searchedInvoices(): Invoice[] { - return this.filteredInvoices.filter( - invoice => - invoice.number.toLowerCase().includes(this.search.toLowerCase()) || - invoice.customer.toLowerCase().includes(this.search.toLowerCase()) - ); + changePageSize(event: Event) { + const size = parseInt((event.target as HTMLSelectElement).value, 10); + this.pageSize.set(size); + this.currentPage.set(1); // Reset to first page whenever size changes } - get sortedInvoices(): Invoice[] { - return [...this.searchedInvoices].sort((a, b) => { - let valA: string | number = a[this.sort.sortBy]; - let valB: string | number = b[this.sort.sortBy]; - if (this.sort.sortBy === 'total') { - valA = Number(valA); - valB = Number(valB); - } else { - valA = typeof valA === 'string' ? valA.toLowerCase() : valA; - valB = typeof valB === 'string' ? valB.toLowerCase() : valB; + private findProductIndex(id: number): number { return this.products().findIndex(p => p.id === id); } + + updateQty(productId: number, change: number) { + const index = this.findProductIndex(productId); + this.products.update(prev => { + const updated = [...prev]; + if (updated[index].toSell + change >= 0 && updated[index].toSell + change <= updated[index].stock) { + updated[index] = { ...updated[index], toSell: updated[index].toSell + change }; } - if (valA < valB) return this.sort.sortDirection === 'asc' ? -1 : 1; - if (valA > valB) return this.sort.sortDirection === 'asc' ? 1 : -1; - return 0; + return updated; }); } - get paginatedInvoices(): Invoice[] { - const start = (this.currentPage - 1) * this.itemsPerPage; - return this.sortedInvoices.slice(start, start + this.itemsPerPage); - } - - get totalPages(): number { - return Math.ceil(this.sortedInvoices.length / this.itemsPerPage) || 1; - } - - get startEntry(): number { - return this.sortedInvoices.length === 0 ? 0 : (this.currentPage - 1) * this.itemsPerPage + 1; - } - - get endEntry(): number { - return Math.min(this.currentPage * this.itemsPerPage, this.sortedInvoices.length); - } - - get visiblePages(): number[] { - const maxVisible = 5; - let start = Math.max(1, this.currentPage - Math.floor(maxVisible / 2)); - let end = Math.min(this.totalPages, start + maxVisible - 1); - if (end - start + 1 < maxVisible) { - start = Math.max(1, end - maxVisible + 1); - } - return Array.from({ length: end - start + 1 }, (_, i) => start + i); + setQty(productId: number, newQty: number | null) { + const quantity = Math.max(0, newQty ?? 0); + const index = this.findProductIndex(productId); + this.products.update(prev => { + const updated = [...prev]; + updated[index] = { ...updated[index], toSell: Math.min(quantity, updated[index].stock) }; + return updated; + }); } - get allPaginatedSelected(): boolean { - return this.paginatedInvoices.length > 0 && this.paginatedInvoices.every(invoice => this.selected.includes(invoice.id)); + updateBaseStock(productId: number, newStock: number) { + const index = this.findProductIndex(productId); + this.products.update(prev => { + const updated = [...prev]; + updated[index] = { ...updated[index], stock: Math.max(newStock || 0, updated[index].toSell), lastUpdated: new Date() }; + return updated; + }); } - ngOnInit(): void {} - - toggleSelectAll(): void { - if (this.allPaginatedSelected) { - this.selected = []; - } else { - this.selected = this.paginatedInvoices.map(i => i.id); - } + updateUnitPrice(productId: number, newPrice: number) { + const index = this.findProductIndex(productId); + this.products.update(prev => { + const updated = [...prev]; + updated[index] = { ...updated[index], price: Math.max(newPrice || 0, 0), priceLastUpdated: new Date() }; + return updated; + }); } - toggleRow(id: number): void { - this.selected = this.selected.includes(id) - ? this.selected.filter(i => i !== id) - : [...this.selected, id]; - } + submitTransaction() { + const activeSales = this.products().filter(p => p.toSell > 0); + if (activeSales.length === 0) return; - sortBy(field: 'number' | 'customer' | 'creationDate' | 'dueDate' | 'total'): void { - this.sort = { - sortBy: field, - sortDirection: this.sort.sortBy === field && this.sort.sortDirection === 'asc' ? 'desc' : 'asc' + const newTransaction: Transaction = { + id: 'TX-' + Math.floor(100000 + Math.random() * 900000), + timestamp: new Date(), + itemsCount: activeSales.reduce((acc, p) => acc + p.toSell, 0), + totalRevenue: this.totalToSell(), + details: activeSales.map(p => ({ productName: p.name, qtySold: p.toSell, subtotal: p.price * p.toSell })) }; - this.currentPage = 1; - } - - goToPage(page: number): void { - if (page >= 1 && page <= this.totalPages) { - this.currentPage = page; - } - } - nextPage(): void { - if (this.currentPage < this.totalPages) { - this.currentPage++; - } + this.products.update(prev => prev.map(p => p.toSell > 0 ? { ...p, stock: p.stock - p.toSell, toSell: 0, lastUpdated: new Date() } : p)); + this.completedTransactions.update(prev => [newTransaction, ...prev]); } - - previousPage(): void { - if (this.currentPage > 1) { - this.currentPage--; - } - } - - setFilterStatus(status: 'All' | 'Unpaid' | 'Draft' | 'Paid'): void { - this.filterStatus = status; - this.currentPage = 1; - } - - toggleFilter(): void { - this.showFilter = !this.showFilter; - } -} +} \ No newline at end of file diff --git a/src/app/shared/components/invoice/invoice-metrics/invoice-metrics.component.html b/src/app/shared/components/invoice/invoice-metrics/invoice-metrics.component.html index 5405bf7..a4c255f 100644 --- a/src/app/shared/components/invoice/invoice-metrics/invoice-metrics.component.html +++ b/src/app/shared/components/invoice/invoice-metrics/invoice-metrics.component.html @@ -2,47 +2,48 @@

    - Overview + Revenue Overview

    -
    -
    + +
    +

    - Overdue + Today's Revenue

    -

    $120.80

    +

    + {{ todayRevenue() | currency:'PHP' }} +

    -
    + +

    - Due within next 30 days + Weekly Revenue

    -

    0.00

    +

    + {{ weeklyRevenue() | currency:'PHP' }} +

    +

    - Average time to get paid + Monthly Revenue

    -

    24 days

    +

    + {{ monthlyRevenue() | currency:'PHP' }} +

    +

    - Upcoming Payout + Overdue

    -

    - $3,450.50 +

    + {{ yearlyAmount() | currency:'PHP' }}

    +
    \ No newline at end of file diff --git a/src/app/shared/components/invoice/invoice-metrics/invoice-metrics.component.ts b/src/app/shared/components/invoice/invoice-metrics/invoice-metrics.component.ts index 29ac810..a62afa9 100644 --- a/src/app/shared/components/invoice/invoice-metrics/invoice-metrics.component.ts +++ b/src/app/shared/components/invoice/invoice-metrics/invoice-metrics.component.ts @@ -1,14 +1,20 @@ -import { Component } from '@angular/core'; +import { Component, signal } from '@angular/core'; import { RouterModule } from '@angular/router'; +import { CommonModule, CurrencyPipe } from '@angular/common'; @Component({ selector: 'app-invoice-metrics', + standalone: true, imports: [ RouterModule, + CommonModule, // Required for CurrencyPipe ], - templateUrl: './invoice-metrics.component.html', - styles: `` + templateUrl: './invoice-metrics.component.html' }) export class InvoiceMetricsComponent { - -} + // Replace these with actual data calls from your service later + public todayRevenue = signal(450.00); + public weeklyRevenue = signal(3200.50); + public monthlyRevenue = signal(12500.80); + public yearlyAmount = signal(312500.80); +} \ No newline at end of file diff --git a/src/app/shared/components/transactions/customer-details/customer-details.component.html b/src/app/shared/components/transactions/customer-details/customer-details.component.html index d7f98e0..adec770 100644 --- a/src/app/shared/components/transactions/customer-details/customer-details.component.html +++ b/src/app/shared/components/transactions/customer-details/customer-details.component.html @@ -8,7 +8,7 @@

    Name - Mushafrof Chowdhury + John Gonzales
  • diff --git a/src/app/shared/components/user-profile/user-info-card/user-info-card.component.ts b/src/app/shared/components/user-profile/user-info-card/user-info-card.component.ts index d0b3447..b1c38d4 100644 --- a/src/app/shared/components/user-profile/user-info-card/user-info-card.component.ts +++ b/src/app/shared/components/user-profile/user-info-card/user-info-card.component.ts @@ -26,8 +26,8 @@ export class UserInfoCardComponent { closeModal() { this.isOpen = false; } user = { - firstName: 'Musharof', - lastName: 'Chowdhury', + firstName: 'John John', + lastName: 'Gonzales', email: 'randomuser@pimjo.com', phone: '+09 363 398 46', bio: 'Team Manager', diff --git a/src/app/shared/components/user-profile/user-meta-card/user-meta-card.component.ts b/src/app/shared/components/user-profile/user-meta-card/user-meta-card.component.ts index 39612c0..c99a459 100644 --- a/src/app/shared/components/user-profile/user-meta-card/user-meta-card.component.ts +++ b/src/app/shared/components/user-profile/user-meta-card/user-meta-card.component.ts @@ -25,11 +25,11 @@ export class UserMetaCardComponent { // Example user data (could be made dynamic) user = { - firstName: 'Musharof', - lastName: 'Chowdhury', + firstName: 'John John', + lastName: 'Gonzales', role: 'Team Manager', location: 'Arizona, United States', - avatar: '/images/user/owner.jpg', + avatar: '/images/user/johng.jpg', social: { facebook: 'https://www.facebook.com/PimjoHQ', x: 'https://x.com/PimjoHQ', diff --git a/src/app/shared/layout/app-header/app-header.component.html b/src/app/shared/layout/app-header/app-header.component.html index 90d8813..9e3a0bb 100644 --- a/src/app/shared/layout/app-header/app-header.component.html +++ b/src/app/shared/layout/app-header/app-header.component.html @@ -3,73 +3,66 @@
    + + - + - Logo - + Logo + + + +
    + +
    - + + + + -
    -
    diff --git a/src/app/shared/layout/app-header/app-header.component.ts b/src/app/shared/layout/app-header/app-header.component.ts index 9f27c3f..ab28aac 100644 --- a/src/app/shared/layout/app-header/app-header.component.ts +++ b/src/app/shared/layout/app-header/app-header.component.ts @@ -5,15 +5,20 @@ import { RouterModule } from '@angular/router'; import { ThemeToggleButtonComponent } from '../../components/common/theme-toggle/theme-toggle-button.component'; import { NotificationDropdownComponent } from '../../components/header/notification-dropdown/notification-dropdown.component'; import { UserDropdownComponent } from '../../components/header/user-dropdown/user-dropdown.component'; +import { TranslateModule } from '@ngx-translate/core'; // +import { LanguageSelectorComponent } from '../../components/common/language/language-selector.component'; @Component({ selector: 'app-header', + standalone: true, imports: [ CommonModule, RouterModule, ThemeToggleButtonComponent, NotificationDropdownComponent, UserDropdownComponent, + TranslateModule, + LanguageSelectorComponent ], templateUrl: './app-header.component.html', }) diff --git a/src/app/shared/layout/app-sidebar/app-sidebar.component.html b/src/app/shared/layout/app-sidebar/app-sidebar.component.html index 21612ca..d635656 100644 --- a/src/app/shared/layout/app-sidebar/app-sidebar.component.html +++ b/src/app/shared/layout/app-sidebar/app-sidebar.component.html @@ -26,20 +26,20 @@ @if ((isExpanded$ | async) || (isHovered$ | async) || (isMobileOpen$ | async)) { Logo } @else { - Logo + Logo }
  • @@ -245,213 +245,7 @@ - - -
    -

    - @if ( - (isExpanded$ | async) || - (isHovered$ | async) || - (isMobileOpen$ | async) - ) { - Others - } @else { - - - - } -

    - - -
    - - @if ((isExpanded$ | async) || (isHovered$ | async) || (isMobileOpen$ | async)) { - - } \ No newline at end of file diff --git a/src/app/shared/layout/app-sidebar/app-sidebar.component.ts b/src/app/shared/layout/app-sidebar/app-sidebar.component.ts index ac27ebe..55ef999 100644 --- a/src/app/shared/layout/app-sidebar/app-sidebar.component.ts +++ b/src/app/shared/layout/app-sidebar/app-sidebar.component.ts @@ -5,7 +5,7 @@ import { NavigationEnd, Router, RouterModule } from '@angular/router'; import { SafeHtmlPipe } from '../../pipe/safe-html.pipe'; import { SidebarWidgetComponent } from './app-sidebar-widget.component'; import { combineLatest, Subscription } from 'rxjs'; - +import { TranslateModule } from '@ngx-translate/core'; type NavItem = { name: string; icon: string; @@ -16,10 +16,12 @@ type NavItem = { @Component({ selector: 'app-sidebar', + standalone: true, imports: [ CommonModule, RouterModule, SafeHtmlPipe, + TranslateModule, SidebarWidgetComponent ], templateUrl: './app-sidebar.component.html', @@ -35,6 +37,19 @@ export class AppSidebarComponent { { name: "Ecommerce", path: "/" }, ], }, + { + name: "E-commerce", + icon: ` + + + `, + subItems: [ + { name: "Products", path: "/products", pro: false }, + { name: "Add Product", path: "/add-product", pro: false }, + { name: "Invoice", path: "/invoices", pro: false } + + ], + }, { icon: ``, name: "Calendar", @@ -45,28 +60,6 @@ export class AppSidebarComponent { name: "User Profile", path: "/profile", }, - { - name: "Forms", - icon: ``, - subItems: [ - { name: "Form Elements", path: "/form-elements", pro: false } - ], - }, - { - name: "Tables", - icon: ``, - subItems: [ - { name: "Basic Tables", path: "/basic-tables", pro: false }, - ], - }, - { - name: "Pages", - icon: ``, - subItems: [ - { name: "Blank Page", path: "/blank", pro: false }, - { name: "404 Error", path: "/error-404", pro: false }, - ], - }, ]; // Others nav items othersItems: NavItem[] = [ diff --git a/src/app/shared/services/product.service.ts b/src/app/shared/services/product.service.ts new file mode 100644 index 0000000..fccca31 --- /dev/null +++ b/src/app/shared/services/product.service.ts @@ -0,0 +1,50 @@ +import { Injectable, signal } from '@angular/core'; + +export interface Product { + id: number; + name: string; + sku: string; + price: number; + stock: number; + toSell: number; + lastUpdated: Date; + priceLastUpdated: Date; +} + +@Injectable({ providedIn: 'root' }) +export class ProductService { + private initialData: Product[] = [ + { id: 1, name: 'Premium watches', sku: 'BP-001', price: 1250.00, stock: 45, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 2, name: 'Premium bags', sku: 'EO-102', price: 2800.00, stock: 12, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 3, name: 'Air cooler', sku: 'AF-552', price: 450.00, stock: 30, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 4, name: 'Wireless Mouse', sku: 'WM-889', price: 850.00, stock: 100, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 5, name: 'Mechanical Keyboard', sku: 'KB-202', price: 3200.00, stock: 25, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 6, name: 'USB-C Cable', sku: 'CB-110', price: 150.00, stock: 200, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 7, name: 'Noise Cancelling Headphones', sku: 'NC-404', price: 5500.00, stock: 15, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 8, name: 'Desk Lamp', sku: 'DL-990', price: 950.00, stock: 40, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 9, name: 'Gaming Chair', sku: 'GC-777', price: 8500.00, stock: 8, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 10, name: 'Webcam 1080p', sku: 'WC-108', price: 1200.00, stock: 50, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 11, name: 'External SSD 1TB', sku: 'ES-1TB', price: 4500.00, stock: 20, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 12, name: 'Bluetooth Speaker', sku: 'BS-505', price: 2200.00, stock: 35, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 13, name: 'Laptop Stand', sku: 'LS-202', price: 600.00, stock: 60, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 14, name: 'USB Hub Multiport', sku: 'UH-004', price: 800.00, stock: 80, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 15, name: 'Ergonomic Chair', sku: 'EC-999', price: 12000.00, stock: 5, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 16, name: 'Microphone Stand', sku: 'MS-111', price: 400.00, stock: 25, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 17, name: 'Monitor 24 inch', sku: 'MT-240', price: 7500.00, stock: 10, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 18, name: 'Phone Case', sku: 'PC-123', price: 250.00, stock: 150, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 19, name: 'Screen Protector', sku: 'SP-456', price: 150.00, stock: 200, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 20, name: 'Wireless Charger', sku: 'WC-789', price: 900.00, stock: 45, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 21, name: 'Power Bank 20k', sku: 'PB-20K', price: 1500.00, stock: 30, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 22, name: 'Table Mat', sku: 'TM-001', price: 350.00, stock: 90, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 23, name: 'HDMI Cable 2m', sku: 'HC-002', price: 200.00, stock: 120, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 24, name: 'Webcam Privacy Cover', sku: 'PC-003', price: 50.00, stock: 300, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() }, + { id: 25, name: 'Portable SSD 500GB', sku: 'PS-500', price: 2800.00, stock: 25, toSell: 0, lastUpdated: new Date(), priceLastUpdated: new Date() } + ]; + + public allProducts = signal(this.initialData); + + getPagedData(data: Product[], page: number, pageSize: number): Product[] { + const startIndex = (page - 1) * pageSize; + return data.slice(startIndex, startIndex + pageSize); + } +} \ No newline at end of file diff --git a/src/app/shared/services/sidebar.service.ts b/src/app/shared/services/sidebar.service.ts index e600811..b97295d 100644 --- a/src/app/shared/services/sidebar.service.ts +++ b/src/app/shared/services/sidebar.service.ts @@ -14,10 +14,14 @@ export class SidebarService { isHovered$ = this.isHoveredSubject.asObservable(); setExpanded(val: boolean) { + console.log("asdsadsa") + this.isExpandedSubject.next(val); } toggleExpanded() { + console.log("asdsadsa") + this.isExpandedSubject.next(!this.isExpandedSubject.value); } diff --git a/src/index.html b/src/index.html index f874eb0..010bb9d 100644 --- a/src/index.html +++ b/src/index.html @@ -5,7 +5,7 @@ Angular Ecommerce Dashboard | TailAdmin - + diff --git a/src/styles.css b/src/styles.css index 558dcb1..0e2a271 100644 --- a/src/styles.css +++ b/src/styles.css @@ -3,6 +3,7 @@ layer(base); @import "tailwindcss"; @import "swiper/css/bundle"; +@import "flatpickr/dist/flatpickr.css"; @custom-variant dark (&:is(.dark *));