@@ -275,45 +275,8 @@ export class PlanPokemonComponent {
this.representative_pokemon = null;
this.evolve_to = []
this.breed_for = []
- this.cdr.detectChanges();
- this.pokemonService.getPokemonFromPFIC(this.pokemon_family.representative).subscribe({
- next: (pokemon) => {
- this.representative_pokemon = pokemon
- },
- error: (error) => {
- console.error('Error loading Pokemon:', error);
- this.cdr.markForCheck();
- }
- });
-
- for(const target of this.pokemon_family.evolve_to) {
- this.pokemonService.getPokemonFromPFIC(target).subscribe({
- next: (pokemon) => {
- if(pokemon) {
- this.evolve_to.push(pokemon)
- }
- },
- error: (error) => {
- console.error('Error loading Pokemon:', error);
- this.cdr.markForCheck();
- }
- });
- }
-
- for(const target of this.pokemon_family.breed_for) {
- this.pokemonService.getPokemonFromPFIC(target).subscribe({
- next: (pokemon) => {
- if(pokemon) {
- this.breed_for.push(pokemon)
- }
- },
- error: (error) => {
- console.error('Error loading Pokemon:', error);
- this.cdr.markForCheck();
- }
- });
- }
+ this.handlePokemonFamilyChange(this.pokemon_family);
}
ngOnChanges(changes: SimpleChanges) {
@@ -337,7 +300,6 @@ export class PlanPokemonComponent {
this.representative_pokemon = null;
this.evolve_to = []
this.breed_for = []
- this.cdr.detectChanges();
this.pokemonService.getPokemonFromPFIC(this.pokemon_family.representative).subscribe({
next: (pokemon) => {
@@ -349,33 +311,41 @@ export class PlanPokemonComponent {
}
});
- for(const target of this.pokemon_family.evolve_to) {
+ const evolveToArray: Pokemon[] = [];
+ newFamily.evolve_to.forEach((target) => {
this.pokemonService.getPokemonFromPFIC(target).subscribe({
next: (pokemon) => {
- if(pokemon) {
- this.evolve_to.push(pokemon)
+ if (pokemon) {
+ evolveToArray.push(pokemon);
}
},
+ complete: () => {
+ this.customSort(evolveToArray);
+ this.evolve_to = [...evolveToArray]; // Assign once all have completed
+ },
error: (error) => {
- console.error('Error loading Pokemon:', error);
- this.cdr.markForCheck();
+ console.error('Error loading Pokémon:', error);
}
});
- }
+ });
- for(const target of this.pokemon_family.breed_for) {
+ const breedForArray: Pokemon[] = [];
+ newFamily.breed_for.forEach((target) => {
this.pokemonService.getPokemonFromPFIC(target).subscribe({
next: (pokemon) => {
- if(pokemon) {
- this.breed_for.push(pokemon)
+ if (pokemon) {
+ breedForArray.push(pokemon);
}
},
+ complete: () => {
+ this.customSort(breedForArray);
+ this.breed_for = [...breedForArray]; // Assign once all have completed
+ },
error: (error) => {
- console.error('Error loading Pokemon:', error);
- this.cdr.markForCheck();
+ console.error('Error loading Pokémon:', error);
}
});
- }
+ });
}
get hasTargets(): boolean {
@@ -431,4 +401,31 @@ export class PlanPokemonComponent {
getRepresentativePokemon() {
return this.pokemonService.getPokemonFromPFIC(this.pokemon_family.representative)
}
+
+ trackByPfic(index: number, item: any): string {
+ return item.PFIC; // Assuming PFIC or another unique identifier is available
+ }
+
+ parsePfic(pfic: string): (number | string)[] {
+ const parts = pfic.split('-');
+ return parts.map(part => /^\d+$/.test(part) ? parseInt(part) : part);
+ }
+
+ customSort(arr: Pokemon[]): Pokemon[] {
+ return arr.sort((a, b) => {
+ const parsedA = this.parsePfic(a.PFIC);
+ const parsedB = this.parsePfic(b.PFIC);
+
+ for (let i = 0; i < Math.min(parsedA.length, parsedB.length); i++) {
+ if (parsedA[i] !== parsedB[i]) {
+ if (typeof parsedA[i] === 'number' && typeof parsedB[i] === 'number') {
+ return (parsedA[i] as number) - (parsedB[i] as number);
+ }
+ return (parsedA[i] as string).localeCompare(parsedB[i] as string);
+ }
+ }
+
+ return parsedA.length - parsedB.length;
+ });
+ }
}
\ No newline at end of file
diff --git a/src/app/features/plan/plan-pokemon/plan-pokemonV2.component.ts b/src/app/features/plan/plan-pokemon/plan-pokemonV2.component.ts
new file mode 100644
index 0000000..6101260
--- /dev/null
+++ b/src/app/features/plan/plan-pokemon/plan-pokemonV2.component.ts
@@ -0,0 +1,325 @@
+import { Component, Input, Output, EventEmitter, ChangeDetectorRef, SimpleChanges } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { MatIconModule } from '@angular/material/icon';
+import { MatChipsModule } from '@angular/material/chips';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { PokemonFamilyEntry } from '../../../core/models/plan.model';
+import { LazyImgDirective } from '../../../shared/directives/lazy-img.directive';
+import { PokemonService } from '../../../core/services/pokemon.service';
+import { Pokemon } from '../../../core/models/pokemon.model';
+import { MatCardModule } from '@angular/material/card';
+
+// Define an interface for the status update event
+interface PokemonStatusEvent {
+ pfic: string;
+ caught: boolean;
+ completed?: boolean; // Make completed optional
+ }
+
+@Component({
+ selector: 'app-plan-pokemonV2',
+ standalone: true,
+ imports: [
+ CommonModule,
+ MatExpansionModule,
+ MatIconModule,
+ MatChipsModule,
+ MatTooltipModule,
+ LazyImgDirective,
+ MatCardModule
+ ],
+ template: `
+
+
+
+
+ `,
+ styles: [`
+ .pokemon-panel {
+ margin-bottom: 8px;
+ }
+
+ .pokemon-row{
+ margin:5px;
+ cursor: pointer;
+ transition: transform 0.2s, box-shadow 0.2s;
+ }
+
+ .pokemon-row:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2);
+ }
+
+ .pokemon-row.selected {
+ border: 2px solid #4CAF50;
+ transform: translateY(-2px);
+ }
+
+ .pokemon-header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ width: 100%;
+ }
+
+ .pokemon-thumbnail {
+ width: 48px;
+ height: 48px;
+ object-fit: contain;
+ }
+
+ .pokemon-info {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-grow: 1;
+ }
+
+ .pokemon-name {
+ font-weight: 500;
+ font-size: 1.1em;
+ }
+
+ .form-name {
+ color: #666;
+ font-size: 0.9em;
+ margin-left: 4px;
+ }
+
+ .catch-info {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-width: 80px;
+ }
+
+ .pokeball-icon {
+ width: 24px;
+ height: 24px;
+ cursor: pointer;
+ transition: filter 0.3s ease;
+ }
+
+ .grayscale {
+ filter: grayscale(100%);
+ }
+
+ .catch-count {
+ font-weight: 500;
+ color: #4CAF50;
+ }
+
+ .targets-grid {
+ padding: 16px 8px;
+ gap: 24px;
+ }
+
+ .target-section {
+ h4 {
+ margin: 0 0 12px 0;
+ color: #666;
+ }
+ }
+
+ .target-cards {
+ gap: 16px;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .target-card {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 8px;
+ border: 1px solid #eee;
+ border-radius: 8px;
+ transition: background-color 0.3s ease;
+
+ &:hover {
+ background-color: #f5f5f5;
+ }
+
+ &.completed {
+ background-color: #f0f0f0;
+ }
+ }
+
+ .target-image {
+ width: 64px;
+ height: 64px;
+ object-fit: contain;
+ }
+
+ .target-details {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .target-name {
+ font-weight: 500;
+
+ span {
+ color: #666;
+ font-size: 0.9em;
+ }
+ }
+
+ mat-chip-list {
+ display: flex;
+ gap: 4px;
+ }
+ `]
+})
+export class PlanPokemonV2Component {
+ @Input() pokemon_family!: PokemonFamilyEntry;
+ @Input() isSelected = false;
+ @Output() statusUpdate = new EventEmitter
();
+ @Output() familySelected = new EventEmitter();
+
+ representative_pokemon: Pokemon | null = null;
+
+ constructor(
+ public pokemonService: PokemonService,
+ private cdr: ChangeDetectorRef
+ ) {}
+
+ ngOnInit() {
+ this.representative_pokemon = null;
+ this.handlePokemonFamilyChange(this.pokemon_family);
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['pokemon_family']) {
+ const currentFamily = changes['pokemon_family'].currentValue;
+ const previousFamily = changes['pokemon_family'].previousValue;
+
+ // Check if there's a meaningful change
+ if (currentFamily && currentFamily !== previousFamily) {
+ // Your logic here, e.g., re-fetch data or reset states
+ this.handlePokemonFamilyChange(currentFamily);
+ }
+ }
+ }
+
+ private handlePokemonFamilyChange(newFamily: PokemonFamilyEntry) {
+ // This function contains logic to handle the input change.
+ // For example, resetting component states or fetching additional data.
+ console.log('Pokemon family has changed:', newFamily);
+
+ this.representative_pokemon = null;
+
+ this.pokemonService.getPokemonFromPFIC(this.pokemon_family.representative).subscribe({
+ next: (pokemon) => {
+ this.representative_pokemon = pokemon
+ },
+ error: (error) => {
+ console.error('Error loading Pokemon:', error);
+ this.cdr.markForCheck();
+ }
+ });
+ }
+
+ get hasTargets(): boolean {
+ return this.pokemon_family.evolve_to.length > 0 || this.pokemon_family.breed_for.length > 0;
+ }
+
+ isTargetCompleted(pfic: string): boolean {
+ return this.pokemonService.isTargetCompleted(pfic);
+ }
+
+ calculateTotalNeeded(): number {
+ let total = 1; // Initial catch
+ let breedCount = 0;
+ let evolveCount = 0;
+
+ // Calculate breeding needs
+ if (this.pokemon_family.breed_for.length > 0) {
+ breedCount = 1; // We only need one for breeding, regardless of how many we breed
+ }
+
+ // Calculate evolution needs
+ this.pokemon_family.evolve_to.forEach(target => {
+ if (!this.isTargetCompleted(target)) {
+ evolveCount += 1;
+ }
+ });
+
+ return total + breedCount + evolveCount;
+ }
+
+ updateCatchCount() {
+ const newCount = this.calculateTotalNeeded();
+ if (newCount !== this.pokemon_family.catch_count) {
+ this.pokemon_family.catch_count = newCount;
+ if (newCount === 0) {
+ // Emit event to move to completed section
+ this.statusUpdate.emit({
+ pfic: this.pokemon_family.representative,
+ caught: true,
+ completed: true
+ });
+ } else if (newCount > 0 && this.pokemon_family.catch_count === 0) {
+ // Emit event to move back to active section
+ this.statusUpdate.emit({
+ pfic: this.pokemon_family.representative,
+ caught: false,
+ completed: false
+ });
+ }
+ }
+ }
+
+ getRepresentativePokemon() {
+ return this.pokemonService.getPokemonFromPFIC(this.pokemon_family.representative)
+ }
+
+ trackByPfic(index: number, item: any): string {
+ return item.PFIC; // Assuming PFIC or another unique identifier is available
+ }
+
+ onSelect() {
+ this.familySelected.emit(this.pokemon_family);
+ }
+}
\ No newline at end of file
diff --git a/src/app/features/plan/planV2.component.ts b/src/app/features/plan/planV2.component.ts
new file mode 100644
index 0000000..94df193
--- /dev/null
+++ b/src/app/features/plan/planV2.component.ts
@@ -0,0 +1,189 @@
+import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MatCardModule } from '@angular/material/card';
+import { FormsModule } from '@angular/forms';
+import { PlanGameComponent } from './plan-game/plan-game.component';
+import { PlanService } from '../../core/services/plan.service';
+import { GamePlan, PokemonFamilyEntry } from '../../core/models/plan.model';
+import { PlanPokemonV2Component } from "./plan-pokemon/plan-pokemonV2.component";
+import { ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
+import { PlanPokemonDetailsComponent } from "./plan-pokemon-details/plan-pokemon-details.component";
+
+@Component({
+ selector: 'app-planV2',
+ standalone: true,
+ imports: [
+ CommonModule,
+ MatCardModule,
+ FormsModule,
+ PlanGameComponent,
+ PlanPokemonV2Component,
+ ScrollingModule,
+ PlanPokemonDetailsComponent
+],
+ template: `
+
+
+
+
+
+
{{ selectedGame.game_name }} - Game Stats
+
+
+
+
+
+
+
+
{{ selectedPokemon?.representative }} - Details
+
+
+
+
+
+ `,
+ styles: [`
+ .plan-container {
+ display: flex;
+ height: calc(100vh - 64px); /* Adjust based on your header height */
+ overflow: hidden;
+ }
+
+ .games-section {
+ width: 11%;
+ padding: 20px;
+ overflow-y: auto;
+ background: #f5f5f5;
+ border-right: 1px solid #ddd;
+ }
+
+ .games-scroll {
+ width: 100%;
+ }
+
+ .games-list {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ }
+
+ .content-section {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding: 20px;
+ overflow: hidden;
+ }
+
+ .middle-section {
+ flex: 2;
+ display: flex;
+ flex-direction: row;
+ margin-right: 20px;
+ }
+
+ .game-stats {
+ padding-bottom: 16px;
+ border-bottom: 1px solid #ddd;
+ margin-bottom: 16px;
+ }
+
+ .pokemon-section {
+ flex: 1;
+ min-height: 0; /* Important for Firefox */
+ display: flex;
+ flex-direction: row;
+ margin-right: 15px;
+ }
+
+ .pokemon-viewport {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+ background: #f5f5f5;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ }
+
+ .details-section {
+ flex: 1;
+ padding: 16px;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ width:15%
+ }
+ `]
+})
+export class PlanV2Component implements OnInit {
+ @ViewChild(CdkVirtualScrollViewport) viewport!: CdkVirtualScrollViewport;
+
+ gamePlans: GamePlan[] = [];
+ selectedGame: GamePlan | null = null;
+ selectedPokemon: PokemonFamilyEntry | null = null;
+
+ constructor(private planService: PlanService, private cdr: ChangeDetectorRef) {}
+
+ ngOnInit() {
+ this.loadPlan();
+ }
+
+ private loadPlan() {
+ this.planService.getPlan().subscribe(
+ plan => {
+ this.gamePlans = plan;
+ if (!this.selectedGame && plan.length > 0) {
+ this.selectedGame = plan[0];
+ }
+ }
+ );
+ }
+
+ selectGame(game: GamePlan) {
+ if (this.viewport) {
+ this.viewport.scrollToIndex(0); // Reset scroll to top when switching games
+ }
+ this.selectedGame = null; // Clear the selected game first to avoid stale data
+ this.cdr.detectChanges();
+ this.selectedGame = game;
+ this.cdr.detectChanges();
+ }
+
+ onPokemonStatusUpdate(event: { pfic: string, caught: boolean }) {
+ this.loadPlan();
+ }
+
+ selectPokemon(pokemon: any) {
+ this.selectedPokemon = pokemon;
+ }
+
+ trackByPfic(index: number, item: any): string {
+ return item.key; // Assuming PFIC or another unique identifier is available
+ }
+}
+