agregar vista publica de vehiculos aun faltan cambios

This commit is contained in:
2025-07-17 19:55:28 -04:00
parent 2e99d7b290
commit 31ffabe6cc
965 changed files with 252291 additions and 0 deletions

16
apiferia/.editorconfig Normal file
View File

@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

42
apiferia/.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

9
apiferia/.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"bracketSpacing": true,
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"useTabs": false,
"tabWidth": 2,
"arrowParens": "always"
}

27
apiferia/README.md Normal file
View File

@@ -0,0 +1,27 @@
# Reback
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.3.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

167
apiferia/angular.json Normal file
View File

@@ -0,0 +1,167 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Reback": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/reback",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js",
"src/polyfills.ts",
"@angular/localize/init"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"node_modules/swiper/swiper-bundle.min.css",
"node_modules/flatpickr/dist/flatpickr.css",
"src/assets/scss/icons.scss",
"node_modules/ngx-toastr/toastr.css",
"src/assets/scss/app.scss",
"src/assets/css/bootstrap.css",
"src/assets/css/swiper-bundle.min.css",
"src/assets/css/animate.css",
"src/assets/css/jquery.fancybox.min.css",
"src/assets/css/magnific-popup.css",
"src/assets/css/nice-select.css",
"src/assets/css/font-awesome.css",
"src/assets/css/styles.css",
"src/styles.scss"
],
"scripts": [
"node_modules/gumshoejs/dist/gumshoe.polyfills.js",
"node_modules/jsvectormap/dist/js/jsvectormap.min.js",
"src/assets/js/jquery.min.js",
"src/assets/js/bootstrap.min.js",
"src/assets/js/swiper-bundle.min.js",
"src/assets/js/jquery.fancybox.js",
"src/assets/js/jquery.nice-select.min.js",
"src/assets/js/lazysize.min.js",
"src/assets/js/main.js"
],
"allowedCommonJsDependencies": [
"jsvectormap",
"jsvectormap/dist/maps/world.js",
"jsvectormap/dist/maps/russia.js",
"jsvectormap/dist/maps/canada.js",
"jsvectormap/dist/maps/iraq.js",
"jsvectormap/dist/maps/spain.js",
"dropzone",
"deepmerge",
"can-use-dom",
"choices.js",
"gumshoejs",
"quill-delta",
"apexcharts",
"dayjs",
"moment",
"sweetalert2",
"jquery",
"bootstrap",
"swiper"
],
"stylePreprocessorOptions": {
"sass": {
"silenceDeprecations": [
"color-functions",
"global-builtin",
"import",
"mixed-decls"
]
}
}
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "8mb",
"maximumError": "8mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all",
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
}
}
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "Reback:build:production"
},
"development": {
"buildTarget": "Reback:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "hyper:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

17957
apiferia/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

89
apiferia/package.json Normal file
View File

@@ -0,0 +1,89 @@
{
"name": "reback",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"format": "prettier --write src/**/*.{ts,html}"
},
"private": true,
"dependencies": {
"@angular/animations": "^19.0.6",
"@angular/cdk": "^19.0.5",
"@angular/common": "^19.0.6",
"@angular/compiler": "^19.0.6",
"@angular/core": "^19.0.6",
"@angular/forms": "^19.0.6",
"@angular/google-maps": "^19.0.2",
"@angular/platform-browser": "^19.0.6",
"@angular/platform-browser-dynamic": "^19.0.6",
"@angular/router": "^19.0.6",
"@ckeditor/ckeditor5-angular": "^9.1.0",
"@ckeditor/ckeditor5-build-classic": "^44.3.0",
"@ctrl/ngx-emoji-mart": "^9.2.0",
"@fullcalendar/angular": "^6.1.17",
"@fullcalendar/bootstrap": "^6.1.17",
"@fullcalendar/core": "^6.1.17",
"@fullcalendar/daygrid": "^6.1.17",
"@fullcalendar/interaction": "^6.1.17",
"@fullcalendar/list": "^6.1.17",
"@fullcalendar/timegrid": "^6.1.17",
"@iconify-json/iconamoon": "^1.2.2",
"@iconify/utils": "^2.3.0",
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
"@ngrx/effects": "^19.1.0",
"@ngrx/store": "^19.1.0",
"@ngrx/store-devtools": "^19.1.0",
"@popperjs/core": "^2.11.8",
"angularx-flatpickr": "^8.1.0",
"apexcharts": "^4.5.0",
"bootstrap": "^5.3.5",
"choices.js": "^11.1.0",
"ckeditor5": "^44.3.0",
"dayjs": "^1.11.13",
"flatpickr": "^4.6.13",
"gumshoejs": "^5.1.2",
"iconify-icon": "^2.3.0",
"jsvectormap": "1.3.3",
"moment": "^2.30.1",
"ng-apexcharts": "^1.15.0",
"ng2-nouislider": "^2.0.0",
"ngrx-store-localstorage": "^19.0.0",
"ngx-clipboard": "^16.0.0",
"ngx-cookie-service": "^19.1.2",
"ngx-dropzone-wrapper": "^17.0.0",
"ngx-mask": "^19.0.6",
"ngx-progressbar": "^14.0.0",
"ngx-quill": "^27.0.1",
"ngx-toastr": "^19.0.0",
"nouislider": "^15.8.1",
"npm": "^11.2.0",
"prettier": "^3.5.3",
"quill": "^2.0.3",
"quill-delta": "^5.1.0",
"rxjs": "~7.8.2",
"simplebar-angular": "3.2.4",
"sweetalert2": "^11.17.2",
"swiper": "^11.2.6",
"tslib": "^2.8.1",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.0.7",
"@angular/cli": "^19.0.7",
"@angular/compiler-cli": "^19.0.0",
"@angular/localize": "^19.0.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.8.3",
"vite": "^6.2.5"
}
}

View File

@@ -0,0 +1,8 @@
<ng-progress
#progressBar
[trickleSpeed]="10"
[speed]="10"
[spinner]="false"
color="#1C84EE"
></ng-progress>
<router-outlet></router-outlet>

0
apiferia/src/app/app.component.scss vendored Normal file
View File

View File

@@ -0,0 +1,29 @@
import { TestBed } from '@angular/core/testing'
import { AppComponent } from './app.component'
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents()
})
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent)
const app = fixture.componentInstance
expect(app).toBeTruthy()
})
it(`should have the 'Reback' title`, () => {
const fixture = TestBed.createComponent(AppComponent)
const app = fixture.componentInstance
// expect(app.title).toEqual('Reback')
})
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent)
fixture.detectChanges()
const compiled = fixture.nativeElement as HTMLElement
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, Reback')
})
})

View File

@@ -0,0 +1,53 @@
import { Component, inject, ViewChild, type OnInit } from '@angular/core'
import { CommonModule } from '@angular/common'
import {
NavigationCancel,
NavigationEnd,
NavigationError,
NavigationStart,
Router,
RouterOutlet,
type Event,
} from '@angular/router'
import { TitleService } from '@core/services/title.service'
import { NgProgressbar, NgProgressRef } from 'ngx-progressbar'
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, NgProgressbar],
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
})
export class AppComponent implements OnInit {
@ViewChild(NgProgressRef) progressBar!: NgProgressRef
private titleService = inject(TitleService)
private router = inject(Router)
constructor() {
this.router.events.subscribe((event: Event) => {
this.checkRouteChange(event)
})
}
ngOnInit(): void {
this.titleService.init()
}
// show Loader when route change
checkRouteChange(routerEvent: Event) {
if (routerEvent instanceof NavigationStart) {
this.progressBar.start()
}
if (
routerEvent instanceof NavigationEnd ||
routerEvent instanceof NavigationCancel ||
routerEvent instanceof NavigationError
) {
setTimeout(() => {
this.progressBar.complete()
}, 200)
}
}
}

View File

@@ -0,0 +1,57 @@
import {
ApplicationConfig,
importProvidersFrom,
isDevMode,
provideExperimentalZonelessChangeDetection,
provideZoneChangeDetection,
} from '@angular/core'
import {
provideRouter,
withInMemoryScrolling,
type InMemoryScrollingFeature,
type InMemoryScrollingOptions,
} from '@angular/router'
import {
HTTP_INTERCEPTORS,
provideHttpClient,
withFetch,
withInterceptorsFromDi,
} from '@angular/common/http'
import { provideStore } from '@ngrx/store'
import { provideStoreDevtools } from '@ngrx/store-devtools'
import { routes } from './app.routes'
import { rootReducer } from './store'
import { localStorageSyncReducer } from './store/layout/layout-reducers'
import { DecimalPipe } from '@angular/common'
import { provideEffects } from '@ngrx/effects'
import { CalendarEffects } from './store/calendar/calendar.effects'
import { FakeBackendProvider } from './helpers/fake-backend'
import { AuthenticationEffects } from './store/authentication/authentication.effects'
import { provideToastr } from 'ngx-toastr'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { BrowserModule } from '@angular/platform-browser'
// scroll
const scrollConfig: InMemoryScrollingOptions = {
scrollPositionRestoration: 'top',
anchorScrolling: 'enabled',
}
const inMemoryScrollingFeatures: InMemoryScrollingFeature =
withInMemoryScrolling(scrollConfig)
export const appConfig: ApplicationConfig = {
providers: [
FakeBackendProvider,
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes, inMemoryScrollingFeatures),
DecimalPipe,
provideStore(rootReducer, { metaReducers: [localStorageSyncReducer] }),
provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
provideEffects(AuthenticationEffects, CalendarEffects),
provideHttpClient(withFetch(), withInterceptorsFromDi()),
importProvidersFrom(BrowserAnimationsModule, BrowserModule),
provideToastr({}),
],
}

View File

@@ -0,0 +1,47 @@
import { RedirectCommand, Router, Routes, type UrlTree } from '@angular/router'
import { AuthLayoutComponent } from './layouts/auth-layout/auth-layout.component'
import { PrivateLayoutComponent } from './layouts/private-layout/private-layout.component'
import { AuthenticationService } from './core/services/auth.service'
import { inject } from '@angular/core'
import { CarViewComponent } from './views/car-view/car-view.component';
export const routes: Routes = [
{
path: '',
redirectTo: 'dashboard/analytics',
pathMatch: 'full',
},
{
path: '',
component: PrivateLayoutComponent,
canActivate: [
() => {
const currentUser = inject(AuthenticationService).session
const router: Router = inject(Router)
if (currentUser) return true
const urlTree: UrlTree = router.parseUrl('/auth/sign-in')
return new RedirectCommand(urlTree, { skipLocationChange: true })
},
],
loadChildren: () =>
import('./views/views.route').then((mod) => mod.VIEW_ROUTES),
},
{
path: '',
component: AuthLayoutComponent,
loadChildren: () =>
import('./views/other-pages/other-page.route').then(
(mod) => mod.OTHER_PAGES_ROUTES
),
},
{
path: 'auth',
component: AuthLayoutComponent,
loadChildren: () =>
import('./views/auth/auth.route').then((mod) => mod.AUTH_ROUTES),
},
{
path: 'cars',
component: CarViewComponent
},
]

View File

@@ -0,0 +1,44 @@
import {
ApexAxisChartSeries,
ApexNonAxisChartSeries,
ApexChart,
ChartComponent,
ApexDataLabels,
ApexPlotOptions,
ApexYAxis,
ApexLegend,
ApexStroke,
ApexXAxis,
ApexFill,
ApexTooltip,
ApexTitleSubtitle,
ApexResponsive,
ApexAnnotations,
ApexGrid,
ApexStates,
ApexMarkers,
ApexTheme,
} from 'ng-apexcharts'
export type ChartOptions = {
series: ApexAxisChartSeries | ApexNonAxisChartSeries
chart: ApexChart
xaxis: ApexXAxis
fill: ApexFill
stroke: ApexStroke
tooltip: ApexTooltip
dataLabels: ApexDataLabels
plotOptions: ApexPlotOptions
markers: ApexMarkers
responsive: ApexResponsive[]
colors: string[]
labels: string[]
annotations: ApexAnnotations
yaxis: ApexYAxis | ApexYAxis[]
grid: ApexGrid
legend: ApexLegend
title: ApexTitleSubtitle
subtitle: ApexTitleSubtitle
states: ApexStates
theme: ApexTheme
}

View File

@@ -0,0 +1,12 @@
type CurrencyType = '₹' | '$' | '€'
export const currency: CurrencyType = '$'
export const currentYear = new Date().getFullYear()
export const credits = {
name: 'Techzaa',
buyLink: '',
}
export const basePath: string = '/'

View File

@@ -0,0 +1,911 @@
export type MenuItem = {
key?: string
label?: string
icon?: string
link?: string
collapsed?: boolean
subMenu?: any
isTitle?: boolean
badge?: any
parentKey?: string
disabled?: boolean
}
export const MENU: MenuItem[] = [
{
key: 'general',
label: 'GENERAL',
isTitle: true,
},
{
key: 'dashboards',
icon: 'iconamoon:home-duotone',
label: 'Dashboards',
collapsed: false,
subMenu: [
{
key: 'dashboard-analytics',
label: 'Analytics',
link: '/dashboard/analytics',
parentKey: 'dashboards',
},
{
key: 'dashboard-finance',
label: 'Finance',
link: '/dashboard/finance',
parentKey: 'dashboards',
},
{
key: 'dashboard-sales',
label: 'Sales',
link: '/dashboard/sales',
parentKey: 'dashboards',
},
],
},
{
key: 'apps',
label: 'APPS',
isTitle: true,
},
{
key: 'ecommerce',
icon: 'iconamoon:shopping-bag-duotone',
label: 'Ecommerce',
collapsed: true,
subMenu: [
{
key: 'ecommerce-products',
label: 'Products',
link: '/ecommerce/products',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-productsdetails',
label: 'Product Details',
link: '/ecommerce/product/1',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-createproduct',
label: 'Create Product',
link: '/ecommerce/create',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-customers',
label: 'Customers',
link: '/ecommerce/customers',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-sellers',
label: 'Sellers',
link: '/ecommerce/sellers',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-orders',
label: 'Orders',
link: '/ecommerce/orders',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-orderdetails',
label: 'Order Details',
link: '/ecommerce/orders/10001',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-inventory',
label: 'Inventory',
link: '/ecommerce/inventory',
parentKey: 'ecommerce',
},
],
},
{
key: 'apps-chat',
icon: 'iconamoon:comment-dots-duotone',
label: 'Chat',
link: '/apps/chat',
},
{
key: 'apps-email',
icon: 'iconamoon:email-duotone',
label: 'Email',
link: '/apps/email',
},
{
key: 'apps-calendar',
icon: 'iconamoon:calendar-1-duotone',
label: 'Calendar',
collapsed: true,
subMenu: [
{
key: 'calendar-schedule',
label: 'Schedule',
link: '/calendar/schedule',
parentKey: 'apps-calendar',
},
{
key: 'calendar-integration',
label: 'Integration',
link: '/calendar/integration',
parentKey: 'apps-calendar',
},
{
key: 'calendar-help',
label: 'Help',
link: '/calendar/help',
parentKey: 'apps-calendar',
},
],
},
{
key: 'apps-todo',
icon: 'iconamoon:ticket-duotone',
label: 'Todo',
link: '/apps/todo',
},
{
key: 'apps-social',
icon: 'iconamoon:squinting-face-duotone',
label: 'Social',
link: '/apps/social',
badge: {
variant: 'danger',
text: 'Hot',
},
},
{
key: 'apps-contacts',
icon: 'iconamoon:profile-circle-duotone',
label: 'Contacts',
link: '/apps/contacts',
},
{
key: 'apps-invoices',
icon: 'iconamoon:invoice-duotone',
label: 'Invoices',
collapsed: true,
subMenu: [
{
key: 'invoices',
label: 'Invoices',
link: '/invoices',
parentKey: 'apps-invoices',
},
{
key: 'invoices-details',
label: 'Invoice Details',
link: '/invoice/RB6985',
parentKey: 'apps-invoices',
},
],
},
{
key: 'feria',
label: 'FERIA DE VEHÍCULOS',
isTitle: true,
},
{
key: 'vehicles',
icon: 'iconamoon:car-duotone',
label: 'Vehículos',
collapsed: true,
subMenu: [
{
key: 'vehicles-list',
label: 'Lista de Vehículos',
link: '/vehicles',
parentKey: 'vehicles',
},
{
key: 'vehicles-create',
label: 'Nuevo Vehículo',
link: '/vehicles/create',
parentKey: 'vehicles',
},
{
key: 'vehicles-import',
label: 'Importar Vehículos',
link: '/vehicles/import',
parentKey: 'vehicles',
},
],
},
{
key: 'finance',
icon: 'iconamoon:certificate-badge-duotone',
label: 'Financiamiento',
collapsed: true,
subMenu: [
{
key: 'finance-applications',
label: 'Aplicaciones',
link: '/finance/applications',
parentKey: 'finance',
},
{
key: 'finance-companies',
label: 'Financieras',
link: '/finance/companies',
parentKey: 'finance',
},
{
key: 'finance-calculator',
label: 'Calculadora',
link: '/finance/calculator',
parentKey: 'finance',
},
],
},
{
key: 'insurance',
icon: 'iconamoon:shield-yes-duotone',
label: 'Seguros',
collapsed: true,
subMenu: [
{
key: 'insurance-quotes',
label: 'Cotizaciones',
link: '/insurance/quotes',
parentKey: 'insurance',
},
{
key: 'insurance-companies',
label: 'Aseguradoras',
link: '/insurance/companies',
parentKey: 'insurance',
},
],
},
{
key: 'sales',
icon: 'iconamoon:shopping-cart-duotone',
label: 'Ventas',
collapsed: true,
subMenu: [
{
key: 'sales-list',
label: 'Lista de Ventas',
link: '/sales',
parentKey: 'sales',
},
{
key: 'sales-reports',
label: 'Reportes',
link: '/sales/reports',
parentKey: 'sales',
},
{
key: 'sales-commissions',
label: 'Comisiones',
link: '/sales/commissions',
parentKey: 'sales',
},
],
},
{
key: 'custom',
label: 'Custom',
isTitle: true,
},
{
key: 'pages',
label: 'Pages',
isTitle: false,
icon: 'iconamoon:copy-duotone',
collapsed: true,
subMenu: [
{
key: 'page-welcome',
label: 'Welcome',
link: '/pages/welcome',
parentKey: 'pages',
},
{
key: 'page-faqs',
label: 'FAQs',
link: '/pages/faqs',
parentKey: 'pages',
},
{
key: 'page-profile',
label: 'Profile',
link: '/pages/profile',
parentKey: 'pages',
},
{
key: 'page-coming-soon',
label: 'Coming Soon',
link: '/coming-soon',
parentKey: 'pages',
},
{
key: 'page-contact-us',
label: 'Contact Us',
link: '/pages/contact-us',
parentKey: 'pages',
},
{
key: 'page-about-us',
label: 'About Us',
link: '/pages/about-us',
parentKey: 'pages',
},
{
key: 'page-our-team',
label: 'Our Team',
link: '/pages/our-team',
parentKey: 'pages',
},
{
key: 'page-timeline',
label: 'Timeline',
link: '/pages/timeline',
parentKey: 'pages',
},
{
key: 'page-pricing',
label: 'Pricing',
link: '/pages/pricing',
parentKey: 'pages',
},
{
key: 'page-maintenance',
label: 'Maintenance',
link: '/maintenance',
parentKey: 'pages',
},
{
key: 'page-404-error',
label: '404 Error',
link: '/error-404',
parentKey: 'pages',
},
{
key: 'page-404-error2',
label: '404 Error 2',
link: '/error-404-2',
parentKey: 'pages',
},
{
key: 'page-error-404-alt',
label: 'Error 404 (alt)',
link: '/pages/error-404-alt',
parentKey: 'pages',
},
],
},
{
key: 'page-authentication',
label: 'Authentication',
isTitle: false,
icon: 'iconamoon:lock-duotone',
collapsed: true,
subMenu: [
{
key: 'sign-in',
label: 'Sign In',
link: '/auth/sign-in',
parentKey: 'page-authentication',
},
{
key: 'sign-in-2',
label: 'Sign In 2',
link: '/auth/sign-in-2',
parentKey: 'page-authentication',
},
{
key: 'signup',
label: 'Sign Up',
link: '/auth/sign-up',
parentKey: 'page-authentication',
},
{
key: 'signup2',
label: 'Sign Up 2',
link: '/auth/sign-up-2',
parentKey: 'page-authentication',
},
{
key: 'reset-pass',
label: 'Reset Password',
link: '/auth/reset-pass',
parentKey: 'page-authentication',
},
{
key: 'reset-pass2',
label: 'Reset Password 2',
link: '/auth/reset-pass-2',
parentKey: 'page-authentication',
},
{
key: 'lock-screen',
label: 'Lock Screen',
link: '/auth/lock-screen',
parentKey: 'page-authentication',
},
{
key: 'lock-screen-2',
label: 'Lock Screen 2',
link: '/auth/lock-screen-2',
parentKey: 'page-authentication',
},
],
},
{
key: 'widgets',
icon: 'iconamoon:gift-duotone',
label: 'Widgets',
link: '/widgets',
badge: {
variant: 'info',
text: '9+',
},
},
{
key: 'components',
label: 'COMPONENTS',
isTitle: true,
},
{
key: 'base-ui',
icon: 'iconamoon:briefcase-duotone',
label: 'Base UI',
collapsed: true,
subMenu: [
{
key: 'base-ui-accordions',
label: 'Accordion',
link: '/ui/accordions',
parentKey: 'base-ui',
},
{
key: 'base-ui-alerts',
label: 'Alerts',
link: '/ui/alerts',
parentKey: 'base-ui',
},
{
key: 'base-ui-avatars',
label: 'Avatar',
link: '/ui/avatars',
parentKey: 'base-ui',
},
{
key: 'base-ui-badges',
label: 'Badge',
link: '/ui/badges',
parentKey: 'base-ui',
},
{
key: 'base-ui-breadcrumb',
label: 'Breadcrumb',
link: '/ui/breadcrumb',
parentKey: 'base-ui',
},
{
key: 'base-ui-buttons',
label: 'Buttons',
link: '/ui/buttons',
parentKey: 'base-ui',
},
{
key: 'base-ui-cards',
label: 'Card',
link: '/ui/cards',
parentKey: 'base-ui',
},
{
key: 'base-ui-carousel',
label: 'Carousel',
link: '/ui/carousel',
parentKey: 'base-ui',
},
{
key: 'base-ui-collapse',
label: 'Collapse',
link: '/ui/collapse',
parentKey: 'base-ui',
},
{
key: 'base-ui-dropdowns',
label: 'Dropdown',
link: '/ui/dropdowns',
parentKey: 'base-ui',
},
{
key: 'base-ui-list-group',
label: 'List Group',
link: '/ui/list-group',
parentKey: 'base-ui',
},
{
key: 'base-ui-modals',
label: 'Modal',
link: '/ui/modals',
parentKey: 'base-ui',
},
{
key: 'base-ui-tabs',
label: 'Tabs',
link: '/ui/tabs',
parentKey: 'base-ui',
},
{
key: 'base-ui-offcanvas',
label: 'Offcanvas',
link: '/ui/offcanvas',
parentKey: 'base-ui',
},
{
key: 'base-ui-pagination',
label: 'Pagination',
link: '/ui/pagination',
parentKey: 'base-ui',
},
{
key: 'base-ui-placeholders',
label: 'Placeholders',
link: '/ui/placeholders',
parentKey: 'base-ui',
},
{
key: 'base-ui-popovers',
label: 'Popovers',
link: '/ui/popovers',
parentKey: 'base-ui',
},
{
key: 'base-ui-progress',
label: 'Progress',
link: '/ui/progress',
parentKey: 'base-ui',
},
{
key: 'base-ui-scrollspy',
label: 'Scrollspy',
link: '/ui/scrollspy',
parentKey: 'base-ui',
},
{
key: 'base-ui-spinners',
label: 'Spinners',
link: '/ui/spinners',
parentKey: 'base-ui',
},
{
key: 'base-ui-toasts',
label: 'Toasts',
link: '/ui/toasts',
parentKey: 'base-ui',
},
{
key: 'base-ui-tooltips',
label: 'Tooltips',
link: '/ui/tooltips',
parentKey: 'base-ui',
},
],
},
{
key: 'advanced-ui',
icon: 'iconamoon:component-duotone',
label: 'Advanced UI',
collapsed: true,
subMenu: [
{
key: 'advanced-ui-ratings',
label: 'Ratings',
link: '/advanced/ratings',
parentKey: 'advanced-ui',
},
{
key: 'advanced-ui-sweet-alert',
label: 'Sweet Alert',
link: '/advanced/alert',
parentKey: 'advanced-ui',
},
{
key: 'advanced-ui-swiper-slider',
label: 'Swiper Slider',
link: '/advanced/swiper',
parentKey: 'advanced-ui',
},
{
key: 'advanced-ui-scrollbar',
label: 'Scrollbar',
link: '/advanced/scrollbar',
parentKey: 'advanced-ui',
},
{
key: 'advanced-ui-toastify',
label: 'Toastify',
link: '/advanced/toastify',
parentKey: 'advanced-ui',
},
],
},
{
key: 'charts',
icon: 'iconamoon:3d-duotone',
label: 'Charts',
collapsed: true,
subMenu: [
{
key: 'charts-area',
label: 'Area',
link: '/charts/area',
parentKey: 'charts',
},
{
key: 'charts-bar',
label: 'Bar',
link: '/charts/bar',
parentKey: 'charts',
},
{
key: 'charts-bubble',
label: 'Bubble',
link: '/charts/bubble',
parentKey: 'charts',
},
{
key: 'charts-candl-stick',
label: 'Candlestick',
link: '/charts/candlestick',
parentKey: 'charts',
},
{
key: 'charts-column',
label: 'Column',
link: '/charts/column',
parentKey: 'charts',
},
{
key: 'charts-heatmap',
label: 'Heatmap',
link: '/charts/heatmap',
parentKey: 'charts',
},
{
key: 'charts-line',
label: 'Line',
link: '/charts/line',
parentKey: 'charts',
},
{
key: 'charts-mixed',
label: 'Mixed',
link: '/charts/mixed',
parentKey: 'charts',
},
{
key: 'charts-timeline',
label: 'Timeline',
link: '/charts/timeline',
parentKey: 'charts',
},
{
key: 'charts-boxplot',
label: 'Boxplot',
link: '/charts/boxplot',
parentKey: 'charts',
},
{
key: 'charts-treemap',
label: 'Treemap',
link: '/charts/treemap',
parentKey: 'charts',
},
{
key: 'charts-pie',
label: 'Pie',
link: '/charts/pie',
parentKey: 'charts',
},
{
key: 'charts-radar',
label: 'Radar',
link: '/charts/radar',
parentKey: 'charts',
},
{
key: 'charts-radial-bar',
label: 'RadialBar',
link: '/charts/radial-bar',
parentKey: 'charts',
},
{
key: 'charts-scatter',
label: 'Scatter',
link: '/charts/scatter',
parentKey: 'charts',
},
{
key: 'charts-polar-area',
label: 'Polar Area',
link: '/charts/polar',
parentKey: 'charts',
},
],
},
{
key: 'forms',
icon: 'iconamoon:cheque-duotone',
label: 'Forms',
collapsed: true,
subMenu: [
{
key: 'forms-basic-elements',
label: 'Basic Elements',
link: '/forms/basic',
parentKey: 'forms',
},
{
key: 'forms-checkbox&radio',
label: 'Checkbox & Radio',
link: '/forms/checkbox',
parentKey: 'forms',
},
{
key: 'forms-choice-select',
label: 'Choice Select',
link: '/forms/select',
parentKey: 'forms',
},
{
key: 'forms-clipboard',
label: 'Clipboard',
link: '/forms/clipboard',
parentKey: 'forms',
},
{
key: 'forms-flat-picker',
label: 'Flatpicker',
link: '/forms/flat-picker',
parentKey: 'forms',
},
{
key: 'forms-validation',
label: 'Validation',
link: '/forms/validation',
parentKey: 'forms',
},
{
key: 'forms-wizard',
label: 'Wizard',
link: '/forms/wizard',
parentKey: 'forms',
},
{
key: 'forms-file-uploads',
label: 'File Upload',
link: '/forms/file-uploads',
parentKey: 'forms',
},
{
key: 'forms-editors',
label: 'Editors',
link: '/forms/editors',
parentKey: 'forms',
},
{
key: 'forms-input-mask',
label: 'Input Mask',
link: '/forms/input-mask',
parentKey: 'forms',
},
{
key: 'forms-slider',
label: 'Slider',
link: '/forms/slider',
parentKey: 'forms',
},
],
},
{
key: 'tables',
icon: 'iconamoon:box-duotone',
label: 'Tables',
collapsed: true,
subMenu: [
{
key: 'tables-basic',
label: 'Basic Tables',
link: '/tables/basic',
parentKey: 'tables',
},
{
key: 'tables-grid-js',
label: 'Datatables',
link: '/tables/datatable',
parentKey: 'tables',
},
],
},
{
key: 'icons',
icon: 'iconamoon:lightning-1-duotone',
label: 'Icons',
collapsed: true,
subMenu: [
{
key: 'icons-boxicons',
label: 'Boxicons',
link: '/icons/boxicons',
parentKey: 'icons',
},
{
key: 'icons-iconamoon',
label: 'IconaMoon Icons',
link: '/icons/iconamoon',
parentKey: 'icons',
},
],
},
{
key: 'maps',
icon: 'iconamoon:location-pin-duotone',
label: 'Maps',
collapsed: true,
subMenu: [
{
key: 'maps-google',
label: 'Google Maps',
link: '/maps/google',
parentKey: 'maps',
},
{
key: 'maps-vector',
label: 'Vector Maps',
link: '/maps/vector',
parentKey: 'maps',
},
],
},
{
key: 'badge-menu',
icon: 'iconamoon:badge-duotone',
label: 'Badge Menu',
badge: {
variant: 'danger',
text: '1',
},
},
{
key: 'menuitem',
icon: 'iconamoon:folder-add-duotone',
label: 'Menu Item',
collapsed: true,
subMenu: [
{
key: 'menu-item-1',
label: 'Menu Item 1',
parentKey: 'menuitem',
},
{
key: 'menu-item-2',
label: 'Menu Item 2',
collapsed: true,
parentKey: 'menuitem',
subMenu: [
{
key: 'menu-sub-item',
label: 'Menu Sub Item',
parentKey: 'menu-item-2',
},
],
},
],
},
{
key: 'disabled-item',
icon: 'iconamoon:unavailable-duotone',
label: 'Disabled Item',
disabled: true,
},
]

View File

@@ -0,0 +1,808 @@
export type MenuItem = {
key?: string
label?: string
icon?: string
link?: string
collapsed?: boolean
subMenu?: any
isTitle?: boolean
badge?: any
parentKey?: string
disabled?: boolean
}
export const MENU: MenuItem[] = [
{
key: 'general',
label: 'GENERAL',
isTitle: true,
},
{
key: 'dashboards',
icon: 'iconamoon:home-duotone',
label: 'Dashboards',
collapsed: false,
subMenu: [
{
key: 'dashboard-analytics',
label: 'Analytics',
link: '/dashboard/analytics',
parentKey: 'dashboards',
},
{
key: 'dashboard-finance',
label: 'Finance',
link: '/dashboard/finance',
parentKey: 'dashboards',
},
{
key: 'dashboard-sales',
label: 'Sales',
link: '/dashboard/sales',
parentKey: 'dashboards',
},
],
},
{
key: 'apps',
label: 'APPS',
isTitle: true,
},
{
key: 'ecommerce',
icon: 'iconamoon:shopping-bag-duotone',
label: 'Ecommerce',
collapsed: true,
subMenu: [
{
key: 'ecommerce-products',
label: 'Products',
link: '/ecommerce/products',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-productsdetails',
label: 'Product Details',
link: '/ecommerce/product/1',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-createproduct',
label: 'Create Product',
link: '/ecommerce/create',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-customers',
label: 'Customers',
link: '/ecommerce/customers',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-sellers',
label: 'Sellers',
link: '/ecommerce/sellers',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-orders',
label: 'Orders',
link: '/ecommerce/orders',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-orderdetails',
label: 'Order Details',
link: '/ecommerce/orders/10001',
parentKey: 'ecommerce',
},
{
key: 'ecommerce-inventory',
label: 'Inventory',
link: '/ecommerce/inventory',
parentKey: 'ecommerce',
},
],
},
{
key: 'apps-chat',
icon: 'iconamoon:comment-dots-duotone',
label: 'Chat',
link: '/apps/chat',
},
{
key: 'apps-email',
icon: 'iconamoon:email-duotone',
label: 'Email',
link: '/apps/email',
},
{
key: 'apps-calendar',
icon: 'iconamoon:calendar-1-duotone',
label: 'Calendar',
collapsed: true,
subMenu: [
{
key: 'calendar-schedule',
label: 'Schedule',
link: '/calendar/schedule',
parentKey: 'apps-calendar',
},
{
key: 'calendar-integration',
label: 'Integration',
link: '/calendar/integration',
parentKey: 'apps-calendar',
},
{
key: 'calendar-help',
label: 'Help',
link: '/calendar/help',
parentKey: 'apps-calendar',
},
],
},
{
key: 'apps-todo',
icon: 'iconamoon:ticket-duotone',
label: 'Todo',
link: '/apps/todo',
},
{
key: 'apps-social',
icon: 'iconamoon:squinting-face-duotone',
label: 'Social',
link: '/apps/social',
badge: {
variant: 'danger',
text: 'Hot',
},
},
{
key: 'apps-contacts',
icon: 'iconamoon:profile-circle-duotone',
label: 'Contacts',
link: '/apps/contacts',
},
{
key: 'apps-invoices',
icon: 'iconamoon:invoice-duotone',
label: 'Invoices',
collapsed: true,
subMenu: [
{
key: 'invoices',
label: 'Invoices',
link: '/invoices',
parentKey: 'apps-invoices',
},
{
key: 'invoices-details',
label: 'Invoice Details',
link: '/invoice/RB6985',
parentKey: 'apps-invoices',
},
],
},
{
key: 'custom',
label: 'Custom',
isTitle: true,
},
{
key: 'pages',
label: 'Pages',
isTitle: false,
icon: 'iconamoon:copy-duotone',
collapsed: true,
subMenu: [
{
key: 'page-welcome',
label: 'Welcome',
link: '/pages/welcome',
parentKey: 'pages',
},
{
key: 'page-faqs',
label: 'FAQs',
link: '/pages/faqs',
parentKey: 'pages',
},
{
key: 'page-profile',
label: 'Profile',
link: '/pages/profile',
parentKey: 'pages',
},
{
key: 'page-coming-soon',
label: 'Coming Soon',
link: '/coming-soon',
parentKey: 'pages',
},
{
key: 'page-contact-us',
label: 'Contact Us',
link: '/pages/contact-us',
parentKey: 'pages',
},
{
key: 'page-about-us',
label: 'About Us',
link: '/pages/about-us',
parentKey: 'pages',
},
{
key: 'page-our-team',
label: 'Our Team',
link: '/pages/our-team',
parentKey: 'pages',
},
{
key: 'page-timeline',
label: 'Timeline',
link: '/pages/timeline',
parentKey: 'pages',
},
{
key: 'page-pricing',
label: 'Pricing',
link: '/pages/pricing',
parentKey: 'pages',
},
{
key: 'page-maintenance',
label: 'Maintenance',
link: '/maintenance',
parentKey: 'pages',
},
{
key: 'page-404-error',
label: '404 Error',
link: '/error-404',
parentKey: 'pages',
},
{
key: 'page-404-error2',
label: '404 Error 2',
link: '/error-404-2',
parentKey: 'pages',
},
{
key: 'page-error-404-alt',
label: 'Error 404 (alt)',
link: '/pages/error-404-alt',
parentKey: 'pages',
},
],
},
{
key: 'page-authentication',
label: 'Authentication',
isTitle: false,
icon: 'iconamoon:lock-duotone',
collapsed: true,
subMenu: [
{
key: 'sign-in',
label: 'Sign In',
link: '/auth/sign-in',
parentKey: 'page-authentication',
},
{
key: 'sign-in-2',
label: 'Sign In 2',
link: '/auth/sign-in-2',
parentKey: 'page-authentication',
},
{
key: 'signup',
label: 'Sign Up',
link: '/auth/sign-up',
parentKey: 'page-authentication',
},
{
key: 'signup2',
label: 'Sign Up 2',
link: '/auth/sign-up-2',
parentKey: 'page-authentication',
},
{
key: 'reset-pass',
label: 'Reset Password',
link: '/auth/reset-pass',
parentKey: 'page-authentication',
},
{
key: 'reset-pass2',
label: 'Reset Password 2',
link: '/auth/reset-pass-2',
parentKey: 'page-authentication',
},
{
key: 'lock-screen',
label: 'Lock Screen',
link: '/auth/lock-screen',
parentKey: 'page-authentication',
},
{
key: 'lock-screen-2',
label: 'Lock Screen 2',
link: '/auth/lock-screen-2',
parentKey: 'page-authentication',
},
],
},
{
key: 'widgets',
icon: 'iconamoon:gift-duotone',
label: 'Widgets',
link: '/widgets',
badge: {
variant: 'info',
text: '9+',
},
},
{
key: 'components',
label: 'COMPONENTS',
isTitle: true,
},
{
key: 'base-ui',
icon: 'iconamoon:briefcase-duotone',
label: 'Base UI',
collapsed: true,
subMenu: [
{
key: 'base-ui-accordions',
label: 'Accordion',
link: '/ui/accordions',
parentKey: 'base-ui',
},
{
key: 'base-ui-alerts',
label: 'Alerts',
link: '/ui/alerts',
parentKey: 'base-ui',
},
{
key: 'base-ui-avatars',
label: 'Avatar',
link: '/ui/avatars',
parentKey: 'base-ui',
},
{
key: 'base-ui-badges',
label: 'Badge',
link: '/ui/badges',
parentKey: 'base-ui',
},
{
key: 'base-ui-breadcrumb',
label: 'Breadcrumb',
link: '/ui/breadcrumb',
parentKey: 'base-ui',
},
{
key: 'base-ui-buttons',
label: 'Buttons',
link: '/ui/buttons',
parentKey: 'base-ui',
},
{
key: 'base-ui-cards',
label: 'Card',
link: '/ui/cards',
parentKey: 'base-ui',
},
{
key: 'base-ui-carousel',
label: 'Carousel',
link: '/ui/carousel',
parentKey: 'base-ui',
},
{
key: 'base-ui-collapse',
label: 'Collapse',
link: '/ui/collapse',
parentKey: 'base-ui',
},
{
key: 'base-ui-dropdowns',
label: 'Dropdown',
link: '/ui/dropdowns',
parentKey: 'base-ui',
},
{
key: 'base-ui-list-group',
label: 'List Group',
link: '/ui/list-group',
parentKey: 'base-ui',
},
{
key: 'base-ui-modals',
label: 'Modal',
link: '/ui/modals',
parentKey: 'base-ui',
},
{
key: 'base-ui-tabs',
label: 'Tabs',
link: '/ui/tabs',
parentKey: 'base-ui',
},
{
key: 'base-ui-offcanvas',
label: 'Offcanvas',
link: '/ui/offcanvas',
parentKey: 'base-ui',
},
{
key: 'base-ui-pagination',
label: 'Pagination',
link: '/ui/pagination',
parentKey: 'base-ui',
},
{
key: 'base-ui-placeholders',
label: 'Placeholders',
link: '/ui/placeholders',
parentKey: 'base-ui',
},
{
key: 'base-ui-popovers',
label: 'Popovers',
link: '/ui/popovers',
parentKey: 'base-ui',
},
{
key: 'base-ui-progress',
label: 'Progress',
link: '/ui/progress',
parentKey: 'base-ui',
},
{
key: 'base-ui-scrollspy',
label: 'Scrollspy',
link: '/ui/scrollspy',
parentKey: 'base-ui',
},
{
key: 'base-ui-spinners',
label: 'Spinners',
link: '/ui/spinners',
parentKey: 'base-ui',
},
{
key: 'base-ui-toasts',
label: 'Toasts',
link: '/ui/toasts',
parentKey: 'base-ui',
},
{
key: 'base-ui-tooltips',
label: 'Tooltips',
link: '/ui/tooltips',
parentKey: 'base-ui',
},
],
},
{
key: 'advanced-ui',
icon: 'iconamoon:component-duotone',
label: 'Advanced UI',
collapsed: true,
subMenu: [
{
key: 'advanced-ui-ratings',
label: 'Ratings',
link: '/advanced/ratings',
parentKey: 'advanced-ui',
},
{
key: 'advanced-ui-sweet-alert',
label: 'Sweet Alert',
link: '/advanced/alert',
parentKey: 'advanced-ui',
},
{
key: 'advanced-ui-swiper-slider',
label: 'Swiper Slider',
link: '/advanced/swiper',
parentKey: 'advanced-ui',
},
{
key: 'advanced-ui-scrollbar',
label: 'Scrollbar',
link: '/advanced/scrollbar',
parentKey: 'advanced-ui',
},
{
key: 'advanced-ui-toastify',
label: 'Toastify',
link: '/advanced/toastify',
parentKey: 'advanced-ui',
},
],
},
{
key: 'charts',
icon: 'iconamoon:3d-duotone',
label: 'Charts',
collapsed: true,
subMenu: [
{
key: 'charts-area',
label: 'Area',
link: '/charts/area',
parentKey: 'charts',
},
{
key: 'charts-bar',
label: 'Bar',
link: '/charts/bar',
parentKey: 'charts',
},
{
key: 'charts-bubble',
label: 'Bubble',
link: '/charts/bubble',
parentKey: 'charts',
},
{
key: 'charts-candl-stick',
label: 'Candlestick',
link: '/charts/candlestick',
parentKey: 'charts',
},
{
key: 'charts-column',
label: 'Column',
link: '/charts/column',
parentKey: 'charts',
},
{
key: 'charts-heatmap',
label: 'Heatmap',
link: '/charts/heatmap',
parentKey: 'charts',
},
{
key: 'charts-line',
label: 'Line',
link: '/charts/line',
parentKey: 'charts',
},
{
key: 'charts-mixed',
label: 'Mixed',
link: '/charts/mixed',
parentKey: 'charts',
},
{
key: 'charts-timeline',
label: 'Timeline',
link: '/charts/timeline',
parentKey: 'charts',
},
{
key: 'charts-boxplot',
label: 'Boxplot',
link: '/charts/boxplot',
parentKey: 'charts',
},
{
key: 'charts-treemap',
label: 'Treemap',
link: '/charts/treemap',
parentKey: 'charts',
},
{
key: 'charts-pie',
label: 'Pie',
link: '/charts/pie',
parentKey: 'charts',
},
{
key: 'charts-radar',
label: 'Radar',
link: '/charts/radar',
parentKey: 'charts',
},
{
key: 'charts-radial-bar',
label: 'RadialBar',
link: '/charts/radial-bar',
parentKey: 'charts',
},
{
key: 'charts-scatter',
label: 'Scatter',
link: '/charts/scatter',
parentKey: 'charts',
},
{
key: 'charts-polar-area',
label: 'Polar Area',
link: '/charts/polar',
parentKey: 'charts',
},
],
},
{
key: 'forms',
icon: 'iconamoon:cheque-duotone',
label: 'Forms',
collapsed: true,
subMenu: [
{
key: 'forms-basic-elements',
label: 'Basic Elements',
link: '/forms/basic',
parentKey: 'forms',
},
{
key: 'forms-checkbox&radio',
label: 'Checkbox & Radio',
link: '/forms/checkbox',
parentKey: 'forms',
},
{
key: 'forms-choice-select',
label: 'Choice Select',
link: '/forms/select',
parentKey: 'forms',
},
{
key: 'forms-clipboard',
label: 'Clipboard',
link: '/forms/clipboard',
parentKey: 'forms',
},
{
key: 'forms-flat-picker',
label: 'Flatpicker',
link: '/forms/flat-picker',
parentKey: 'forms',
},
{
key: 'forms-validation',
label: 'Validation',
link: '/forms/validation',
parentKey: 'forms',
},
{
key: 'forms-wizard',
label: 'Wizard',
link: '/forms/wizard',
parentKey: 'forms',
},
{
key: 'forms-file-uploads',
label: 'File Upload',
link: '/forms/file-uploads',
parentKey: 'forms',
},
{
key: 'forms-editors',
label: 'Editors',
link: '/forms/editors',
parentKey: 'forms',
},
{
key: 'forms-input-mask',
label: 'Input Mask',
link: '/forms/input-mask',
parentKey: 'forms',
},
{
key: 'forms-slider',
label: 'Slider',
link: '/forms/slider',
parentKey: 'forms',
},
],
},
{
key: 'tables',
icon: 'iconamoon:box-duotone',
label: 'Tables',
collapsed: true,
subMenu: [
{
key: 'tables-basic',
label: 'Basic Tables',
link: '/tables/basic',
parentKey: 'tables',
},
{
key: 'tables-grid-js',
label: 'Datatables',
link: '/tables/datatable',
parentKey: 'tables',
},
],
},
{
key: 'icons',
icon: 'iconamoon:lightning-1-duotone',
label: 'Icons',
collapsed: true,
subMenu: [
{
key: 'icons-boxicons',
label: 'Boxicons',
link: '/icons/boxicons',
parentKey: 'icons',
},
{
key: 'icons-iconamoon',
label: 'IconaMoon Icons',
link: '/icons/iconamoon',
parentKey: 'icons',
},
],
},
{
key: 'maps',
icon: 'iconamoon:location-pin-duotone',
label: 'Maps',
collapsed: true,
subMenu: [
{
key: 'maps-google',
label: 'Google Maps',
link: '/maps/google',
parentKey: 'maps',
},
{
key: 'maps-vector',
label: 'Vector Maps',
link: '/maps/vector',
parentKey: 'maps',
},
],
},
{
key: 'badge-menu',
icon: 'iconamoon:badge-duotone',
label: 'Badge Menu',
badge: {
variant: 'danger',
text: '1',
},
},
{
key: 'menuitem',
icon: 'iconamoon:folder-add-duotone',
label: 'Menu Item',
collapsed: true,
subMenu: [
{
key: 'menu-item-1',
label: 'Menu Item 1',
parentKey: 'menuitem',
},
{
key: 'menu-item-2',
label: 'Menu Item 2',
collapsed: true,
parentKey: 'menuitem',
subMenu: [
{
key: 'menu-sub-item',
label: 'Menu Sub Item',
parentKey: 'menu-item-2',
},
],
},
],
},
{
key: 'disabled-item',
icon: 'iconamoon:unavailable-duotone',
label: 'Disabled Item',
disabled: true,
},
]

View File

@@ -0,0 +1,7 @@
// shuffle chart series
export function shuffleArray(array: any[]): void {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[array[i], array[j]] = [array[j], array[i]]
}
}

View File

@@ -0,0 +1,129 @@
import { Component, Input } from '@angular/core'
import {
DROPZONE_CONFIG,
DropzoneConfigInterface,
DropzoneModule,
} from 'ngx-dropzone-wrapper'
type UploadedFile = {
name: string
size: number
type: string
dataURL?: string
}
const DEFAULT_DROPZONE_CONFIG: DropzoneConfigInterface = {
// Change this to your upload POST address:
url: 'https://httpbin.org/post',
maxFilesize: 50,
acceptedFiles: 'image/*',
}
@Component({
selector: 'FileUploader',
standalone: true,
imports: [DropzoneModule],
template: `
<dropzone
class="dropzone"
[config]="dropzoneConfig"
[message]="dropzone"
(success)="onUploadSuccess($event)"
></dropzone>
@if (showPreview && uploadedFiles) {
<ul class="list-unstyled mb-0" id="dropzone-preview">
@for (file of uploadedFiles; track $index) {
<li class="mt-2" id="dropzone-preview-list">
<div class="border rounded">
<div class="d-flex align-items-center p-2">
<div class="flex-shrink-0 me-3">
<div class="avatar-sm bg-light rounded">
<img
data-dz-thumbnail
[src]="file.dataURL"
class="img-fluid rounded d-block"
src="#"
alt="Dropzone-Image"
/>
</div>
</div>
<div class="flex-grow-1">
<div class="pt-1">
<h5 class="fs-14 mb-1" data-dz-name>
&nbsp; {{ file.name }}
</h5>
<p class="fs-13 text-muted mb-0" data-dz-size>
{{ file.size }}
</p>
<strong
class="error text-danger"
data-dz-errormessage
></strong>
</div>
</div>
<div class="flex-shrink-0 ms-3">
<button
(click)="removeFile($index)"
data-dz-remove
class="btn btn-sm btn-danger"
>
Delete
</button>
</div>
</div>
</div>
</li>
}
</ul>
}
`,
providers: [
{
provide: DROPZONE_CONFIG,
useValue: DEFAULT_DROPZONE_CONFIG,
},
],
})
export class FileUploaderComponent {
@Input() showPreview: boolean = false
dropzone = `<div class="dz-message needsclick">
<i class="h1 bx bx-cloud-upload"></i>
<h3 class="mb-0">
Drop files here or click to
upload.
</h3>
<span class="text-muted fs-13">
(This is just a demo
dropzone. Selected files are
<strong>not</strong>
actually uploaded.)
</span>
</div>`
dropzoneConfig: DropzoneConfigInterface = {
url: 'https://httpbin.org/post',
maxFilesize: 50,
clickable: true,
addRemoveLinks: true,
}
uploadedFiles: any[] = []
ngOnInit(): void {
if (this.showPreview == true) {
this.dropzoneConfig.previewsContainer = false
}
}
// File Upload
imageURL: string = ''
onUploadSuccess(event: UploadedFile[]) {
setTimeout(() => {
this.uploadedFiles.push(event[0])
}, 0)
}
// File Remove
removeFile(index: number) {
this.uploadedFiles.splice(index, 1)
}
}

View File

@@ -0,0 +1 @@
export { FileUploaderComponent } from './file-uploader.component'

View File

@@ -0,0 +1,46 @@
import { CommonModule } from '@angular/common'
import { Component, Input } from '@angular/core'
@Component({
selector: 'app-logo-box',
standalone: true,
imports: [CommonModule],
template: `
<div [class]="className">
<a href="index.html" class="logo-dark">
<img
src="assets/images/logo-sm.png"
[ngClass]="className == 'logo-box' ? 'logo-sm' : 'me-1'"
[height]="logoHeight"
alt="logo sm"
/>
<img
src="assets/images/logo-dark.png"
[ngClass]="className == 'logo-box' ? 'logo-lg' : ''"
[height]="height"
alt="logo dark"
/>
</a>
<a href="index.html" class="logo-light">
<img
src="assets/images/logo-sm.png"
[ngClass]="className == 'logo-box' ? 'logo-sm' : 'me-1'"
[height]="logoHeight"
alt="logo sm"
/>
<img
src="assets/images/logo-light.png"
[ngClass]="className == 'logo-box' ? 'logo-lg' : ''"
[height]="height"
alt="logo light"
/>
</a>
</div>
`,
})
export class LogoBoxComponent {
@Input() className: string = ''
@Input() height: string = ''
@Input() logoHeight: string = ''
}

View File

@@ -0,0 +1,25 @@
import { Component, Input } from '@angular/core'
@Component({
selector: 'app-page-title',
standalone: true,
template: `
<div class="row">
<div class="col-12">
<div class="page-title-box">
<h4 class="mb-0 fw-semibold">{{ subtitle }}</h4>
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item">
<a href="javascript: void(0);">{{ title }}</a>
</li>
<li class="breadcrumb-item active">{{ subtitle }}</li>
</ol>
</div>
</div>
</div>
`,
})
export class PageTitleComponent {
@Input() title: string = ''
@Input() subtitle: string = ''
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SocialBtnComponent } from './social-btn.component'
describe('SocialBtnComponent', () => {
let component: SocialBtnComponent
let fixture: ComponentFixture<SocialBtnComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SocialBtnComponent],
}).compileComponents()
fixture = TestBed.createComponent(SocialBtnComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,24 @@
import { Component } from '@angular/core'
@Component({
selector: 'app-social-btn',
standalone: true,
imports: [],
template: `
<p class="mt-3 fw-semibold no-span">OR sign with</p>
<div class="text-center">
<a href="javascript:void(0);" class="btn btn-light shadow-none me-1"
><i class="bx bxl-google fs-20"></i
></a>
<a href="javascript:void(0);" class="btn btn-light shadow-none me-1"
><i class="bx bxl-facebook fs-20"></i
></a>
<a href="javascript:void(0);" class="btn btn-light shadow-none"
><i class="bx bxl-github fs-20"></i
></a>
</div>
`,
styles: ``,
})
export class SocialBtnComponent {}

View File

@@ -0,0 +1,20 @@
<div class="row">
@for (data of stateData; track $index; let last = $last) {
<div class="col-xl col-lg-4 col-md-6">
<div class="card">
<div class="card-body overflow-hidden position-relative">
<iconify-icon
[icon]="data.icon"
class="fs-36 text-{{ data.iconColor }}"
></iconify-icon>
<h3 class="mb-0 fw-bold mt-3 mb-1">${{ data.amount }}k</h3>
<p class="text-muted">{{ data.title }}</p>
<span class="badge fs-12 badge-soft-{{ data.badgeColor }}"
><i class="ti ti-arrow-badge-up"></i> {{ data.badge }}%</span
>
<i class="{{ data.badgeIcon }} widget-icon"></i>
</div>
</div>
</div>
}
</div>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { StateCardComponent } from './state-card.component'
describe('StateCardComponent', () => {
let component: StateCardComponent
let fixture: ComponentFixture<StateCardComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [StateCardComponent],
}).compileComponents()
fixture = TestBed.createComponent(StateCardComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,58 @@
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
@Component({
selector: 'state-card',
standalone: true,
imports: [],
templateUrl: './state-card.component.html',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class StateCardComponent {
stateData = [
{
icon: 'iconamoon:shopping-card-add-duotone',
iconColor: 'info',
amount: '59.6',
title: 'Total Sales',
badge: '8.72',
badgeColor: 'success',
badgeIcon: 'bx bx-doughnut-chart',
},
{
icon: 'iconamoon:link-external-duotone',
iconColor: 'success',
amount: '24.03',
title: 'Total Expenses',
badge: '3.28',
badgeColor: 'danger',
badgeIcon: 'bx bx-bar-chart-alt-2',
},
{
icon: 'iconamoon:store-duotone',
iconColor: 'purple',
amount: '48.7',
title: 'Investments',
badge: '5.69',
badgeColor: 'danger',
badgeIcon: 'bx bx-building-house',
},
{
icon: 'iconamoon:gift-duotone',
iconColor: 'orange',
amount: '11.3',
title: 'Profit',
badge: '10.58',
badgeColor: 'success',
badgeIcon: 'bx bx-bowl-hot',
},
{
icon: 'iconamoon:certificate-badge-duotone',
iconColor: 'warning',
amount: '5.06',
title: 'Savings',
badge: '8.72',
badgeColor: 'success',
badgeIcon: 'bx bx-cricket-ball',
},
]
}

View File

@@ -0,0 +1,24 @@
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core'
import type { SwiperOptions } from 'swiper/types/swiper-options'
@Directive({
selector: 'swiper-container',
standalone: true,
})
export class SwiperDirective implements AfterViewInit {
private readonly swiperElement: HTMLElement
@Input('config') config?: SwiperOptions
constructor(
private el: ElementRef<HTMLElement & { initialize: () => void }>
) {
this.swiperElement = el.nativeElement
}
ngAfterViewInit() {
Object.assign(this.el.nativeElement, this.config)
this.el.nativeElement.initialize()
}
}

View File

@@ -0,0 +1,14 @@
<div class="card docs-nav">
<ul class="nav bg-transparent flex-column">
@for (item of linkList; track $index) {
<li class="nav-item">
<a
href="{{ item.link }}"
(click)="scrollToSection($event, item.link)"
class="nav-link"
>{{ item.label }}</a
>
</li>
}
</ul>
</div>

View File

@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { UIExamplesListComponent } from './ui-examples-list.component'
describe('UIExamplesListComponent', () => {
let component: UIExamplesListComponent
let fixture: ComponentFixture<UIExamplesListComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UIExamplesListComponent],
}).compileComponents()
fixture = TestBed.createComponent(UIExamplesListComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,24 @@
import { AfterViewInit, Component, Input } from '@angular/core'
import Gumshoe from 'gumshoejs'
@Component({
selector: 'ui-examples-list',
standalone: true,
templateUrl: './ui-examples-list.component.html',
})
export class UIExamplesListComponent implements AfterViewInit {
@Input() linkList: { label: string; link: string }[] | undefined
ngAfterViewInit() {
if (document.querySelector('.docs-nav a')) new Gumshoe('.docs-nav a')
}
scrollToSection(event: Event, link: string) {
event.preventDefault()
const targetId = link.substring(1)
const targetElement = document.getElementById(targetId)
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
}

View File

@@ -0,0 +1,35 @@
import { AfterViewInit, Component, Input, OnDestroy } from '@angular/core'
declare global {
interface Window {
jsVectorMap?: any
}
}
@Component({
selector: 'app-world-vector-map',
standalone: true,
template:
'<div [id]="mapId" [style.width]="width" [style.height]="height"></div>',
})
export class WorldVectorMapComponent implements AfterViewInit {
@Input() width: string = ''
@Input() height: string = ''
@Input() options: Record<string, unknown> = {}
@Input() type: string = ''
@Input() mapId: string = 'map'
constructor() {}
ngOnInit(): void {}
ngAfterViewInit(): void {
setTimeout(() => {
new (window as Window).jsVectorMap({
selector: '#' + this.mapId,
map: this.type,
...this.options,
})
}, 200)
}
}

View File

@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { AuthenticationService } from '../services/auth.service';
@Injectable({ providedIn: 'root' })
export class RoleGuard implements CanActivate {
constructor(
private router: Router,
private authService: AuthenticationService
) {}
canActivate(route: ActivatedRouteSnapshot): boolean {
const requiredRoles = route.data['roles'] as string[];
if (!requiredRoles || requiredRoles.length === 0) {
return true;
}
if (this.authService.hasRole(requiredRoles)) {
return true;
}
// No tiene el rol requerido
this.router.navigate(['/']);
return false;
}
}

View File

@@ -0,0 +1,45 @@
import { Injectable } from '@angular/core'
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpErrorResponse
} from '@angular/common/http'
import { Observable, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'
import { Router } from '@angular/router'
import { Store } from '@ngrx/store'
import { logout } from '@/app/store/authentication/authentication.actions'
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(
private router: Router,
private store: Store
) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Obtener el token
const token = localStorage.getItem('access_token')
// Si hay token, agregarlo al header
if (token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
})
}
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
// Token expirado o inválido
this.store.dispatch(logout())
}
return throwError(() => error)
})
)
}
}

View File

@@ -0,0 +1,62 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Vehicle {
id: string;
dealerId: string;
brand: string;
model: string;
year: number;
mileage: number;
condition: string;
price: number;
status: string;
qrCode?: string;
viewsCount: number;
scansCount: number;
createdAt?: Date;
updatedAt?: Date;
}
@Injectable({
providedIn: 'root'
})
export class VehiclesService {
private endpoint = '/api/vehicles';
constructor(private http: HttpClient) {}
getAll(): Observable<Vehicle[]> {
return this.http.get<Vehicle[]>(this.endpoint);
}
getById(id: string): Observable<Vehicle> {
return this.http.get<Vehicle>(`${this.endpoint}/${id}`);
}
create(vehicle: Partial<Vehicle>): Observable<Vehicle> {
return this.http.post<Vehicle>(this.endpoint, vehicle);
}
update(id: string, vehicle: Partial<Vehicle>): Observable<Vehicle> {
return this.http.patch<Vehicle>(`${this.endpoint}/${id}`, vehicle);
}
delete(id: string): Observable<void> {
return this.http.delete<void>(`${this.endpoint}/${id}`);
}
// Métodos adicionales específicos de vehículos
getByDealer(dealerId: string): Observable<Vehicle[]> {
return this.http.get<Vehicle[]>(`${this.endpoint}/dealer/${dealerId}`);
}
generateQR(id: string): Observable<{qrCode: string}> {
return this.http.post<{qrCode: string}>(`${this.endpoint}/${id}/generate-qr`, {});
}
updateStatus(id: string, status: string): Observable<Vehicle> {
return this.http.patch<Vehicle>(`${this.endpoint}/${id}/status`, { status });
}
}

View File

@@ -0,0 +1,61 @@
import { Injectable, inject } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { map } from 'rxjs/operators'
import { CookieService } from 'ngx-cookie-service'
import { User } from '@/app/store/authentication/auth.model'
@Injectable({ providedIn: 'root' })
export class AuthenticationService {
user: User | null = null
public readonly authSessionKey = '_REBACK_AUTH_SESSION_KEY_'
private cookieService = inject(CookieService)
constructor(private http: HttpClient) {
// Recuperar usuario si existe
const savedUser = localStorage.getItem('currentUser');
if (savedUser) {
this.user = JSON.parse(savedUser);
}
}
login(email: string, password: string) {
return this.http.post<User>(`/api/login`, { email, password }).pipe(
map((user) => {
if (user && user.token) {
this.user = user
this.saveSession(user.token)
}
return user
})
)
}
logout(): void {
this.removeSession()
localStorage.removeItem('access_token')
localStorage.removeItem('currentUser')
this.user = null
}
get session(): string {
return this.cookieService.get(this.authSessionKey) || localStorage.getItem('access_token') || ''
}
saveSession(token: string): void {
this.cookieService.set(this.authSessionKey, token)
localStorage.setItem('access_token', token)
}
removeSession(): void {
this.cookieService.delete(this.authSessionKey)
}
getCurrentUser(): User | null {
return this.user || JSON.parse(localStorage.getItem('currentUser') || '{}')
}
hasRole(roles: string[]): boolean {
const user = this.getCurrentUser();
return user && user.role ? roles.includes(user.role) : false;
}
}

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core'
import { Observable, of } from 'rxjs'
import { defaultEvents } from '@/app/store/calendar/data'
import type { EventInput } from '@fullcalendar/core'
@Injectable({ providedIn: 'root' })
export class CrudService {
constructor() {}
/***
* Get
*/
fetchCalendarEvents(): Observable<EventInput[]> {
return of(defaultEvents)
}
addCalendarEvents(newData: EventInput): Observable<EventInput[]> {
let newEvents = [...defaultEvents, newData] // Create a new array by spreading defaultEvents and adding newData
return of(newEvents)
}
updateCalendarEvents(updatedData: EventInput): Observable<EventInput[]> {
const index = defaultEvents.findIndex((item) => item.id === updatedData.id)
let updatedEvents = defaultEvents.slice()
if (index !== -1) {
updatedEvents[index] = updatedData
}
return of(updatedEvents)
}
deleteCalendarEvents(id: string): Observable<EventInput[]> {
return of(defaultEvents.filter((item) => item.id !== id))
}
}

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core'
@Injectable({
providedIn: 'root',
})
export class PaginationService {
page = 1
startIndex: number = 0
endIndex: number = 0
constructor() {}
refreshData(displayList: any[], data: any[], pageSize: number) {
this.startIndex = (this.page - 1) * pageSize + 1
this.endIndex = (this.page - 1) * pageSize + pageSize
displayList = data
.map((item: any, i: number) => ({ id: i + 1, ...item }))
.slice(this.startIndex - 1, this.endIndex)
return displayList
}
searchTerm(displayList: any[], data: any[], searchQuery: string) {
if (searchQuery) {
displayList = data.filter((item) =>
Object.values(item).some((value: any) =>
value.toString().toLowerCase().includes(searchQuery.toLowerCase())
)
)
} else {
displayList = data
}
return displayList
}
}

View File

@@ -0,0 +1,182 @@
import type { SortDirection } from '@/app/directive/sortable.directive'
import { DecimalPipe } from '@angular/common'
import { inject, Injectable, PipeTransform } from '@angular/core'
import { BehaviorSubject, Observable, of, Subject } from 'rxjs'
import { debounceTime, delay, switchMap, tap } from 'rxjs/operators'
interface SearchResult<T> {
items: T[]
total: number
}
interface State<T> {
page: number
startIndex: number
endIndex: number
pageSize: number
searchTerm: string
sortColumn: keyof T | ''
sortDirection: SortDirection
}
function matches<T>(items: any, term: string, searchFields: (keyof T)[]) {
if (!term) return true
term = term.toLowerCase()
for (const field of searchFields) {
const value = (items[field] as unknown as string)?.toString().toLowerCase()
if (value?.includes(term)) {
return true
}
}
return false
}
function compare<T>(v1: T, v2: T): number {
return v1 < v2 ? -1 : v1 > v2 ? 1 : 0
}
@Injectable({
providedIn: 'root',
})
export class TableService<T extends {}> {
private _loading$ = new BehaviorSubject<boolean>(true)
private _search$ = new Subject<void>()
private _items$ = new BehaviorSubject<T[]>([])
private _total$ = new BehaviorSubject<number>(0)
items: T[] = []
private _state: State<T> = {
page: 1,
pageSize: 10,
searchTerm: '',
startIndex: 1,
endIndex: 10,
sortColumn: '',
sortDirection: '',
}
public pipe = inject(DecimalPipe)
constructor() {
this._search$
.pipe(
tap(() => this._loading$.next(true)),
debounceTime(200),
switchMap(() => this._search()),
delay(0),
tap(() => this._loading$.next(false))
)
.subscribe((result) => {
this._items$.next(result.items)
this._total$.next(result.total)
})
this._search$.next()
}
get items$(): Observable<T[]> {
return this._items$.asObservable()
}
get total$(): Observable<number> {
return this._total$.asObservable()
}
get loading$(): Observable<boolean> {
return this._loading$.asObservable()
}
get page(): number {
return this._state.page
}
get startIndex(): number {
return this._state.startIndex
}
get endIndex(): number {
return this._state.endIndex
}
get pageSize(): number {
return this._state.pageSize
}
get searchTerm(): string {
return this._state.searchTerm
}
get sortColumn(): keyof T | '' {
return this._state.sortColumn
}
get sortDirection(): SortDirection {
return this._state.sortDirection
}
set page(page: number) {
this._set({ page })
}
set startIndex(startIndex: number) {
this._set({ startIndex })
}
set endIndex(endIndex: number) {
this._set({ endIndex })
}
set pageSize(pageSize: number) {
this._set({ pageSize })
}
set searchTerm(searchTerm: string) {
this._set({ searchTerm })
}
set sortColumn(sortColumn: keyof T | '') {
this._set({ sortColumn })
}
set sortDirection(sortDirection: SortDirection) {
this._set({ sortDirection })
}
setItems(items: T[], pageSize: number): void {
this.items = items
this._set({ pageSize })
this._set({ endIndex: pageSize })
}
private _set(patch: Partial<State<T>>): void {
Object.assign(this._state, patch)
this._search$.next()
}
private _search(): Observable<SearchResult<T>> {
const { pageSize, page, searchTerm, sortColumn, sortDirection } =
this._state
const searchableFields = Object.keys(this.items[0]) as (keyof T)[]
// filter
let filteredItems = this.items.filter((item) =>
matches(item, searchTerm, searchableFields)
)
// Sort
if (sortColumn) {
filteredItems = [...filteredItems].sort((a, b) => {
const res = compare(a[sortColumn], b[sortColumn])
return sortDirection === 'asc' ? res : -res
})
}
const total = filteredItems.length
// Paginate the items
this.startIndex = (page - 1) * pageSize
this.endIndex = this.startIndex + pageSize
const paginatedItems = filteredItems.slice(this.startIndex, this.endIndex)
this._loading$.next(false)
return of({ items: paginatedItems, total })
}
}

View File

@@ -0,0 +1,38 @@
// title.service.ts
import { Injectable } from '@angular/core'
import { Title } from '@angular/platform-browser'
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'
import { filter } from 'rxjs/operators'
@Injectable({
providedIn: 'root',
})
export class TitleService {
constructor(
private titleService: Title,
private router: Router,
private activatedRoute: ActivatedRoute
) {}
init(): void {
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => {
this.updateTitle()
})
}
private updateTitle(): void {
let route = this.activatedRoute
while (route.firstChild) {
route = route.firstChild
}
if (route.snapshot.data['title']) {
this.titleService.setTitle(
route.snapshot.data['title'] +
' | Reback - Responsive Angular Admin Dashboard Template'
)
}
}
}

View File

@@ -0,0 +1,21 @@
import { Directive, ElementRef, Input, OnInit } from '@angular/core'
import flatpickr from 'flatpickr'
import { Options } from 'flatpickr/dist/types/options'
@Directive({
selector: '[mwlFlatpickr]',
standalone: true,
})
export class FlatpickrDirective implements OnInit {
@Input() flatpickrOptions: Options = {}
constructor(private el: ElementRef) {}
ngOnInit() {
this.initFlatpickr()
}
private initFlatpickr() {
flatpickr(this.el.nativeElement, this.flatpickrOptions)
}
}

View File

@@ -0,0 +1,37 @@
import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
Input,
ViewChild,
ElementRef,
type AfterViewInit,
} from '@angular/core'
import { getIcon, loadIcon, buildIcon } from 'iconify-icon'
import { getIconData, iconToSVG, iconToHTML, replaceIDs } from '@iconify/utils'
@Component({
selector: 'ng-iconify',
standalone: true,
imports: [],
template: `<template #iconTemplate></template>`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
styles: `
:host(ng-iconify) {
display: contents;
}
`,
})
export class IconifyComponent implements AfterViewInit {
@Input() icon: string = ''
@ViewChild('iconTemplate') iconTemplate!: ElementRef
svg = ''
ngAfterViewInit(): void {
const builtIcon = buildIcon(getIcon(this.icon))
this.svg = iconToHTML(builtIcon.body, builtIcon.attributes)
this.iconTemplate.nativeElement.innerHTML = this.svg
}
ngOnInit(): void {}
}

View File

@@ -0,0 +1,33 @@
import { Directive, ElementRef, Input, type OnInit } from '@angular/core'
import Choices, { Options as ChoiceOption } from 'choices.js'
export type SelectOptions = Partial<ChoiceOption>
@Directive({
selector: '[selectFormInput]',
standalone: true,
})
export class SelectFormInputDirective implements OnInit {
@Input() className?: string
@Input() onChange?: (text: string) => void
@Input() options?: SelectOptions
constructor(private eleRef: ElementRef) {}
ngOnInit(): void {
const choices = new Choices(this.eleRef.nativeElement, {
...this.options,
placeholder: true,
placeholderValue: 'Type and hit enter',
allowHTML: true,
shouldSort: false,
})
choices.passedElement.element.addEventListener('change', (e: Event) => {
if (!(e.target instanceof HTMLSelectElement)) return
if (this.onChange) {
this.onChange(e.target.value)
}
})
}
}

View File

@@ -0,0 +1,33 @@
import { Directive, EventEmitter, Input, Output } from '@angular/core'
export type SortDirection = 'asc' | 'desc' | ''
const rotate: { [key: string]: SortDirection } = {
asc: 'desc',
desc: '',
'': 'asc',
}
export interface SortEvent<T> {
column: keyof T | ''
direction: SortDirection
}
@Directive({
selector: 'th[sortable]',
standalone: true,
host: {
'[class.asc]': 'direction === "asc"',
'[class.desc]': 'direction === "desc"',
'(click)': 'rotate()',
},
})
export class NgbdSortableHeader<T> {
@Input() sortable: keyof T | '' = ''
@Input() direction: SortDirection = ''
@Output() sort = new EventEmitter<SortEvent<T>>()
rotate() {
this.direction = rotate[this.direction]
this.sort.emit({ column: this.sortable, direction: this.direction })
}
}

View File

@@ -0,0 +1,18 @@
const eventDate = new Date('Jan 17, 2026 12:00:01')
const calculateTimeToEvent = () => {
const currentDate = new Date()
const timeRemaining = eventDate.getTime() - currentDate.getTime()
const days = Math.floor(timeRemaining / (1000 * 60 * 60 * 24))
const hours = Math.floor(
(timeRemaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
)
const minutes = Math.floor((timeRemaining % (1000 * 60 * 60)) / (1000 * 60))
const seconds = Math.floor((timeRemaining % (1000 * 60)) / 1000)
return { days, hours, minutes, seconds }
}
export default calculateTimeToEvent

View File

@@ -0,0 +1,138 @@
import { User } from "@/app/store/authentication/auth.model"
import { Injectable } from '@angular/core'
import {
HttpRequest,
HttpResponse,
HttpHandler,
HttpEvent,
HttpInterceptor,
HTTP_INTERCEPTORS,
HttpClient,
HttpHeaders,
} from '@angular/common/http'
import { Observable, throwError } from 'rxjs'
import { catchError, map } from 'rxjs/operators'
import { environment } from '../../environments/environment'
@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
private apiUrl = environment.apiUrl;
constructor(private http: HttpClient) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
// Solo interceptar llamadas a /api
if (!request.url.includes('/api/')) {
return next.handle(request);
}
// Obtener el token del localStorage
const token = localStorage.getItem('access_token');
// Construir la URL real
const apiEndpoint = request.url.replace('/api/', '/');
const fullUrl = `${this.apiUrl}${apiEndpoint}`;
// Clonar la petición con la nueva URL y headers
let headers = new HttpHeaders({
'Content-Type': 'application/json'
});
if (token && !request.url.includes('/auth/')) {
headers = headers.set('Authorization', `Bearer ${token}`);
}
const apiReq = request.clone({
url: fullUrl,
headers: headers
});
// Manejar login especialmente
if (request.url.endsWith('/api/login') && request.method === 'POST') {
return this.http.post<any>(`${this.apiUrl}/auth/login`, request.body).pipe(
map(response => {
if (response && response.access_token) {
// Guardar token y usuario
localStorage.setItem('access_token', response.access_token);
localStorage.setItem('currentUser', JSON.stringify(response.user));
// Retornar en el formato esperado por el template
return new HttpResponse({
status: 200,
body: {
...response.user,
token: response.access_token,
name: response.user.firstName + ' ' + response.user.lastName
}
});
}
return new HttpResponse({ status: 200, body: response });
}),
catchError(error => {
console.error('Login error:', error);
return throwError({
status: error.status || 400,
error: { message: error.error?.message || 'Error al iniciar sesión' }
});
})
);
}
// Manejar registro
if (request.url.endsWith('/api/signup') && request.method === 'POST') {
const [firstName, lastName] = request.body?.name?.split(' ') || ['', ''];
const registerData = {
...request.body,
firstName,
lastName,
role: 'cliente' // Rol por defecto
};
return this.http.post<any>(`${this.apiUrl}/auth/register`, registerData).pipe(
map(response => {
return new HttpResponse({
status: 200,
body: response
});
}),
catchError(error => {
return throwError({
status: error.status || 400,
error: { message: error.error?.message || 'Error al registrar usuario' }
});
})
);
}
// Para todas las demás peticiones, enviarlas a la API real
return this.http.request(apiReq.method, apiReq.url, {
body: apiReq.body,
headers: apiReq.headers,
observe: 'response',
responseType: 'json'
}).pipe(
map(response => {
return response;
}),
catchError(error => {
// Si es error 401, limpiar sesión
if (error.status === 401) {
localStorage.removeItem('access_token');
localStorage.removeItem('currentUser');
}
return throwError(error);
})
);
}
}
export let FakeBackendProvider = {
provide: HTTP_INTERCEPTORS,
useClass: FakeBackendInterceptor,
multi: true,
}

View File

@@ -0,0 +1,35 @@
import type { MenuItem } from '../common/menu-meta'
export const findAllParent = (menuItems: MenuItem[], menuItem: any): any => {
let parents = []
const parent = findMenuItem(menuItems, menuItem['parentKey'])
if (parent) {
parents.push(parent['key'])
if (parent['parentKey'])
parents = [...parents, ...findAllParent(menuItems, parent)]
}
return parents
}
export const findMenuItem = (
menuItems: MenuItem[],
menuItemKey: string
): any => {
if (menuItems && menuItemKey) {
for (var i = 0; i < menuItems.length; i++) {
if (menuItems[i].key === menuItemKey) {
return menuItems[i]
}
var found = findMenuItem(menuItems[i].subMenu, menuItemKey)
if (found) return found
}
}
return null
}
export function addOrSubtractDaysFromDate(days: number): Date {
const result = new Date()
result.setDate(result.getDate() + days)
return result
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { AuthLayoutComponent } from './auth-layout.component'
describe('AuthLayoutComponent', () => {
let component: AuthLayoutComponent
let fixture: ComponentFixture<AuthLayoutComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AuthLayoutComponent],
}).compileComponents()
fixture = TestBed.createComponent(AuthLayoutComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,31 @@
import {
Component,
inject,
Renderer2,
type OnDestroy,
type OnInit,
} from '@angular/core'
import { RouterModule } from '@angular/router'
@Component({
selector: 'app-auth-layout',
standalone: true,
imports: [RouterModule],
template: ` <div class="account-pages pt-2 pt-sm-5 pb-4 pb-sm-5">
<div class="container">
<router-outlet></router-outlet>
</div>
</div>`,
styles: ``,
})
export class AuthLayoutComponent implements OnInit, OnDestroy {
private renderer = inject(Renderer2)
ngOnInit(): void {
this.renderer.addClass(document.body, 'authentication-bg')
}
ngOnDestroy(): void {
this.renderer.removeClass(document.body, 'authentication-bg')
}
}

View File

@@ -0,0 +1,17 @@
<footer class="footer">
<div class="container-fluid">
<div class="row">
<div class="col-12 text-center">
{{ year }}
&copy; Reback. Crafted by
<iconify-icon
icon="iconamoon:heart-duotone"
class="fs-18 align-middle text-danger"
></iconify-icon>
<a href="" class="fw-bold footer-text" target="_blank">{{
credits.name
}}</a>
</div>
</div>
</div>
</footer>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FooterComponent } from './footer.component'
describe('FooterComponent', () => {
let component: FooterComponent
let fixture: ComponentFixture<FooterComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FooterComponent],
}).compileComponents()
fixture = TestBed.createComponent(FooterComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,15 @@
import { credits, currentYear } from '@/app/common/constants'
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
@Component({
selector: 'app-footer',
standalone: true,
imports: [],
templateUrl: './footer.component.html',
styles: ``,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class FooterComponent {
year = currentYear
credits = credits
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { PrivateLayoutComponent } from './private-layout.component'
describe('PrivateLayoutComponent', () => {
let component: PrivateLayoutComponent
let fixture: ComponentFixture<PrivateLayoutComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PrivateLayoutComponent],
}).compileComponents()
fixture = TestBed.createComponent(PrivateLayoutComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,30 @@
import { Component, inject } from '@angular/core'
import { Store } from '@ngrx/store'
import { VerticalComponent } from '../vertical/vertical.component'
@Component({
selector: 'app-private-layout',
standalone: true,
imports: [VerticalComponent],
template: ` <app-vertical></app-vertical> `,
styles: ``,
})
export class PrivateLayoutComponent {
layoutType: any
private store = inject(Store)
ngOnInit(): void {
this.store.select('layout').subscribe((data) => {
this.layoutType = data.LAYOUT
document.documentElement.setAttribute('data-bs-theme', data.LAYOUT_THEME)
document.documentElement.setAttribute('data-menu-color', data.MENU_COLOR)
document.documentElement.setAttribute(
'data-topbar-color',
data.TOPBAR_COLOR
)
document.documentElement.setAttribute('data-menu-size', data.MENU_SIZE)
})
}
}

View File

@@ -0,0 +1,217 @@
<div>
<div
class="offcanvas-end border-0"
tabindex="-1"
id="theme-settings-offcanvas"
>
<div class="d-flex align-items-center bg-primary p-3 offcanvas-header">
<h5 class="text-white m-0">Theme Settings</h5>
<button
type="button"
class="btn-close btn-close-white ms-auto"
(click)="offcanvas.dismiss('Cross click')"
></button>
</div>
<div class="offcanvas-body p-0">
<ngx-simplebar style="height: calc(100vh - 140px)">
<div class="p-3 settings-bar">
<div>
<h5 class="mb-3 font-16 fw-semibold">Color Scheme</h5>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-bs-theme"
id="layout-color-light"
value="light"
[checked]="color == 'light'"
(change)="changeLayoutColor('light')"
/>
<label class="form-check-label" for="layout-color-light">
Light
</label>
</div>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-bs-theme"
id="layout-color-dark"
value="dark"
[checked]="color == 'dark'"
(change)="changeLayoutColor('dark')"
/>
<label class="form-check-label" for="layout-color-dark"
>Dark
</label>
</div>
</div>
<div>
<h5 class="my-3 font-16 fw-semibold">Topbar Color</h5>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-topbar-color"
id="topbar-color-light"
value="light"
[checked]="topbar == 'light'"
(change)="changeTopbar('light')"
/>
<label class="form-check-label"> Light </label>
</div>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-topbar-color"
id="topbar-color-dark"
value="dark"
[checked]="topbar == 'dark'"
(change)="changeTopbar('dark')"
/>
<label class="form-check-label" for="topbar-color-dark">
Dark
</label>
</div>
</div>
<div>
<h5 class="my-3 font-16 fw-semibold">Menu Color</h5>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-menu-color"
id="leftbar-color-light"
value="light"
[checked]="menucolor == 'light'"
(change)="changeMenu('light')"
/>
<label class="form-check-label" for="leftbar-color-light">
Light
</label>
</div>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-menu-color"
id="leftbar-color-dark"
value="dark"
[checked]="menucolor == 'dark'"
(change)="changeMenu('dark')"
/>
<label class="form-check-label" for="leftbar-color-dark">
Dark
</label>
</div>
</div>
<div>
<h5 class="my-3 font-16 fw-semibold">Sidebar Size</h5>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-menu-size"
id="leftbar-size-default"
value="default"
[checked]="sidebarsize == 'default'"
(change)="changeSize('default')"
/>
<label class="form-check-label" for="leftbar-size-default">
Default
</label>
</div>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-menu-size"
id="leftbar-size-small"
value="condensed"
[checked]="sidebarsize == 'condensed'"
(change)="changeSize('condensed')"
/>
<label class="form-check-label" for="leftbar-size-small">
Condensed
</label>
</div>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-menu-size"
id="leftbar-hidden"
value="hidden"
[checked]="sidebarsize == 'hidden'"
(change)="changeSize('hidden')"
/>
<label class="form-check-label" for="leftbar-hidden">
Hidden
</label>
</div>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-menu-size"
id="leftbar-size-small-hover-active"
value="sm-hover-active"
[checked]="sidebarsize == 'sm-hover-active'"
(change)="changeSize('sm-hover-active')"
/>
<label
class="form-check-label"
for="leftbar-size-small-hover-active"
>
Small Hover Active
</label>
</div>
<div class="form-check mb-2">
<input
class="form-check-input"
type="radio"
name="data-menu-size"
id="leftbar-size-small-hover"
value="sm-hover"
[checked]="sidebarsize == 'sm-hover'"
(change)="changeSize('sm-hover')"
/>
<label class="form-check-label" for="leftbar-size-full">
Small Hover
</label>
</div>
</div>
</div>
</ngx-simplebar>
</div>
<div class="offcanvas-footer border-top p-3 text-center">
<div class="row">
<div class="col">
<button
type="button"
class="btn btn-danger w-100"
id="reset-layout"
(click)="reset()"
>
Reset
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { RightSidebarComponent } from './right-sidebar.component'
describe('RightSidebarComponent', () => {
let component: RightSidebarComponent
let fixture: ComponentFixture<RightSidebarComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RightSidebarComponent],
}).compileComponents()
fixture = TestBed.createComponent(RightSidebarComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,82 @@
import { Component, inject } from '@angular/core'
import { NgbActiveOffcanvas } from '@ng-bootstrap/ng-bootstrap'
import { Store } from '@ngrx/store'
import { SimplebarAngularModule } from 'simplebar-angular'
import {
changemenucolor,
changesidebarsize,
changetheme,
changetopbarcolor,
resetState,
} from '../../store/layout/layout-action'
import {
getLayoutColor,
getMenucolor,
getSidebarsize,
getTopbarcolor,
} from '../../store/layout/layout-selector'
@Component({
selector: 'app-right-sidebar',
standalone: true,
imports: [SimplebarAngularModule],
templateUrl: './right-sidebar.component.html',
styles: ``,
})
export class RightSidebarComponent {
public isRightSidebarOpen: boolean = false
offcanvas = inject(NgbActiveOffcanvas)
store = inject(Store)
color: any
topbar: any
menucolor: any
sidebarsize: any
ngOnInit(): void {
this.store.select('layout').subscribe((data: any) => {
this.color = data.LAYOUT_THEME
this.topbar = data.TOPBAR_COLOR
this.menucolor = data.MENU_COLOR
this.sidebarsize = data.MENU_SIZE
})
}
// Change Layout Color
changeLayoutColor(color: any) {
this.store.dispatch(changetheme({ color }))
this.store.select(getLayoutColor).subscribe((color) => {
document.documentElement.setAttribute('data-bs-theme', color)
})
}
// Change Topbar Color
changeTopbar(topbar: any) {
this.store.dispatch(changetopbarcolor({ topbar }))
this.store.select(getTopbarcolor).subscribe((topbar) => {
document.documentElement.setAttribute('data-topbar-color', topbar)
})
}
// Change Menu Color
changeMenu(menu: any) {
this.store.dispatch(changemenucolor({ menu }))
this.store.select(getMenucolor).subscribe((menucolor) => {
document.documentElement.setAttribute('data-menu-color', menucolor)
})
}
// Change Sidebar Size
changeSize(size: any) {
this.store.dispatch(changesidebarsize({ size }))
this.store.select(getSidebarsize).subscribe((size) => {
document.documentElement.setAttribute('data-menu-size', size)
})
}
// Reset Option
reset() {
this.store.dispatch(resetState())
}
}

View File

@@ -0,0 +1,159 @@
<div class="main-nav">
<app-logo-box className="logo-box" />
<button
type="button"
class="button-sm-hover"
aria-label="Show Full Sidebar"
(click)="changeSidebarSize()"
>
<iconify-icon
icon="iconamoon:arrow-left-4-square-duotone"
class="button-sm-hover-icon"
></iconify-icon>
</button>
<ngx-simplebar class="scrollbar" id="leftside-menu-container" data-simplebar>
<ul class="navbar-nav" id="navbar-nav">
@for (item of menuItems; track item.label) {
@if (item.isTitle) {
<li class="menu-title">{{ item.label }}</li>
} @else {
@if (hasSubmenu(item)) {
<ng-container
*ngTemplateOutlet="
MenuItemWithChildren;
context: {
menu: item,
linkClassName: 'nav-link menu-arrow',
subMenuClassNames: 'nav sub-navbar-nav',
itemClassName: 'nav-item',
}
"
>
</ng-container>
} @else {
<ng-container
*ngTemplateOutlet="
MenuItem;
context: {
menu: item,
linkClassName: 'nav-link nav-link-ref',
itemClassName: 'nav-item',
}
"
>
</ng-container>
}
}
}
</ul>
</ngx-simplebar>
</div>
<ng-template
#MenuItemWithChildren
let-menu="menu"
let-itemClassName="itemClassName"
let-linkClassName="linkClassName"
let-subMenuClassNames="subMenuClassNames"
>
<li [class]="itemClassName">
<a
[class]="linkClassName"
[ngClass]="{ active: activeMenuItems.includes(menu.key) }"
(click)="toggleMenuItem(menu, collapse)"
role="button"
[attr.aria-expanded]="!menu.collapsed"
aria-controls="sidebarDashboards"
[attr.aria-controls]="menu.key"
>
@if (menu.icon) {
<span class="nav-icon">
<iconify-icon [icon]="menu.icon"></iconify-icon>
</span>
}
<span class="nav-text"> {{ menu.label }} </span>
</a>
<div
#collapse="ngbCollapse"
[(ngbCollapse)]="menu.collapsed"
class="collapse"
id="sidebarDashboards"
>
<ul [class]="subMenuClassNames">
@for (child of menu.subMenu; track child.label) {
@if (hasSubmenu(child)) {
<ng-container
*ngTemplateOutlet="
MenuItemWithChildren;
context: {
menu: child,
linkClassName: 'sub-nav-link menu-arrow',
itemClassName: 'sub-nav-item',
subMenuClassNames: 'nav sub-navbar-nav',
}
"
>
</ng-container>
} @else {
<ng-container
*ngTemplateOutlet="
MenuItem;
context: {
menu: child,
linkClassName: 'sub-nav-link nav-link-ref',
itemClassName: 'sub-nav-item',
}
"
>
</ng-container>
}
}
</ul>
</div>
</li>
</ng-template>
<ng-template
#MenuItem
let-menu="menu"
let-linkClassName="linkClassName"
let-itemClassName="itemClassName"
>
<li
[class]="itemClassName"
[ngClass]="{ active: activeMenuItems.includes(menu.key) }"
>
<ng-container
*ngTemplateOutlet="
MenuItemLink;
context: { menu: menu, className: linkClassName }
"
>
</ng-container>
</li>
</ng-template>
<ng-template #MenuItemLink let-menu="menu" let-className="className">
<a
[routerLink]="menu.link"
[class]="className"
[ngClass]="{ active: activeMenuItems.includes(menu.key) }"
[attr.aria-controls]="menu.key"
>
@if (menu.icon) {
<span class="nav-icon">
<iconify-icon [icon]="menu.icon"></iconify-icon>
</span>
<span class="nav-text">{{ menu.label }}</span>
} @else {
{{ menu.label }}
}
@if (menu.badge) {
<span class="badge badge-pill text-end bg-{{ menu.badge.variant }}">{{
menu.badge.text
}}</span>
}
</a>
</ng-template>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SidebarComponent } from './sidebar.component'
describe('SidebarComponent', () => {
let component: SidebarComponent
let fixture: ComponentFixture<SidebarComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SidebarComponent],
}).compileComponents()
fixture = TestBed.createComponent(SidebarComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,201 @@
import { CUSTOM_ELEMENTS_SCHEMA, Component, inject } from '@angular/core'
import { SimplebarAngularModule } from 'simplebar-angular'
import { NavigationEnd, Router, RouterModule } from '@angular/router'
import {
NgbCollapse,
NgbCollapseModule,
NgbTooltipModule,
} from '@ng-bootstrap/ng-bootstrap'
import { CommonModule } from '@angular/common'
import { findAllParent, findMenuItem } from '../../helpers/utils'
import { LogoBoxComponent } from '@/app/components/logo-box.component'
import { MENU, type MenuItem } from '@/app/common/menu-meta'
import { changesidebarsize } from '@/app/store/layout/layout-action'
import { Store } from '@ngrx/store'
import { getSidebarsize } from '@/app/store/layout/layout-selector'
import { basePath } from '@/app/common/constants'
@Component({
selector: 'app-sidebar',
standalone: true,
imports: [
SimplebarAngularModule,
RouterModule,
NgbCollapseModule,
CommonModule,
NgbTooltipModule,
LogoBoxComponent,
],
templateUrl: './sidebar.component.html',
styles: ``,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class SidebarComponent {
menuItems: MenuItem[] = []
activeMenuItems: string[] = []
store = inject(Store)
router = inject(Router)
trimmedURL = this.router.url?.replaceAll(
basePath !== '' ? basePath + '/' : '',
'/'
)
constructor() {
this.router.events.forEach((event) => {
if (event instanceof NavigationEnd) {
this.trimmedURL = this.router.url?.replaceAll(
basePath !== '' ? basePath + '/' : '',
'/'
)
this._activateMenu()
setTimeout(() => {
this.scrollToActive()
}, 200)
}
})
}
ngOnInit(): void {
this.initMenu()
}
initMenu(): void {
this.menuItems = MENU
}
ngAfterViewInit() {
setTimeout(() => {
this._activateMenu()
})
setTimeout(() => {
this.scrollToActive()
}, 200)
}
scrollToActive(): void {
const activatedItem = document.querySelector('.nav-item li a.active')
if (activatedItem) {
const simplebarContent = document.querySelector(
'.main-nav .simplebar-content-wrapper'
)
if (simplebarContent) {
const activatedItemRect = activatedItem.getBoundingClientRect()
const simplebarContentRect = simplebarContent.getBoundingClientRect()
const activatedItemOffsetTop =
activatedItemRect.top + simplebarContent.scrollTop
const centerOffset =
activatedItemOffsetTop -
simplebarContentRect.top -
simplebarContent.clientHeight / 2 +
activatedItemRect.height / 2
this.scrollTo(simplebarContent, centerOffset, 600)
}
}
}
easeInOutQuad(t: number, b: number, c: number, d: number): number {
t /= d / 2
if (t < 1) return (c / 2) * t * t + b
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
scrollTo(element: Element, to: number, duration: number): void {
const start = element.scrollTop
const change = to - start
const increment = 20
let currentTime = 0
const animateScroll = () => {
currentTime += increment
const val = this.easeInOutQuad(currentTime, start, change, duration)
element.scrollTop = val
if (currentTime < duration) {
setTimeout(animateScroll, increment)
}
}
animateScroll()
}
_activateMenu(): void {
const div = document.querySelector('.navbar-nav')
let matchingMenuItem = null
if (div) {
let items: any = div.getElementsByClassName('nav-link-ref')
for (let i = 0; i < items.length; ++i) {
if (
this.trimmedURL === items[i].pathname ||
(this.trimmedURL.startsWith('/invoice/') &&
items[i].pathname === '/invoice/RB6985') ||
(this.trimmedURL.startsWith('/ecommerce/product/') &&
items[i].pathname === '/ecommerce/product/1')
) {
matchingMenuItem = items[i]
break
}
}
if (matchingMenuItem) {
const mid = matchingMenuItem.getAttribute('aria-controls')
const activeMt = findMenuItem(this.menuItems, mid)
if (activeMt) {
const matchingObjs = [
activeMt['key'],
...findAllParent(this.menuItems, activeMt),
]
this.activeMenuItems = matchingObjs
this.menuItems.forEach((menu: MenuItem) => {
menu.collapsed = !matchingObjs.includes(menu.key!)
})
}
}
}
}
/**
* Returns true or false if given menu item has child or not
* @param item menuItem
*/
hasSubmenu(menu: MenuItem): boolean {
return menu.subMenu ? true : false
}
/**
* toggles open menu
* @param menuItem clicked menuitem
* @param collapse collpase instance
*/
toggleMenuItem(menuItem: MenuItem, collapse: NgbCollapse): void {
collapse.toggle()
let openMenuItems: string[]
if (!menuItem.collapsed) {
openMenuItems = [
menuItem['key'],
...findAllParent(this.menuItems, menuItem),
]
this.menuItems.forEach((menu: MenuItem) => {
if (!openMenuItems.includes(menu.key!)) {
menu.collapsed = true
}
})
}
}
changeSidebarSize() {
let size = document.documentElement.getAttribute('data-menu-size')
if (size == 'sm-hover') {
size = 'sm-hover-active'
} else {
size = 'sm-hover'
}
this.store.dispatch(changesidebarsize({ size }))
this.store.select(getSidebarsize).subscribe((size) => {
document.documentElement.setAttribute('data-menu-size', size)
})
}
}

View File

@@ -0,0 +1,164 @@
import { addOrSubtractDaysFromDate } from '@/app/helpers/utils'
const bitbucketImg = 'assets/images/brands/bitbucket.svg'
const dribbleImg = 'assets/images/brands/dribbble.svg'
const dropboxImg = 'assets/images/brands/dropbox.svg'
const githubImg = 'assets/images/brands/github.svg'
const slackImg = 'assets/images/brands/slack.svg'
const smImg3 = 'assets/images/small/img-3.jpg'
const smImg4 = 'assets/images/small/img-4.jpg'
const smImg6 = 'assets/images/small/img-6.jpg'
const avatar1 = 'assets/images/users/avatar-1.jpg'
const avatar3 = 'assets/images/users/avatar-3.jpg'
const avatar5 = 'assets/images/users/avatar-5.jpg'
const avatar6 = 'assets/images/users/avatar-6.jpg'
const avatar7 = 'assets/images/users/avatar-7.jpg'
export type BootstrapVariantType =
| 'primary'
| 'secondary'
| 'success'
| 'danger'
| 'warning'
| 'info'
| 'dark'
| 'light'
export type FileType = Partial<File> & {
preview?: string
}
export type ActivityType = {
title: string
icon?: string
variant?: BootstrapVariantType
status?: 'completed' | 'latest'
files?: FileType[]
time: Date
type?: 'task' | 'design' | 'achievement'
content?: string
}
export type AppType = {
image: string
name: string
handle: string
}
export type NotificationType = {
from: string
content: string
icon?: string
}
export const appsData: AppType[] = [
{
image: githubImg,
name: 'Github',
handle: '@reback',
},
{
image: bitbucketImg,
name: 'Bitbucket',
handle: '@reback',
},
{
image: dribbleImg,
name: 'Dribble',
handle: '@username',
},
{
image: dropboxImg,
name: 'Dropbox',
handle: '@username',
},
{
image: slackImg,
name: 'Slack',
handle: '@reback',
},
]
export const notificationsData: NotificationType[] = [
{
from: 'Josephine Thompson',
content:
'commented on admin panel "Wow 😍! this admin looks good and awesome design"',
icon: avatar1,
},
{
from: 'Donoghue Susan',
content: 'Hi, How are you? What about our next meeting',
},
{
from: 'Jacob Gines',
content: "Answered to your comment on the cash flow forecast's graph 🔔.",
icon: avatar3,
},
{
from: 'Shawn Bunch',
content: 'Commented on Admin',
icon: avatar5,
},
{
from: 'Vanessa R. Davis',
content: 'Delivery processing your order is being shipped',
},
]
export const activityStreamData: ActivityType[] = [
{
title: 'Report-Fix / Update',
variant: 'danger',
type: 'task',
files: [
{
name: 'Concept.fig',
},
{
name: 'reback.docs',
},
],
time: addOrSubtractDaysFromDate(0),
},
{
title: 'Project Status',
files: [
{
name: 'UI/UX Figma Design.fig',
},
],
variant: 'success',
type: 'design',
status: 'completed',
time: addOrSubtractDaysFromDate(1),
},
{
title: 'Reback Application UI v2.0.0',
variant: 'primary',
content:
'Get access to over 20+ pages including a dashboard layout, charts, kanban board, calendar, and pre-order E-commerce & Marketing pages.',
files: [{ name: 'Backup.zip' }],
status: 'latest',
time: addOrSubtractDaysFromDate(3),
},
{
title: 'Alex Smith Attached Photos',
icon: avatar7,
time: addOrSubtractDaysFromDate(4),
files: [{ preview: smImg6 }, { preview: smImg3 }, { preview: smImg4 }],
},
{
title: 'Rebecca J. added a new team member',
icon: avatar6,
time: addOrSubtractDaysFromDate(4),
content: 'Added a new member to Front Dashboard',
},
{
title: 'Achievements',
variant: 'warning',
type: 'achievement',
time: addOrSubtractDaysFromDate(5),
content: 'Earned a "Best Product Award"',
},
]

View File

@@ -0,0 +1,380 @@
<header class="topbar">
<div class="container-xxl">
<div class="navbar-header">
<div class="d-flex align-items-center gap-2">
<div class="topbar-item">
<button
type="button"
class="button-toggle-menu"
(click)="toggleMobileMenu()"
>
<iconify-icon
icon="iconamoon:menu-burger-horizontal"
class="fs-22"
></iconify-icon>
</button>
</div>
<!-- App Search-->
<form class="app-search d-none d-md-block me-auto">
<div class="position-relative">
<input
type="search"
class="form-control"
placeholder="Search..."
autocomplete="off"
value=""
/>
<iconify-icon
icon="iconamoon:search-duotone"
class="search-widget-icon"
></iconify-icon>
</div>
</form>
</div>
<div class="d-flex align-items-center gap-1">
<div class="topbar-item">
<button
type="button"
class="topbar-button"
id="light-dark-mode"
(click)="changeTheme()"
>
<iconify-icon
icon="iconamoon:mode-dark-duotone"
class="fs-24 align-middle"
></iconify-icon>
</button>
</div>
<div ngbDropdown class="dropdown topbar-item d-none d-lg-flex">
<button
ngbDropdownToggle
type="button"
class="topbar-button arrow-none"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<iconify-icon
icon="iconamoon:apps"
class="fs-24 align-middle"
></iconify-icon>
</button>
<div ngbDropdownMenu class="dropdown-menu dropdown-menu-end p-0">
<div class="p-1">
@for (item of appsList; track $index) {
<a class="dropdown-item py-2" href="javascript:void(0);">
<img [src]="item.image" class="avatar-xs" alt="Github" />
<span class="ms-2"
>{{ item.name }}:
<span class="fw-medium">{{ item.handle }}</span></span
>
</a>
}
</div>
</div>
</div>
<div ngbDropdown class="dropdown topbar-item">
<button
ngbDropdownToggle
type="button"
class="topbar-button position-relative arrow-none"
id="page-header-notifications-dropdown"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<iconify-icon
icon="iconamoon:notification-duotone"
class="fs-24 align-middle"
></iconify-icon>
<span
class="position-absolute topbar-badge fs-10 translate-middle badge bg-danger rounded-pill"
>{{ notificationList.length
}}<span class="visually-hidden">unread messages</span></span
>
</button>
<div
ngbDropdownMenu
class="dropdown-menu py-0 dropdown-lg dropdown-menu-end"
aria-labelledby="page-header-notifications-dropdown"
>
<div
class="p-3 border-top-0 border-start-0 border-end-0 border-dashed border"
>
<div class="row align-items-center">
<div class="col">
<h6 class="m-0 fs-16 fw-semibold">Notifications</h6>
</div>
<div class="col-auto">
<a
href="javascript: void(0);"
class="text-dark text-decoration-underline"
>
<small>Clear All</small>
</a>
</div>
</div>
</div>
<ngx-simplebar style="max-height: 280px">
@for (notify of notificationList; track $index) {
<a
href="javascript:void(0);"
class="dropdown-item py-3 border-bottom text-wrap"
>
<div class="d-flex">
<div class="flex-shrink-0">
@if (notify.icon) {
<img
[src]="notify.icon"
class="img-fluid me-2 avatar-sm rounded-circle"
alt="avatar-1"
/>
} @else {
<div class="avatar-sm me-2">
<span
class="avatar-title bg-soft-info text-info fs-20 rounded-circle"
>
{{ notify.from.charAt(0) }}
</span>
</div>
}
</div>
<div class="flex-grow-1">
<p class="mb-0">
<span class="fw-medium">{{ notify.from }} </span>
</p>
<p class="mb-0 text-wrap">{{ notify.content }}</p>
</div>
</div>
</a>
}
</ngx-simplebar>
<div class="text-center py-3">
<a href="javascript:void(0);" class="btn btn-primary btn-sm"
>View All Notification <i class="bx bx-right-arrow-alt ms-1"></i
></a>
</div>
</div>
</div>
<div class="topbar-item">
<button
type="button"
(click)="settingMenu()"
class="topbar-button"
id="theme-settings-btn"
data-bs-target="#theme-settings-offcanvas"
aria-controls="theme-settings-offcanvas"
>
<iconify-icon
icon="iconamoon:settings-duotone"
class="fs-24 align-middle"
></iconify-icon>
</button>
</div>
<div class="topbar-item d-none d-md-flex">
<button
type="button"
class="topbar-button"
id="theme-settings-btn"
(click)="open(content)"
aria-controls="theme-settings-offcanvas"
>
<iconify-icon
icon="iconamoon:history-duotone"
class="fs-24 align-middle"
></iconify-icon>
</button>
</div>
<div ngbDropdown class="dropdown topbar-item">
<a
type="button"
ngbDropdownToggle
class="topbar-button arrow-none"
id="page-header-user-dropdown"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<span class="d-flex align-items-center">
<img
class="rounded-circle"
width="32"
src="assets/images/users/avatar-1.jpg"
alt="avatar-3"
/>
</span>
</a>
<div ngbDropdownMenu class="dropdown-menu dropdown-menu-end">
<!-- item-->
<h6 class="dropdown-header">Welcome Gaston!</h6>
<a class="dropdown-item" routerLink="/pages/profile">
<i
class="bx bx-user-circle text-muted fs-18 align-middle me-1"
></i
><span class="align-middle">Profile</span>
</a>
<a class="dropdown-item" routerLink="/apps/chat">
<i
class="bx bx-message-dots text-muted fs-18 align-middle me-1"
></i
><span class="align-middle">Messages</span>
</a>
<a class="dropdown-item" routerLink="/pages/pricing">
<i class="bx bx-wallet text-muted fs-18 align-middle me-1"></i
><span class="align-middle">Pricing</span>
</a>
<a class="dropdown-item" routerLink="/pages/faqs">
<i
class="bx bx-help-circle text-muted fs-18 align-middle me-1"
></i
><span class="align-middle">Help</span>
</a>
<a class="dropdown-item" routerLink="/auth/lock-screen">
<i class="bx bx-lock text-muted fs-18 align-middle me-1"></i
><span class="align-middle">Lock screen</span>
</a>
<div class="dropdown-divider my-1"></div>
<a class="dropdown-item text-danger" (click)="logout()">
<i class="bx bx-log-out fs-18 align-middle me-1"></i
><span class="align-middle">Logout</span>
</a>
</div>
</div>
</div>
</div>
</div>
</header>
<ng-template #content let-offcanvas>
<div class="d-flex align-items-center bg-primary p-3 offcanvas-header">
<h5 class="text-white m-0 fw-semibold">Activity Stream</h5>
<button
type="button"
class="btn-close btn-close-white ms-auto"
(click)="offcanvas.close()"
></button>
</div>
<div class="offcanvas-body p-0">
<ngx-simplebar data-simplebar class="h-100 p-4">
<div class="position-relative ms-2">
<span
class="position-absolute start-0 top-0 border border-dashed h-100"
></span>
@for (data of activityList; track $index) {
<div class="position-relative ps-4">
<div class="mb-4">
<span
class="position-absolute start-0 translate-middle-x d-inline-flex align-items-center justify-content-center rounded-circle text-light fs-20 bg-{{
data.variant
}} "
[ngClass]="{ 'avatar-sm': !data.icon }"
>
@if (data.icon) {
<img
[src]="data.icon"
alt="avatar-5"
class="avatar-sm rounded-circle"
/>
} @else if (data.type) {
<iconify-icon
[icon]="getActivityIcon(data.type)"
></iconify-icon>
} @else {
{{ data.title.charAt(0).toUpperCase() }}
}
</span>
<div class="ms-2">
<h5 class="mb-1 text-dark fw-semibold fs-15 lh-base">
{{ data.title }}
@if (data.status) {
<span
class="badge bg-{{ data.variant }}-subtle text-{{
data.variant
}} px-2 py-1 ms-1"
>{{ data.status }}</span
>
}
</h5>
@if (data.files && data.type) {
<p class="d-flex align-items-center">
Add {{ data.files.length }} files to
<span class="d-flex align-items-center text-primary ms-1">
<iconify-icon icon="iconamoon:file-light"></iconify-icon>
{{ data.type }}</span
>
</p>
}
@if (data.content) {
<p>{{ data.content }}</p>
}
@if (data.files) {
<div class="bg-light bg-opacity-50 rounded-2 p-2">
<div class="row">
@for (file of data.files; track $index) {
@if (file.preview) {
<div class="col-lg-4">
<a href="javascript:void(0);">
<img
[src]="file.preview"
alt=""
class="img-fluid rounded"
/></a>
</div>
} @else {
<div
class="border-end border-light"
[ngClass]="{ 'col-lg-6': data.files.length > 1 }"
>
<div class="d-flex align-items-center gap-2">
<i
class="bx fs-20"
[class]="getFileExtensionIcon(file.name)"
></i>
<a
href="javascript:void(0);"
class="text-dark fw-medium"
>{{ file.name }}</a
>
<div class="ms-auto">
<a
href="javascript:void(0);"
class="fw-medium text-primary fs-18"
ngbTooltip="Download"
placement="bottom"
>
<iconify-icon
icon="iconamoon:cloud-download-duotone"
></iconify-icon>
</a>
</div>
</div>
</div>
}
}
</div>
</div>
}
<h6 class="mt-2 text-muted">{{ data.time.toDateString() }}</h6>
</div>
</div>
</div>
}
</div>
<a href="javascript:void(0);" class="btn btn-outline-dark w-100"
>View All</a
>
</ngx-simplebar>
</div>
</ng-template>

View File

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { TopbarComponent } from './topbar.component'
describe('TopbarComponent', () => {
let component: TopbarComponent
let fixture: ComponentFixture<TopbarComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TopbarComponent],
}).compileComponents()
fixture = TestBed.createComponent(TopbarComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,116 @@
import { changetheme } from '@/app/store/layout/layout-action'
import { CommonModule, DOCUMENT } from '@angular/common'
import {
CUSTOM_ELEMENTS_SCHEMA,
Component,
EventEmitter,
Inject,
Output,
inject,
type TemplateRef,
} from '@angular/core'
import {
NgbDropdownModule,
NgbOffcanvas,
NgbOffcanvasModule,
NgbTooltipModule,
} from '@ng-bootstrap/ng-bootstrap'
import { Store } from '@ngrx/store'
import { SimplebarAngularModule } from 'simplebar-angular'
import { getLayoutColor } from '../../store/layout/layout-selector'
import { logout } from '@/app/store/authentication/authentication.actions'
import { Router, RouterLink } from '@angular/router'
import { activityStreamData, appsData, notificationsData } from './data'
@Component({
selector: 'app-topbar',
standalone: true,
imports: [
NgbDropdownModule,
SimplebarAngularModule,
NgbOffcanvasModule,
RouterLink,
CommonModule,
NgbTooltipModule,
],
templateUrl: './topbar.component.html',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class TopbarComponent {
element: any
router = inject(Router)
store = inject(Store)
offcanvasService = inject(NgbOffcanvas)
notificationList = notificationsData
appsList = appsData
activityList = activityStreamData
constructor(@Inject(DOCUMENT) private document: any) {}
@Output() settingsButtonClicked = new EventEmitter()
@Output() mobileMenuButtonClicked = new EventEmitter()
ngOnInit(): void {
this.element = document.documentElement
}
settingMenu() {
this.settingsButtonClicked.emit()
}
/**
* Toggle the menu bar when having mobile screen
*/
toggleMobileMenu() {
// document.getElementById('topnav-hamburger-icon')?.classList.toggle('open');
this.mobileMenuButtonClicked.emit()
}
// Change Theme
changeTheme() {
const color = document.documentElement.getAttribute('data-bs-theme')
console.log(color)
if (color == 'light') {
this.store.dispatch(changetheme({ color: 'dark' }))
} else {
this.store.dispatch(changetheme({ color: 'light' }))
}
this.store.select(getLayoutColor).subscribe((color) => {
document.documentElement.setAttribute('data-bs-theme', color)
})
}
open(content: TemplateRef<any>) {
this.offcanvasService.open(content, {
position: 'end',
panelClass: 'border-0 width-auto',
})
}
logout() {
this.store.dispatch(logout())
}
getFileExtensionIcon(file: any) {
const dotIndex = file.lastIndexOf('.')
const extension = file.slice(dotIndex + 1)
if (extension == 'docs') {
return 'bxs-file-doc'
} else if (extension == 'zip') {
return 'bxs-file-archive'
} else {
return 'bxl-figma '
}
}
getActivityIcon(type: string) {
if (type == 'task') {
return 'iconamoon:folder-check-duotone'
} else if (type == 'design') {
return 'iconamoon:check-circle-1-duotone'
} else {
return 'iconamoon:certificate-badge-duotone'
}
}
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { VerticalComponent } from './vertical.component'
describe('VerticalComponent', () => {
let component: VerticalComponent
let fixture: ComponentFixture<VerticalComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [VerticalComponent],
}).compileComponents()
fixture = TestBed.createComponent(VerticalComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,107 @@
import { Component, HostListener, inject, Renderer2 } from '@angular/core'
import { SidebarComponent } from '../sidebar/sidebar.component'
import { TopbarComponent } from '../topbar/topbar.component'
import { FooterComponent } from '../footer/footer.component'
import { RouterModule } from '@angular/router'
import { RightSidebarComponent } from '../right-sidebar/right-sidebar.component'
import { NgbActiveOffcanvas, NgbOffcanvas } from '@ng-bootstrap/ng-bootstrap'
import { Store } from '@ngrx/store'
import { changesidebarsize } from '@store/layout/layout-action'
import { getSidebarsize } from '@store/layout/layout-selector'
@Component({
selector: 'app-vertical',
standalone: true,
imports: [SidebarComponent, TopbarComponent, FooterComponent, RouterModule],
template: `
<div class="wrapper">
<app-topbar
(settingsButtonClicked)="onSettingsButtonClicked()"
(mobileMenuButtonClicked)="onToggleMobileMenu()"
></app-topbar>
<app-sidebar></app-sidebar>
<div class="page-content">
<!-- Start Content-->
<div class="container-xxl">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
</div>
</div>
`,
styles: ``,
providers: [NgbActiveOffcanvas],
})
export class VerticalComponent {
private offcanvasService = inject(NgbOffcanvas)
private store = inject(Store)
private renderer = inject(Renderer2)
ngOnInit(): void {
this.onResize()
}
@HostListener('window:resize', ['$event'])
onResize() {
if (document.documentElement.clientWidth <= 1140) {
this.store.dispatch(changesidebarsize({ size: 'hidden' }))
} else {
this.store.dispatch(changesidebarsize({ size: 'default' }))
document.documentElement.classList.remove('sidebar-enable')
const backdrop = document.querySelector('.offcanvas-backdrop')
if (backdrop) this.renderer.removeChild(document.body, backdrop)
}
this.store.select(getSidebarsize).subscribe((size: string) => {
this.renderer.setAttribute(
document.documentElement,
'data-sidenav-size',
size
)
})
}
onSettingsButtonClicked() {
this.offcanvasService.open(RightSidebarComponent, { position: 'end' })
}
onToggleMobileMenu() {
this.store.select(getSidebarsize).subscribe((size: any) => {
document.documentElement.setAttribute('data-menu-size', size)
})
const size = document.documentElement.getAttribute('data-menu-size')
document.documentElement.classList.toggle('sidebar-enable')
if (size != 'hidden') {
if (document.documentElement.classList.contains('sidebar-enable')) {
this.store.dispatch(changesidebarsize({ size: 'condensed' }))
} else {
this.store.dispatch(changesidebarsize({ size: 'default' }))
}
} else {
this.showBackdrop()
}
}
showBackdrop() {
const backdrop = this.renderer.createElement('div')
this.renderer.addClass(backdrop, 'offcanvas-backdrop')
this.renderer.addClass(backdrop, 'fade')
this.renderer.addClass(backdrop, 'show')
this.renderer.appendChild(document.body, backdrop)
this.renderer.setStyle(document.body, 'overflow', 'hidden')
if (window.innerWidth > 1040) {
this.renderer.setStyle(document.body, 'paddingRight', '15px')
}
this.renderer.listen(backdrop, 'click', () => {
document.documentElement.classList.remove('sidebar-enable')
this.renderer.removeChild(document.body, backdrop)
this.renderer.setStyle(document.body, 'overflow', null)
this.renderer.setStyle(document.body, 'paddingRight', null)
})
}
}

View File

@@ -0,0 +1,17 @@
export class User {
id?: string
email?: string
role?: 'admin' | 'dealer' | 'cliente' | 'aseguradora' | 'financiera' | 'tasador' | 'gps'
firstName?: string
lastName?: string
phone?: string
isActive?: boolean
createdAt?: Date
updatedAt?: Date
fullName?: string
token?: string
// Compatibilidad con el template original
username?: string
password?: string
name?: string
}

View File

@@ -0,0 +1,21 @@
import { createAction, props } from '@ngrx/store'
import type { User } from './auth.model'
// login action
export const login = createAction(
'[Authentication] Login',
props<{ email: string; password: string }>()
)
export const loginSuccess = createAction(
'[Authentication] Login Success',
props<{ user: User }>()
)
export const loginFailure = createAction(
'[Authentication] Login Failure',
props<{ error: string }>()
)
// logout action
export const logout = createAction('[Authentication] Logout')
export const logoutSuccess = createAction('[Auth] Logout Success')

View File

@@ -0,0 +1,53 @@
import { Inject, Injectable } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Actions, createEffect, ofType } from '@ngrx/effects'
import { of } from 'rxjs'
import { catchError, exhaustMap, map } from 'rxjs/operators'
import {
login,
loginFailure,
loginSuccess,
logout,
logoutSuccess,
} from './authentication.actions'
import { AuthenticationService } from '@/app/core/services/auth.service'
@Injectable()
export class AuthenticationEffects {
login$ = createEffect(() =>
this.actions$.pipe(
ofType(login),
exhaustMap(({ email, password }) => {
return this.AuthenticationService.login(email, password).pipe(
map((user) => {
if (user) {
const returnUrl =
this.route.snapshot.queryParams['returnUrl'] || '/'
this.router.navigateByUrl(returnUrl)
}
return loginSuccess({ user })
}),
catchError((error) => of(loginFailure({ error })))
)
})
)
)
logout$ = createEffect(() =>
this.actions$.pipe(
ofType(logout),
exhaustMap(() => {
this.AuthenticationService.logout()
this.router.navigate(['/auth/sign-in'])
return of(logoutSuccess())
})
)
)
constructor(
@Inject(Actions) private actions$: Actions,
private AuthenticationService: AuthenticationService,
private router: Router,
private route: ActivatedRoute
) {}
}

View File

@@ -0,0 +1,34 @@
import { createReducer, on } from '@ngrx/store'
import {
login,
loginFailure,
loginSuccess,
logout,
} from './authentication.actions'
import type { User } from './auth.model'
export type AuthenticationState = {
isLoggedIn: boolean
user: User | null
error: string | null
}
const initialState: AuthenticationState = {
isLoggedIn: false,
user: null,
error: null,
}
export const authenticationReducer = createReducer(
initialState,
on(login, (state) => ({ ...state, error: null })),
on(loginSuccess, (state, { user }) => ({
...state,
isLoggedIn: true,
user,
error: null,
})),
on(loginFailure, (state, { error }) => ({ ...state, error })),
on(logout, (state) => ({ ...state, user: null }))
)

View File

@@ -0,0 +1,25 @@
import { createFeatureSelector, createSelector } from '@ngrx/store'
import { AuthenticationState } from './authentication.reducer'
export const getUserState =
createFeatureSelector<AuthenticationState>('authentication')
export const getUser = createSelector(
getUserState,
(state: AuthenticationState) => state.user
)
export const getToken = createSelector(
getUserState,
(state: AuthenticationState) => state.user?.token
)
export const getisLoggedIn = createSelector(
getUserState,
(state: AuthenticationState) => state.isLoggedIn
)
export const getError = createSelector(
getUserState,
(state: AuthenticationState) => state.error
)

View File

@@ -0,0 +1,67 @@
import { createAction, props } from '@ngrx/store'
import { EventInput } from '@fullcalendar/core'
export const fetchCalendar = createAction('[Calendar] Fetch Calendar')
export const fetchCalendarSuccess = createAction(
'[Calendar] Fetch Calendar Success',
props<{ events: EventInput[] }>()
)
export const fetchCalendarFailure = createAction(
'[Data] Fetch Calendar Failure',
props<{ error: string }>()
)
export const addEvent = createAction(
'[Calendar] Add Event',
props<{ event: EventInput }>()
)
export const addEventSuccess = createAction(
'[Calendar] Add Event Success',
props<{ events: EventInput }>()
)
export const addEventFailure = createAction(
'[Calendar] Add Event Failure',
props<{ error: string }>()
)
// Add Data
export const addCalendar = createAction(
'[Data] Add Calendar',
props<{ events: EventInput }>()
)
export const addCalendarSuccess = createAction(
'[Data] Add Calendar Success',
props<{ events: EventInput }>()
)
export const addCalendarFailure = createAction(
'[Data] Add Calendar Failure',
props<{ error: string }>()
)
// Update Data
export const updateCalendar = createAction(
'[Data] Update calendar event',
props<{ events: EventInput }>()
)
export const updateCalendarSuccess = createAction(
'[Data] Update calendar event Success',
props<{ events: EventInput }>()
)
export const updateCalendarFailure = createAction(
'[Data] Update calendar event Failure',
props<{ error: string }>()
)
// Delete Data
export const deleteCalendar = createAction(
'[Data] Delete Calendar',
props<{ id: string }>()
)
export const deleteCalendarSuccess = createAction(
'[Data] Delete Calendar Success',
props<{ id: string }>()
)
export const deleteCalendarFailure = createAction(
'[Data] Delete Calendar Failure',
props<{ error: string }>()
)

View File

@@ -0,0 +1,75 @@
import { inject, Injectable } from '@angular/core'
import { of } from 'rxjs'
import { Actions, createEffect, ofType } from '@ngrx/effects'
import { catchError, map, mergeMap, tap } from 'rxjs/operators'
// Action
import {
addCalendar,
addCalendarFailure,
addCalendarSuccess,
deleteCalendar,
deleteCalendarFailure,
deleteCalendarSuccess,
fetchCalendar,
fetchCalendarFailure,
fetchCalendarSuccess,
updateCalendar,
updateCalendarFailure,
updateCalendarSuccess,
} from './calendar.actions'
import { CrudService } from '@/app/core/services/crud.service'
@Injectable()
export class CalendarEffects {
private CrudService = inject(CrudService)
private actions$ = inject(Actions)
fetchEvents$ = createEffect(() =>
this.actions$.pipe(
ofType(fetchCalendar),
mergeMap(() =>
this.CrudService.fetchCalendarEvents().pipe(
map((events) => fetchCalendarSuccess({ events })),
catchError((error) => of(fetchCalendarFailure({ error })))
)
)
)
)
addEvents$ = createEffect(() =>
this.actions$.pipe(
ofType(addCalendar),
mergeMap(({ events }) =>
this.CrudService.addCalendarEvents(events).pipe(
map(() => addCalendarSuccess({ events })),
catchError((error) => of(addCalendarFailure({ error })))
)
)
)
)
updateEvents$ = createEffect(() =>
this.actions$.pipe(
ofType(updateCalendar),
mergeMap(({ events }) =>
this.CrudService.updateCalendarEvents(events).pipe(
map(() => updateCalendarSuccess({ events })),
catchError((error) => of(updateCalendarFailure({ error })))
)
)
)
)
deleteEvents$ = createEffect(() =>
this.actions$.pipe(
ofType(deleteCalendar),
mergeMap(({ id }) =>
this.CrudService.deleteCalendarEvents(id).pipe(
map(() => deleteCalendarSuccess({ id })),
catchError((error) => of(deleteCalendarFailure({ error })))
)
)
)
)
}

View File

@@ -0,0 +1,57 @@
import { createReducer, on, Action } from '@ngrx/store'
import { EventInput } from '@fullcalendar/core'
import {
addEvent,
fetchCalendar,
fetchCalendarSuccess,
updateCalendarSuccess,
deleteCalendar,
addEventSuccess,
deleteCalendarSuccess,
addCalendarSuccess,
} from './calendar.actions'
export type CalendarState = {
events: EventInput[]
}
export const initialState: CalendarState = {
events: [],
}
export const calendarReducer = createReducer(
initialState,
on(fetchCalendar, (state) => {
return { ...state }
}),
on(fetchCalendarSuccess, (state, { events }) => {
return { ...state, events }
}),
on(addCalendarSuccess, (state, { events }) => {
return { ...state, events: [...state.events, events], error: null }
}),
on(updateCalendarSuccess, (state, { events }) => {
return {
...state,
events: state.events.map((event) =>
event.id == events.id ? events : event
),
error: null,
}
}),
on(deleteCalendarSuccess, (state, { id }) => {
return {
...state,
events: state.events.filter((event) => event.id !== id),
error: null,
}
})
)
// Selector
export function reducer(state: CalendarState | undefined, action: Action) {
return calendarReducer(state, action)
}

View File

@@ -0,0 +1,9 @@
import { createFeatureSelector, createSelector } from '@ngrx/store'
import { CalendarState } from './calendar.reducer'
export const selectDataState = createFeatureSelector<CalendarState>('Calendar')
export const getEvents = createSelector(
selectDataState,
(state: CalendarState) => state.events
)

View File

@@ -0,0 +1,100 @@
import { EventInput } from '@fullcalendar/core'
export type externalModel = {
id: number
textClass: string
className: string
title: string
}
const defaultEvents: EventInput[] = [
{
id: '1',
title: 'Interview - Backend Engineer',
start: new Date(),
className: 'bg-primary',
},
{
id: '2',
title: 'Meeting with CT Team',
start: new Date(Date.now() + 13000000),
className: 'bg-warning',
},
{
id: '3',
title: 'Meeting with Mr. Reback',
start: new Date(Date.now() + 308000000),
end: new Date(Date.now() + 338000000),
className: 'bg-info',
},
{
id: '4',
title: 'Interview - Frontend Engineer',
start: new Date(Date.now() + 60570000),
end: new Date(Date.now() + 153000000),
className: 'bg-secondary',
},
{
id: '5',
title: 'Phone Screen - Frontend Engineer',
start: new Date(Date.now() + 168000000),
className: 'bg-success',
},
{
id: '6',
title: 'Buy Design Assets',
start: new Date(Date.now() + 330000000),
end: new Date(Date.now() + 330800000),
className: 'bg-primary',
},
{
id: '7',
title: 'Setup Github Repository',
start: new Date(Date.now() + 1008000000),
end: new Date(Date.now() + 1108000000),
className: 'bg-danger',
},
{
id: '8',
title: 'Meeting with Mr. Shreyu',
start: new Date(Date.now() + 2508000000),
end: new Date(Date.now() + 2508000000),
className: 'bg-dark',
},
]
// external events
const externalEvents: externalModel[] = [
{
id: 1,
textClass: 'text-primary',
className: 'primary',
title: 'Team Building Retreat Meeting ',
},
{
id: 2,
textClass: 'text-info',
className: 'info',
title: 'Product Launch Strategy Meeting',
},
{
id: 3,
textClass: 'text-success',
className: 'success',
title: 'Monthly Sales Review',
},
{
id: 4,
textClass: 'text-danger',
className: 'danger',
title: 'Team Lunch Celebration',
},
{
id: 5,
textClass: 'text-warning',
className: 'warning',
title: 'Marketing Campaign Kickoff',
},
]
export { defaultEvents, externalEvents }

View File

@@ -0,0 +1,22 @@
import { ActionReducerMap } from '@ngrx/store'
import { LayoutState, layoutReducer } from './layout/layout-reducers'
import {
calendarReducer,
type CalendarState,
} from './calendar/calendar.reducer'
import {
authenticationReducer,
type AuthenticationState,
} from './authentication/authentication.reducer'
export interface RootReducerState {
authentication: AuthenticationState
layout: LayoutState
Calendar: CalendarState
}
export const rootReducer: ActionReducerMap<RootReducerState> = {
authentication: authenticationReducer,
layout: layoutReducer,
Calendar: calendarReducer,
}

View File

@@ -0,0 +1,19 @@
import { createAction, props } from '@ngrx/store'
export const changetheme = createAction(
'[Layout] Set Color',
props<{ color: string }>()
)
export const changetopbarcolor = createAction(
'[Layout] Set Topbar',
props<{ topbar: string }>()
)
export const changemenucolor = createAction(
'[Layout] Set Menu',
props<{ menu: string }>()
)
export const changesidebarsize = createAction(
'[Layout] Set size',
props<{ size: string }>()
)
export const resetState = createAction('[App] Reset State')

View File

@@ -0,0 +1,65 @@
import { Action, createReducer, on } from '@ngrx/store'
import { localStorageSync } from 'ngrx-store-localstorage'
import {
LAYOUT_COLOR_TYPES,
SIDEBAR_SIZE_TYPES,
TOPBAR_COLOR_TYPES,
} from './layout'
import * as appActions from './layout-action'
import {
changemenucolor,
changesidebarsize,
changetheme,
changetopbarcolor,
} from './layout-action'
export interface LayoutState {
LAYOUT_THEME: string
TOPBAR_COLOR: string
MENU_COLOR: string
MENU_SIZE: string
}
// IntialState
export const initialState: LayoutState = {
LAYOUT_THEME: LAYOUT_COLOR_TYPES.LIGHTMODE,
TOPBAR_COLOR: TOPBAR_COLOR_TYPES.LIGHT,
MENU_COLOR: TOPBAR_COLOR_TYPES.LIGHT,
MENU_SIZE: SIDEBAR_SIZE_TYPES.DEFAULT,
}
// Reducer
export const layoutReducer = createReducer(
initialState,
on(changetheme, (state, action) => ({
...state,
LAYOUT_THEME: action.color,
})),
on(changetopbarcolor, (state, action) => ({
...state,
TOPBAR_COLOR: action.topbar,
})),
on(changemenucolor, (state, action) => ({
...state,
MENU_COLOR: action.menu,
})),
on(changesidebarsize, (state, action) => ({
...state,
MENU_SIZE: action.size,
})),
on(appActions.resetState, () => initialState)
)
// Configuration for localStorageSync
export function localStorageSyncReducer(reducer: any) {
return localStorageSync({ keys: ['app'], rehydrate: true })(reducer)
}
// Selector
export function reducer(state: LayoutState | undefined, action: Action) {
return layoutReducer(state, action)
}
export const rootReducer = localStorageSyncReducer(layoutReducer)
const metaReducers = [rootReducer]

View File

@@ -0,0 +1,24 @@
import { createFeatureSelector, createSelector } from '@ngrx/store'
import { LayoutState } from './layout-reducers'
export const getLayoutState = createFeatureSelector<LayoutState>('layout')
export const getLayoutColor = createSelector(
getLayoutState,
(state: LayoutState) => state.LAYOUT_THEME
)
export const getTopbarcolor = createSelector(
getLayoutState,
(state: LayoutState) => state.TOPBAR_COLOR
)
export const getMenucolor = createSelector(
getLayoutState,
(state: LayoutState) => state.MENU_COLOR
)
export const getSidebarsize = createSelector(
getLayoutState,
(state: LayoutState) => state.MENU_SIZE
)

View File

@@ -0,0 +1,41 @@
export enum LAYOUT_TYPES {
VERTICAL = 'vertical',
HORIZONTAL = 'horizontal',
}
export enum LAYOUT_COLOR_TYPES {
LIGHTMODE = 'light',
DARKMODE = 'dark',
}
export enum LAYOUT_MODE_TYPES {
FLUID = 'fluid',
BOXED = 'boxed',
DETACHED = 'detached',
}
export enum TOPBAR_COLOR_TYPES {
LIGHT = 'light',
DARK = 'dark',
BRAND = 'brand',
}
export enum MENU_COLOR_TYPES {
LIGHT = 'light',
DARK = 'dark',
BRAND = 'brand',
}
export enum SIDEBAR_SIZE_TYPES {
DEFAULT = 'default',
COMPACT = 'compact',
CONDENSED = 'condensed',
HOVER = 'sm-hover',
FULL = 'full',
FULLSCREEN = 'fullscreen',
}
export enum LAYOUT_POSITION_TYPES {
FIXED = 'fixed',
SCROLLABLE = 'scrollable',
}

View File

@@ -0,0 +1,34 @@
import { Route } from '@angular/router'
import { RatingsComponent } from './ratings/ratings.component'
import { SweetalertComponent } from './sweetalert/sweetalert.component'
import { SwiperComponent } from './swiper/swiper.component'
import { ScrollbarComponent } from './scrollbar/scrollbar.component'
import { ToastifyComponent } from './toastify/toastify.component'
export const ADVANCED_ROUTES: Route[] = [
{
path: 'ratings',
component: RatingsComponent,
data: { title: 'Ratings' },
},
{
path: 'alert',
component: SweetalertComponent,
data: { title: 'Sweet Alert' },
},
{
path: 'swiper',
component: SwiperComponent,
data: { title: 'Swiper Slider' },
},
{
path: 'scrollbar',
component: ScrollbarComponent,
data: { title: 'Scrollbar' },
},
{
path: 'toastify',
component: ToastifyComponent,
data: { title: 'Toastify' },
},
]

View File

@@ -0,0 +1,214 @@
<app-page-title [title]="'Advanced UI'" [subtitle]="'Ratings'" />
<div class="row">
<div class="col-xl-9">
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="overview">
Overview
<a
class="btn btn-sm btn-outline-success rounded-2 float-end"
href="https://ng-bootstrap.github.io/#/components/rating/api"
target="_blank"
>
Official Website
</a>
</h5>
<p class="text-muted mb-3">
Rater js is the best star rater for the browser. No dependencies.
Unlimited number of stars
</p>
<h5 class="mt-2">Usage</h5>
<p class="mb-0">
To use Rating, follow the instructions from its documentation over
<a
href="https://ng-bootstrap.github.io/#/components/rating/api"
class="text-primary"
>
here</a
>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="basic">
Basic Rater Example
<a class="anchor-link" href="#basic">#</a>
</h5>
<div class="mb-3">
<div id="basic-rater" dir="ltr"></div>
<ngb-rating
[(rate)]="basicRating"
[starTemplate]="t"
[readonly]="true"
[max]="5"
/>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="step">
Rater with Step Example
<a class="anchor-link" href="#step">#</a>
</h5>
<div class="mb-3">
<div id="rater-step" dir="ltr"></div>
<ng-template #t let-fill="fill">
@if (fill > 0) {
<i
class="bx bxs-star text-warning fs-3"
[style.width.%]="fill"
></i>
} @else {
<i class="bx bxs-star text-muted opacity-25 fs-3"> </i>
}
</ng-template>
<ngb-rating [(rate)]="rating" [starTemplate]="t" [max]="5" />
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="custom-message">
Custom Messages Example
<a class="anchor-link" href="#custom-message">#</a>
</h5>
<div class="mb-3">
<div id="rater-message" dir="ltr"></div>
<ng-template #t let-fill="fill">
@if (fill > 0) {
<i
class="bx bxs-star text-warning fs-3"
[style.width.%]="fill"
></i>
} @else {
<i class="bx bxs-star text-muted opacity-25 fs-3"> </i>
}
</ng-template>
<ngb-rating [(rate)]="stepRating" [starTemplate]="t" [max]="5" />
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="readOnly">
ReadOnly Example
<a class="anchor-link" href="#readOnly">#</a>
</h5>
<div class="mb-3">
<div id="rater-unlimitedstar" dir="ltr"></div>
<ng-template #step let-fill="fill">
@if (fill > 0) {
<i
class="bx bxs-star text-warning fs-3"
[style.width.%]="fill"
></i>
} @else {
<i class="bx bxs-star text-muted opacity-25 fs-3"> </i>
}
</ng-template>
<ngb-rating
[(rate)]="readonly"
[readonly]="true"
[starTemplate]="step"
[max]="5"
/>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="onhover">
On Hover Event Example
<a class="anchor-link" href="#onhover">#</a>
</h5>
<div class="mb-3">
<div dir="ltr d-flex align-items-center">
<div id="rater-onhover" class="align-middle"></div>
<ng-template #tem let-fill="fill">
@if (fill > 0) {
<i
class="bx bxs-star text-warning fs-3"
[style.width.%]="fill"
></i>
} @else {
<i class="bx bxs-star text-muted opacity-25 fs-3"> </i>
}
</ng-template>
<ngb-rating
[(rate)]="hoverSelected"
[starTemplate]="tem"
(hover)="hovered = $event"
(leave)="hovered = 0"
[max]="5"
/>
<span class="ratingnum badge bg-info align-middle mt-n2 ms-2">{{
hoverSelected
}}</span>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="reset">
Clear/Reset Rater Example
<a class="anchor-link link-offset-2" href="#reset">#</a>
</h5>
<div class="mb-3">
<div dir="ltr">
<div id="raterreset" class="align-middle"></div>
<span class="clear-rating"></span>
<ng-template #tem let-fill="fill">
@if (fill > 0) {
<i
class="bx bxs-star text-warning fs-3"
[style.width.%]="fill"
></i>
} @else {
<i class="bx bxs-star text-muted opacity-25 fs-3"> </i>
}
</ng-template>
<ngb-rating [formControl]="ctrl" [starTemplate]="tem" [max]="5" />
<button
(click)="ctrl.setValue(null)"
id="raterreset-button"
class="btn btn-light btn-sm ms-2 mt-n2"
>
Reset
</button>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3">
<ui-examples-list
[linkList]="[
{ label: 'Overview', link: '#overview' },
{ label: 'Basic Example', link: '#basic' },
{ label: 'Rater with Step Example', link: '#step' },
{ label: 'Custom Messages Example', link: '#custom-message' },
{ label: 'ReadOnly Example', link: '#readOnly' },
{ label: 'On Hover Event Example', link: '#onhover' },
{ label: 'Clear/Reset Rater Example', link: '#reset' },
]"
/>
</div>
</div>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { RatingsComponent } from './ratings.component'
describe('RatingsComponent', () => {
let component: RatingsComponent
let fixture: ComponentFixture<RatingsComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RatingsComponent],
}).compileComponents()
fixture = TestBed.createComponent(RatingsComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,37 @@
import { PageTitleComponent } from '@/app/components/page-title.component'
import { UIExamplesListComponent } from '@/app/components/ui-examples-list/ui-examples-list.component'
import { Component } from '@angular/core'
import {
FormControl,
FormsModule,
ReactiveFormsModule,
Validators,
} from '@angular/forms'
import { NgbRatingModule } from '@ng-bootstrap/ng-bootstrap'
@Component({
selector: 'app-ratings',
standalone: true,
imports: [
PageTitleComponent,
NgbRatingModule,
ReactiveFormsModule,
UIExamplesListComponent,
],
templateUrl: './ratings.component.html',
styles: ``,
})
export class RatingsComponent {
basicRating = 5
rating = 3
stepRating = 0
currentRate = 2
selected = 0
hovered = 0
hoverSelected = 1
readonly = 3.5
ariaValueText(current: number, max: number) {
return `${current} out of ${max} hearts`
}
ctrl = new FormControl<number | null>(null, Validators.required)
}

View File

@@ -0,0 +1,226 @@
<app-page-title [title]="'Advanced UI'" [subtitle]="'Scrollbar'" />
<div class="row">
<div class="col-xl-9">
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="default">
Default Scroll Example
<a class="anchor-link" href="#defalut">#</a>
</h5>
<p class="text-muted">
Just use tag
<code>ngx-simplebar </code> and add <code>max-height: **px</code> oh
fix height
</p>
<div class="border rounded py-3 mb-3">
<ngx-simplebar class="px-3" style="max-height: 250px">
SimpleBar does only one thing: replace the browser's default
scrollbar with a custom CSS-styled one without losing performances.
Unlike some popular plugins, SimpleBar doesn't mimic scroll with
Javascript, causing janks and strange scrolling behaviours... You
keep the awesomeness of native scrolling...with a custom scrollbar!
<p>
SimpleBar
<strong>does NOT implement a custom scroll behaviour</strong>. It
keeps the
<strong>native</strong>
<code>overflow: auto</code>
scroll and
<strong>only</strong> replace the scrollbar visual appearance.
</p>
<h5>Design it as you want</h5>
<p>
SimpleBar uses pure CSS to style the scrollbar. You can easily
customize it as you want! Or even have multiple style on the same
page...or just keep the default style ("Mac OS" scrollbar style).
</p>
<h5>Lightweight and performant</h5>
<p>
Only 6kb minified. SimpleBar doesn't use Javascript to handle
scrolling. You keep the performances/behaviours of the native
scroll.
</p>
<h5>Supported everywhere</h5>
<p>
SimpleBar has been tested on the following browsers: Chrome,
Firefox, Safari, Edge, IE11.
</p>
</ngx-simplebar>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="rtl">
RTL Position
<a class="anchor-link" href="#rtl">#</a>
</h5>
<p class="text-muted">
Just add tag <code>ngx-simplebar</code> and use data attribute
<code> data-simplebar-direction='rtl'</code>
and add <code>max-height: **px</code> oh fix height
</p>
<div class="border rounded py-3 mb-3">
<ngx-simplebar
class="px-3"
data-simplebar-direction="rtl"
style="max-height: 250px"
>
SimpleBar does only one thing: replace the browser's default
scrollbar with a custom CSS-styled one without losing performances.
Unlike some popular plugins, SimpleBar doesn't mimic scroll with
Javascript, causing janks and strange scrolling behaviours... You
keep the awesomeness of native scrolling...with a custom scrollbar!
<p>
SimpleBar
<strong>does NOT implement a custom scroll behaviour</strong>. It
keeps the
<strong>native</strong>
<code>overflow: auto</code>
scroll and
<strong>only</strong> replace the scrollbar visual appearance.
</p>
<h5>Design it as you want</h5>
<p>
SimpleBar uses pure CSS to style the scrollbar. You can easily
customize it as you want! Or even have multiple style on the same
page...or just keep the default style ("Mac OS" scrollbar style).
</p>
<h5>Lightweight and performant</h5>
<p>
Only 6kb minified. SimpleBar doesn't use Javascript to handle
scrolling. You keep the performances/behaviours of the native
scroll.
</p>
<h5>Supported everywhere</h5>
<p>
SimpleBar has been tested on the following browsers: Chrome,
Firefox, Safari, Edge, IE11.
</p>
</ngx-simplebar>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="size">
Scroll Size
<a class="anchor-link" href="#size">#</a>
</h5>
<p class="text-muted">
Just add a tag <code>ngx-simplebar</code> and use data attribute use
data attribute <code>data-simplebar-lg</code> and add
<code>max-height: **px</code> oh fix height
</p>
<div class="border rounded py-3 mb-3">
<ngx-simplebar
class="px-3"
data-simplebar-lg
style="max-height: 250px"
>
SimpleBar does only one thing: replace the browser's default
scrollbar with a custom CSS-styled one without losing performances.
Unlike some popular plugins, SimpleBar doesn't mimic scroll with
Javascript, causing janks and strange scrolling behaviours... You
keep the awesomeness of native scrolling...with a custom scrollbar!
<p>
SimpleBar
<strong>does NOT implement a custom scroll behaviour</strong>. It
keeps the
<strong>native</strong>
<code>overflow: auto</code>
scroll and
<strong>only</strong> replace the scrollbar visual appearance.
</p>
<h5>Design it as you want</h5>
<p>
SimpleBar uses pure CSS to style the scrollbar. You can easily
customize it as you want! Or even have multiple style on the same
page...or just keep the default style ("Mac OS" scrollbar style).
</p>
<h5>Lightweight and performant</h5>
<p>
Only 6kb minified. SimpleBar doesn't use Javascript to handle
scrolling. You keep the performances/behaviours of the native
scroll.
</p>
<h5>Supported everywhere</h5>
<p>
SimpleBar has been tested on the following browsers: Chrome,
Firefox, Safari, Edge, IE11.
</p>
</ngx-simplebar>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="color">
Scroll Color
<a class="anchor-link" href="#color">#</a>
</h5>
<p class="text-muted">
Just add a tag <code>ngx-simplebar</code> and use data attribute
<code>data-simplebar data-simplebar-*</code>
and add <code>max-height: **px</code> oh fix height
</p>
<div class="border rounded py-3 mb-3">
<ngx-simplebar
class="px-3"
data-simplebar-primary
style="max-height: 250px"
>
SimpleBar does only one thing: replace the browser's default
scrollbar with a custom CSS-styled one without losing performances.
Unlike some popular plugins, SimpleBar doesn't mimic scroll with
Javascript, causing janks and strange scrolling behaviours... You
keep the awesomeness of native scrolling...with a custom scrollbar!
<p>
SimpleBar
<strong>does NOT implement a custom scroll behaviour</strong>. It
keeps the
<strong>native</strong>
<code>overflow: auto</code>
scroll and
<strong>only</strong> replace the scrollbar visual appearance.
</p>
<h5>Design it as you want</h5>
<p>
SimpleBar uses pure CSS to style the scrollbar. You can easily
customize it as you want! Or even have multiple style on the same
page...or just keep the default style ("Mac OS" scrollbar style).
</p>
<h5>Lightweight and performant</h5>
<p>
Only 6kb minified. SimpleBar doesn't use Javascript to handle
scrolling. You keep the performances/behaviours of the native
scroll.
</p>
<h5>Supported everywhere</h5>
<p>
SimpleBar has been tested on the following browsers: Chrome,
Firefox, Safari, Edge, IE11.
</p>
</ngx-simplebar>
</div>
</div>
</div>
</div>
<div class="col-xl-3">
<ui-examples-list
[linkList]="[
{ label: 'Default Scroll Example', link: '#default' },
{ label: 'RTL Position', link: '#rtl' },
{ label: 'Scroll Size', link: '#size' },
{ label: 'Scroll Color', link: '#color' },
]"
/>
</div>
</div>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ScrollbarComponent } from './scrollbar.component'
describe('ScrollbarComponent', () => {
let component: ScrollbarComponent
let fixture: ComponentFixture<ScrollbarComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ScrollbarComponent],
}).compileComponents()
fixture = TestBed.createComponent(ScrollbarComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,17 @@
import { PageTitleComponent } from '@/app/components/page-title.component'
import { UIExamplesListComponent } from '@/app/components/ui-examples-list/ui-examples-list.component'
import { Component } from '@angular/core'
import { SimplebarAngularModule } from 'simplebar-angular'
@Component({
selector: 'app-scrollbar',
standalone: true,
imports: [
PageTitleComponent,
SimplebarAngularModule,
UIExamplesListComponent,
],
templateUrl: './scrollbar.component.html',
styles: ``,
})
export class ScrollbarComponent {}

View File

@@ -0,0 +1,166 @@
<app-page-title [title]="'Advanced UI'" [subtitle]="'Sweet Alert'" />
<div class="row">
<div class="col-xl-9">
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="overview">
Overview
<a
class="btn btn-sm btn-outline-success rounded-2 float-end"
href="https://sweetalert2.github.io/"
target="_blank"
>
Official Website
</a>
</h5>
<p class="text-muted mb-3">
A beautiful, responsive, customizable, accessible (WAI-ARIA)
replacement for JavaScript's popup boxes
</p>
<h5 class="mt-2">Usage</h5>
<p class="mb-0">
To use SweetAlert, follow the instructions from its documentation
over<a href="https://sweetalert2.github.io/" class="text-primary">
here</a
>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="basic">
Basic<a class="anchor-link" href="#basic">#</a>
</h5>
<div class="mb-3">
<button
type=" "
class="btn btn-primary"
(click)="basicMessage()"
id="sweetalert-basic"
>
Click me
</button>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="title">
A Title with a Text Under<a class="anchor-link" href="#title">#</a>
</h5>
<div class="mb-3">
<button
type="button"
(click)="titleText()"
class="btn btn-primary"
id="sweetalert-title"
>
Click me
</button>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="message">
Message<a class="anchor-link" href="#message">#</a>
</h5>
<div class="hstack gap-2 mb-3">
<button
type="button"
class="btn btn-success"
(click)="success()"
id="sweetalert-success"
>
Success
</button>
<button
type="button"
class="btn btn-warning"
(click)="warning()"
id="sweetalert-warning"
>
Warning
</button>
<button
type="button"
class="btn btn-info"
(click)="info()"
id="sweetalert-info"
>
Info
</button>
<button
type="button"
class="btn btn-danger"
(click)="danger()"
id="sweetalert-error"
>
Error
</button>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="longcontent">
Long content Images Message<a class="anchor-link" href="#longcontent"
>#</a
>
</h5>
<div class="mb-3">
<button
type="button"
(click)="longContent()"
class="btn btn-primary"
id="sweetalert-longcontent"
>
Click me
</button>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="parameter">
Parameter<a class="anchor-link" href="#parameter">#</a>
</h5>
<div class="mb-3">
<button
type="button"
(click)="parameter()"
class="btn btn-primary"
id="sweetalert-params"
>
Click me
</button>
</div>
</div>
</div>
</div>
<div class="col-xl-3">
<ui-examples-list
[linkList]="[
{ label: 'Overview', link: '#overview' },
{ label: 'Basic', link: '#basic' },
{ label: 'A Title with a Text Under', link: '#title' },
{ label: 'Message', link: '#message' },
{ label: 'long content Images Message', link: '#longcontent' },
{ label: 'Parameter', link: '#parameter' },
]"
/>
</div>
</div>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SweetalertComponent } from './sweetalert.component'
describe('SweetalertComponent', () => {
let component: SweetalertComponent
let fixture: ComponentFixture<SweetalertComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SweetalertComponent],
}).compileComponents()
fixture = TestBed.createComponent(SweetalertComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,146 @@
import { PageTitleComponent } from '@/app/components/page-title.component'
import { UIExamplesListComponent } from '@/app/components/ui-examples-list/ui-examples-list.component'
import { Component } from '@angular/core'
import Swal from 'sweetalert2'
@Component({
selector: 'app-sweetalert',
standalone: true,
imports: [PageTitleComponent, UIExamplesListComponent],
templateUrl: './sweetalert.component.html',
styles: ``,
})
export class SweetalertComponent {
basicMessage() {
Swal.fire({
title: 'Any fool can use a computer',
confirmButtonColor: '#1c84ee',
showCloseButton: false,
})
}
titleText() {
Swal.fire({
title: 'The Internet?',
text: 'That thing is still around?',
icon: 'question',
customClass: {
confirmButton: 'btn btn-primary w-xs mt-2',
},
buttonsStyling: false,
showCloseButton: false,
})
}
success() {
Swal.fire({
title: 'Good job!',
text: 'You clicked the button!',
icon: 'success',
showCancelButton: true,
customClass: {
confirmButton: 'btn btn-primary w-xs me-2 mt-2',
cancelButton: 'btn btn-danger w-xs mt-2',
},
buttonsStyling: false,
showCloseButton: false,
})
}
danger() {
Swal.fire({
title: 'Oops...',
text: 'Something went wrong!',
icon: 'error',
customClass: {
confirmButton: 'btn btn-primary w-xs mt-2',
},
buttonsStyling: false,
footer: '<a href="">Why do I have this issue?</a>',
showCloseButton: false,
})
}
info() {
Swal.fire({
title: 'Oops...',
text: 'Something went wrong!',
icon: 'info',
customClass: {
confirmButton: 'btn btn-primary w-xs mt-2',
},
buttonsStyling: false,
footer: '<a href="">Why do I have this issue?</a>',
showCloseButton: false,
})
}
warning() {
Swal.fire({
title: 'Oops...',
text: 'Something went wrong!',
icon: 'warning',
customClass: {
confirmButton: 'btn btn-primary w-xs mt-2',
},
buttonsStyling: false,
footer: '<a href="">Why do I have this issue?</a>',
showCloseButton: false,
})
}
longContent() {
Swal.fire({
imageUrl: 'https://placeholder.pics/svg/300x1500',
imageHeight: 1500,
imageAlt: 'A tall image',
customClass: {
confirmButton: 'btn btn-primary w-xs mt-2',
},
buttonsStyling: false,
showCloseButton: false,
})
}
parameter() {
Swal.fire({
title: 'Are you sure?',
text: "You won't be able to revert this!",
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, delete it!',
cancelButtonText: 'No, cancel!',
customClass: {
confirmButton: 'btn btn-primary w-xs me-2 mt-2',
cancelButton: 'btn btn-danger w-xs mt-2',
},
buttonsStyling: false,
showCloseButton: false,
}).then(function (result) {
if (result.value) {
Swal.fire({
title: 'Deleted!',
text: 'Your file has been deleted.',
icon: 'success',
customClass: {
confirmButton: 'btn btn-primary w-xs mt-2',
},
buttonsStyling: false,
})
} else if (
// Read more about handling dismissals
result.dismiss === Swal.DismissReason.cancel
) {
Swal.fire({
title: 'Cancelled',
text: 'Your imaginary file is safe :)',
icon: 'error',
customClass: {
confirmButton: 'btn btn-primary mt-2',
},
buttonsStyling: false,
})
}
})
}
}

View File

@@ -0,0 +1,567 @@
<app-page-title [title]="'Advanced UI'" [subtitle]="'Swiper Slider'" />
<div class="row">
<div class="col-xl-9">
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="overview">
Overview
<a
class="btn btn-sm btn-outline-success rounded-2 float-end"
href="https://swiperjs.com/get-started"
target="_blank"
>
Official Website
</a>
</h5>
<p class="text-muted mb-3">
Swiper is the most modern slider with hardware accelerated transitions
and amazing native behavior.
</p>
<h5 class="mt-2">Usage</h5>
<p>
To use Swiper, follow the instructions from its documentation over
<a href="https://swiperjs.com/get-started" class="text-primary">
here</a
>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="default">
Default Swiper
<a class="anchor-link" href="#default">#</a>
</h5>
<p class="text-muted">
Use
<code>[config]="swiperConfig" init="false"</code>
attribute to set a default swiper.
</p>
<div class="row">
<div class="col-lg-6 mb-3">
<div class="swiper rounded" data-swiper="default">
<swiper-container
class="swiper-wrapper"
[config]="swiperConfig"
init="false"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-1.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-2.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-3.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
</swiper-container>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="navigation">
Navigation & Pagination Swiper
<a class="anchor-link" href="#navigation">#</a>
</h5>
<p class="text-muted">
Use
<code>[config]="swiperNavigation" init="false"</code>
attribute to set a swiper with navigation and pagination.
</p>
<div class="row">
<div class="col-lg-6 mb-3">
<div class="swiper rounded">
<swiper-container
[config]="swiperNavigation"
class="swiper-wrapper"
init="false"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-4.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-5.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-6.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
</swiper-container>
<div class="swiper-button-next basic-next"></div>
<div class="swiper-button-prev basic-prev"></div>
<div class="swiper-pagination basic-pagination"></div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="pagination-dynamic">
Pagination Dynamic Swiper
<a class="anchor-link" href="#pagination-dynamic">#</a>
</h5>
<p class="text-muted">
Use
<code> init="false" [config]="swiperPagination"</code>
attribute to set a swiper with dynamic and pagination.
</p>
<div class="row">
<div class="col-lg-6 mb-3">
<div class="swiper rounded" data-swiper="dynamic">
<swiper-container
class="swiper-wrapper"
init="false"
[config]="swiperPagination"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-4.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-5.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-6.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
</swiper-container>
<div
id="swiper-pagination"
class="swiper-pagination dynamic-pagination"
></div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="effect-fade">
Effect Fade Swiper
<a class="anchor-link" href="#effect-fade">#</a>
</h5>
<p class="text-muted">
Use
<code>[config]="swiperfadeEffect" init="false"</code>
attribute to set a swiper with fade effect.
</p>
<div class="row">
<div class="col-lg-6 mb-3">
<div class="swiper rounded" data-swiper="effect-fade">
<swiper-container
class="swiper-wrapper"
[config]="swiperfadeEffect"
init="false"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-3.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-4.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-5.jpg"
alt=""
class="img-fluid"
/>
</swiper-slide>
</swiper-container>
<div
id="swiper-pagination"
class="swiper-pagination effect-pagination"
></div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="effect-creative">
Effect Creative Swiper
<a class="anchor-link" href="#effect-creative">#</a>
</h5>
<p class="text-muted">
Use
<code> [config]="swiperCreativeEffect" init="false"</code>
attribute to set a swiper with creative custom effect.
</p>
<div class="row">
<div class="col-lg-6 mb-3">
<div class="swiper" data-swiper="creative">
<swiper-container
class="swiper-wrapper"
[config]="swiperCreativeEffect"
init="false"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-3.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-4.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-5.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
</swiper-container>
<div class="swiper-pagination creative-pagination"></div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="effect-flip">
Effect Flip Swiper
<a class="anchor-link" href="#effect-flip">#</a>
</h5>
<p class="text-muted">
Use
<code>[config]="swiperFlip" init="false"</code>
attribute to set a swiper with flip custom effect.
</p>
<div class="row">
<div class="col-lg-6 mb-3">
<div class="swiper" data-swiper="flip">
<swiper-container
class="swiper-wrapper"
[config]="swiperFlip"
init="false"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-3.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-4.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-5.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
</swiper-container>
<div class="swiper-pagination swiper-flip"></div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="scrollbar">
Scrollbar Swiper
<a class="anchor-link" href="#scrollbar">#</a>
</h5>
<p class="text-muted">
Use
<code>[config]="swiperScroll" init="false"</code>
attribute to set a swiper with scrollbar pagination.
</p>
<div class="row">
<div class="col-lg-6 mb-3">
<div class="swiper" data-swiper="scrollbar">
<swiper-container
class="swiper-wrapper"
[config]="swiperScroll"
init="false"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-3.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-4.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-5.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
</swiper-container>
<div class="swiper-button-next"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-scrollbar"></div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="vertical">
Vertical Swiper
<a class="anchor-link" href="#vertical">#</a>
</h5>
<p class="text-muted">
Use
<code>[config]="verticalConfig" init="false"</code>
attribute to set a swiper with vertical pagination.
</p>
<div class="row">
<div class="col-lg-6 mb-3">
<div class="swiper" data-swiper="vertical" style="height: 320px">
<swiper-container
[config]="verticalConfig"
init="false"
class="swiper-wrapper"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-3.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-4.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-5.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
</swiper-container>
<div class="swiper-pagination vertical-pagination"></div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="mousewheel">
Mousewheel Control Swiper
<a class="anchor-link" href="#mousewheel">#</a>
</h5>
<p class="text-muted">
Use
<code>init="false" [config]="swiperMouseWheel"</code>
attribute to set a swiper with mousewheel scroll.
</p>
<div class="row">
<div class="col-lg-6 mb-3">
<div
class="swiper rounded"
data-swiper="mousewheel"
style="height: 324px"
>
<swiper-container
class="swiper-wrapper"
init="false"
[config]="swiperMouseWheel"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-3.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-4.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-5.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
</swiper-container>
<div class="swiper-pagination mouse-wheel-pagination"></div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="responsive">
Responsive Breakpoints Swiper
<a class="anchor-link" href="#overview">#</a>
</h5>
<p class="text-muted">
Use
<code> init="false" [config]="resposiveConfig"</code>
attribute to set a responsive swiper.
</p>
<div class="mb-3">
<div
class="swiper rounded gallery-light pb-4"
data-swiper="responsive"
>
<swiper-container
class="swiper-wrapper"
init="false"
[config]="resposiveConfig"
>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-1.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-2.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-3.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-4.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-5.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
<swiper-slide class="swiper-slide">
<img
src="assets/images/small/img-6.jpg"
alt=""
class="img-fluid rounded"
/>
</swiper-slide>
</swiper-container>
<div
class="swiper-pagination swiper-pagination-dark responsive-pagination"
></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3">
<ui-examples-list
[linkList]="[
{ label: 'Overview', link: '#overview' },
{ label: 'Default Swiper', link: '#default' },
{ label: 'Navigation & Pagination Swiper', link: '#navigation' },
{ label: 'Pagination Dynamic Swiper', link: '#pagination-dynamic' },
{ label: 'Effect Fade Swiper', link: '#effect-fade' },
{ label: 'Effect Creative Swiper', link: '#effect-creative' },
{ label: 'Effect Flip Swiper', link: '#effect-flip' },
{ label: 'Scrollbar Swiper', link: '#scrollbar' },
{ label: 'Vertical Swiper', link: '#vertical' },
{ label: 'Mousewheel Control Swiper', link: '#mousewheel' },
{ label: 'Responsive Breakpoints Swiper', link: '#responsive' },
]"
/>
</div>
</div>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SwiperComponent } from './swiper.component'
describe('SwiperComponent', () => {
let component: SwiperComponent
let fixture: ComponentFixture<SwiperComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SwiperComponent],
}).compileComponents()
fixture = TestBed.createComponent(SwiperComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,187 @@
import { PageTitleComponent } from '@/app/components/page-title.component'
import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core'
import { SwiperOptions } from 'swiper/types'
import {
Autoplay,
EffectCreative,
EffectFade,
EffectFlip,
Mousewheel,
Navigation,
Pagination,
Scrollbar,
} from 'swiper/modules'
import { UIExamplesListComponent } from '@/app/components/ui-examples-list/ui-examples-list.component'
import { register } from 'swiper/element'
import { SwiperDirective } from '@/app/components/swiper-directive.component'
register()
@Component({
selector: 'app-swiper',
standalone: true,
imports: [PageTitleComponent, UIExamplesListComponent, SwiperDirective],
templateUrl: './swiper.component.html',
styles: ``,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class SwiperComponent {
swiperConfig: SwiperOptions = {
loop: true,
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
}
swiperNavigation: SwiperOptions = {
modules: [Autoplay, Pagination, Navigation],
loop: true,
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
pagination: {
clickable: true,
el: '.basic-pagination',
},
navigation: {
nextEl: '.basic-next',
prevEl: '.basic-prev',
},
}
swiperPagination: SwiperOptions = {
modules: [Autoplay, Pagination],
loop: true,
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
pagination: {
clickable: true,
el: '.dynamic-pagination',
dynamicBullets: true,
},
}
swiperfadeEffect: SwiperOptions = {
modules: [Pagination, Autoplay, EffectFade],
loop: true,
effect: 'fade',
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
pagination: {
clickable: true,
el: '.effect-pagination',
},
}
swiperCreativeEffect: SwiperOptions = {
modules: [Autoplay, Pagination, EffectCreative],
loop: true,
grabCursor: true,
effect: 'creative',
// EffectCreative: {
// prev: {
// shadow: true,
// translate: [0, 0, -400],
// },
// next: {
// translate: ["100%", 0, 0],
// },
// },
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
pagination: {
el: '.creative-pagination',
clickable: true,
},
}
swiperFlip: SwiperOptions = {
modules: [EffectFlip, Pagination],
loop: true,
effect: 'flip',
grabCursor: true,
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
pagination: {
el: '.swiper-flip',
clickable: true,
},
}
swiperScroll: SwiperOptions = {
modules: [Autoplay, Scrollbar, Navigation],
loop: true,
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
scrollbar: {
el: '.swiper-scrollbar',
hide: true,
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
}
verticalConfig: SwiperOptions = {
modules: [Autoplay, Pagination],
loop: true,
direction: 'vertical',
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
pagination: {
el: '.vertical-pagination',
clickable: true,
},
}
swiperMouseWheel: SwiperOptions = {
modules: [Autoplay, Pagination, Mousewheel],
loop: true,
direction: 'vertical',
mousewheel: true,
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
pagination: {
el: '.mouse-wheel-pagination',
clickable: true,
},
}
resposiveConfig: SwiperOptions = {
modules: [Pagination],
loop: true,
slidesPerView: 1,
spaceBetween: 10,
pagination: {
el: '.responsive-pagination',
clickable: true,
},
breakpoints: {
768: {
slidesPerView: 2,
spaceBetween: 40,
},
1200: {
slidesPerView: 3,
spaceBetween: 50,
},
},
}
}

View File

@@ -0,0 +1,224 @@
<app-page-title [title]="'Advanced UI'" [subtitle]="'Toastify'" />
<div class="row">
<div class="col-xl-9">
<div class="card">
<div class="card-body">
<h5 class="card-title mb-1 anchor" id="overview">
Overview
<a
class="btn btn-sm btn-outline-success rounded-2 float-end"
href="https://www.npmjs.com/package/ngx-toastr"
target="_blank"
>
Official Website
</a>
</h5>
<p class="text-muted mb-3">Better notification messages</p>
<h5 class="mt-2">Usage</h5>
<p class="mb-0">
To use Toastify, follow the instructions from its documentation over
<a
href="https://www.npmjs.com/package/ngx-toastr#setup"
class="text-primary"
>
here</a
>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title anchor mb-3" id="basic">
Basic Toastify JS Example
<a class="anchor-link" href="#basic">#</a>
</h5>
<div class="mb-3">
<div class="hstack flex-wrap gap-2">
<button
type="button"
data-toast
data-toast-text="Welcome Back! This is a Toast Notification"
(click)="default()"
class="btn btn-light w-xs"
>
Default
</button>
<button
type="button"
(click)="success('toast-top-center')"
class="btn btn-light w-xs"
>
Success
</button>
<button
type="button"
(click)="warning()"
class="btn btn-light w-xs"
>
Warning
</button>
<button
type="button"
(click)="error()"
data-toast
data-toast-text="Error ! An error occurred."
data-toast-gravity="top"
data-toast-position="center"
data-toast-className="danger"
data-toast-duration="3000"
class="btn btn-light w-xs"
>
Error
</button>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="display_position">
Display Position Example
<a class="anchor-link" href="#display_position">#</a>
</h5>
<div class="mb-3">
<div class="hstack flex-wrap gap-2">
<button
type="button"
(click)="positionToast('toast-top-left')"
data-toast
data-toast-text="Welcome Back ! This is a Toast Notification"
data-toast-gravity="top"
data-toast-position="left"
data-toast-duration="3000"
data-toast-close="close"
class="btn btn-light w-xs"
>
Top Left
</button>
<button
type="button"
(click)="positionToast('toast-top-center')"
class="btn btn-light w-xs"
>
Top Center
</button>
<button
type="button"
(click)="positionToast('toast-top-right')"
data-toast
data-toast-text="Welcome Back ! This is a Toast Notification"
data-toast-gravity="top"
data-toast-position="right"
data-toast-duration="3000"
data-toast-close="close"
class="btn btn-light w-xs"
>
Top Right
</button>
<button
type="button"
(click)="positionToast('toast-bottom-left')"
data-toast
data-toast-text="Welcome Back ! This is a Toast Notification"
data-toast-gravity="bottom"
data-toast-position="left"
data-toast-duration="3000"
data-toast-close="close"
class="btn btn-light w-xs"
>
Bottom Left
</button>
<button
type="button"
(click)="positionToast('toast-bottom-center')"
data-toast
data-toast-text="Welcome Back ! This is a Toast Notification"
data-toast-gravity="bottom"
data-toast-position="center"
data-toast-duration="3000"
data-toast-close="close"
class="btn btn-light w-xs"
>
Bottom Center
</button>
<button
type="button"
(click)="positionToast('toast-bottom-right')"
data-toast
data-toast-text="Welcome Back ! This is a Toast Notification"
data-toast-gravity="bottom"
data-toast-position="right"
data-toast-duration="3000"
data-toast-close="close"
class="btn btn-light w-xs"
>
Bottom Right
</button>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3 anchor" id="rater">
Offset, Close Button & Duration Example
<a class="anchor-link" href="#rater">#</a>
</h5>
<div class="mb-3">
<div class="d-flex align-items-center flex-wrap gap-2">
<button
type="button"
(click)="positionToast('toast-top-right')"
class="btn btn-light w-xs"
>
Offset Position
</button>
<button
type="button"
(click)="positionToast('toast-top-right')"
class="btn btn-light w-xs"
>
Close icon Display
</button>
<button
type="button"
(click)="durationToast('toast-top-right')"
class="btn btn-light w-xs"
>
Duration
</button>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3">
<ui-examples-list
[linkList]="[
{ label: 'Overview', link: '#overview' },
{ label: 'Basic', link: '#basic' },
{ label: 'Display Position Example', link: '#display_position' },
{ label: 'Offset, Close Button & Duration Example', link: '#rater' },
]"
/>
</div>
</div>

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ToastifyComponent } from './toastify.component'
describe('ToastifyComponent', () => {
let component: ToastifyComponent
let fixture: ComponentFixture<ToastifyComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ToastifyComponent],
}).compileComponents()
fixture = TestBed.createComponent(ToastifyComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -0,0 +1,66 @@
import { PageTitleComponent } from '@/app/components/page-title.component'
import { UIExamplesListComponent } from '@/app/components/ui-examples-list/ui-examples-list.component'
import { Component } from '@angular/core'
import { ToastrService } from 'ngx-toastr'
@Component({
selector: 'app-toastify',
standalone: true,
imports: [PageTitleComponent, UIExamplesListComponent],
templateUrl: './toastify.component.html',
styles: ``,
})
export class ToastifyComponent {
constructor(public toastService: ToastrService) {}
default() {
this.toastService.show('Welcome Back! This is a Toast Notification', '', {
toastClass: 'btn-light text-bg-primary px-3 py-2 mt-2 rounded-1',
positionClass: 'toast-top-right',
})
}
success(position: any) {
this.toastService.show('Your application was successfully sent', '', {
toastClass: 'btn-light text-bg-success px-3 py-2 mt-2 rounded-1',
positionClass: position,
})
}
warning() {
this.toastService.show('Warning ! Something went wrong try again', '', {
toastClass: 'btn-light text-bg-warning px-3 py-2 mt-2 rounded-1',
positionClass: 'toast-top-center',
})
}
error() {
this.toastService.show('Error ! An error occurred.', '', {
toastClass: 'btn-light text-bg-danger px-3 py-2 mt-2 rounded-1',
positionClass: 'toast-top-center',
})
}
positionToast(position: any) {
this.toastService.success(
'Welcome Back ! This is a Toast Notification',
'',
{
timeOut: 3000,
closeButton: true,
toastClass: 'btn-light text-bg-success px-3 py-2 mt-2 rounded-1',
positionClass: position,
tapToDismiss: true,
}
)
}
durationToast(position: any) {
this.toastService.show('Toast Duration 5s', '', {
timeOut: 5000,
positionClass: position,
toastClass: 'btn-light text-bg-success px-3 py-2 mt-2 rounded-1',
closeButton: false,
})
}
}

View File

@@ -0,0 +1,34 @@
import { Route } from '@angular/router'
import { ChatComponent } from './chat/chat.component'
import { ContactsComponent } from './contacts/contacts.component'
import { EmailComponent } from './email/email.component'
import { SocialComponent } from './social/social.component'
import { TodoComponent } from './todo/todo.component'
export const APPS_ROUTES: Route[] = [
{
path: 'chat',
component: ChatComponent,
data: { title: 'Chat' },
},
{
path: 'email',
component: EmailComponent,
data: { title: 'Email' },
},
{
path: 'todo',
component: TodoComponent,
data: { title: 'To do' },
},
{
path: 'social',
component: SocialComponent,
data: { title: 'Social' },
},
{
path: 'contacts',
component: ContactsComponent,
data: { title: 'Contacts' },
},
]

View File

@@ -0,0 +1,17 @@
<div class="row g-1">
<div class="col-xxl-3">
<div
class="offcanvas-xxl offcanvas-start h-100"
container="body"
tabindex="-1"
id="Contactoffcanvas"
aria-labelledby="ContactoffcanvasLabel"
>
<chat-contacts (profileDetail)="receiveDataFromChild($event)" />
</div>
</div>
<div class="col-xxl-9">
<chat-list [profileDetail]="profileDetail" />
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More