Hello from MCP server

List Files | Just Commands | Repo | Logs

← back |
<template>
  <BaseLayout title="Problem Selection">
    <ion-grid :fixed="true">
      <ion-row>
        <ion-col>
          <p>
            <ion-button color="tertiary" fill="outline" @click="goToSession"
              >Session Home</ion-button
            >
          </p>
        </ion-col>
      </ion-row>

      <!-- Search and Filter Section -->
      <ion-row>
        <ion-col>
          <ion-card>
            <ion-card-header>
              <ion-card-title>Search & Filter Problems</ion-card-title>
            </ion-card-header>
            <ion-card-content>
              <ion-searchbar
                v-model="searchQuery"
                placeholder="Search problems by title, description, or tags..."
                @ionInput="onSearchInput"
              ></ion-searchbar>
            </ion-card-content>
          </ion-card>
        </ion-col>
      </ion-row>

      <!-- Tags Section - Only shows tags from filtered problems -->
      <ion-row>
        <ion-col>
          <ion-card>
            <ion-card-header>
              <ion-card-title>Filter by Tags</ion-card-title>
              <ion-card-subtitle>
                {{ availableTags.length }} tag{{ availableTags.length === 1 ? '' : 's' }} available
              </ion-card-subtitle>
            </ion-card-header>
            <ion-card-content>
              <div class="tags-container">
                <ion-chip
                  v-for="tag in availableTags"
                  :key="tag.id"
                  :color="isTagSelected(tag.id) ? 'primary' : 'medium'"
                  @click="toggleTag(tag.id)"
                  class="tag-chip"
                >
                  <ion-label>
                    {{ tag.name }} ({{ getTagUsageCount(tag.id) }})
                  </ion-label>
                </ion-chip>
                <span v-if="availableTags.length === 0" class="no-tags">
                  No tags match your search
                </span>
              </div>
              <div v-if="selectedTags.length > 0" class="selected-tags-info">
                <ion-text color="primary">
                  <small>Selected: {{ selectedTags.length }} tag{{ selectedTags.length === 1 ? '' : 's' }}</small>
                </ion-text>
                <ion-button 
                  size="small" 
                  fill="clear"
                  @click="clearTags"
                >
                  Clear Tags
                </ion-button>
              </div>
            </ion-card-content>
          </ion-card>
        </ion-col>
      </ion-row>

      <!-- Results Summary -->
      <ion-row>
        <ion-col>
          <div class="results-summary">
            <h3>
              {{ filteredProblems.length }} Problem{{ filteredProblems.length === 1 ? '' : 's' }} Found
            </h3>
            <ion-button
              v-if="hasActiveFilters"
              fill="clear"
              @click="clearAllFilters"
            >
              Clear All Filters
            </ion-button>
          </div>
        </ion-col>
      </ion-row>

      <div
        style="border-top: 1px solid var(--ion-color-medium); margin: 16px 0"
      ></div>

      <!-- Problems List -->
      <ion-row>
        <ion-col>
          <ion-card v-for="problem in filteredProblems" :key="problem.id">
            <ion-card-header>
              <ion-card-title>{{ problem.name }}</ion-card-title>
            </ion-card-header>
            <ion-card-content>
              <ion-grid>
                <ion-row>
                  <ion-col size="10">
                    <p class="problem-description">
                      {{ problem.description || "No description available" }}
                    </p>
                    <div class="problem-tags">
                      <span v-if="getTagsForProblem(problem).length > 0">
                        <ion-chip
                          v-for="tagName in getTagsForProblem(problem)"
                          :key="tagName"
                          size="small"
                          :color="isTagNameSelected(tagName) ? 'primary' : 'light'"
                          @click="toggleTagByName(tagName)"
                          class="problem-tag"
                        >
                          <ion-label>#{{ tagName }}</ion-label>
                        </ion-chip>
                      </span>
                      <span v-else class="no-tags">No tags</span>
                    </div>
                  </ion-col>
                  <ion-col size="2">
                    <div class="button-group">
                      <ion-button :data-id="problem.id" @click="viewProblem"
                        >View</ion-button
                      >
                    </div>
                  </ion-col>
                </ion-row>
              </ion-grid>
            </ion-card-content>
          </ion-card>

          <!-- No Results Message -->
          <ion-card v-if="filteredProblems.length === 0">
            <ion-card-content>
              <div class="no-results">
                <ion-icon :icon="searchOutline" size="large" color="medium"></ion-icon>
                <h3>No problems found</h3>
                <p>Try adjusting your search or filters</p>
                <ion-button
                  v-if="hasActiveFilters"
                  fill="outline"
                  @click="clearAllFilters"
                >
                  Clear All Filters
                </ion-button>
              </div>
            </ion-card-content>
          </ion-card>
        </ion-col>
      </ion-row>
    </ion-grid>
  </BaseLayout>
</template>

<script setup lang="ts">
import { onMounted, ref, computed } from "vue";
import { useRouter } from "vue-router";
import BaseLayout from "@/components/BaseLayout.vue";
import type { ProblemsRecord, ProblemTagsRecord } from "@/pocketbase-types.ts";
import { getDb } from "@/dataAccess/getDb";
import { getApi } from "@/dataAccess/getApi";
import { useSessionStore } from "@/stores/session";
import {
  IonButton,
  IonGrid,
  IonRow,
  IonCol,
  IonCard,
  IonCardHeader,
  IonCardTitle,
  IonCardSubtitle,
  IonCardContent,
  IonSearchbar,
  IonChip,
  IonLabel,
  IonText,
  IonIcon,
} from "@ionic/vue";
import { searchOutline } from "ionicons/icons";

const router = useRouter();
const sessionStore = useSessionStore();
const problems = ref<ProblemsRecord[]>([]);
const problemTags = ref<ProblemTagsRecord[]>([]);
const searchQuery = ref("");
const selectedTags = ref<string[]>([]);

// Helper function to get tag IDs from a problem
const getProblemTagIds = (problem: ProblemsRecord): string[] => {
  if (!problem.problemTags) return [];
  
  if (typeof problem.problemTags === 'string') {
    try {
      return JSON.parse(problem.problemTags);
    } catch (e) {
      return [];
    }
  } else if (Array.isArray(problem.problemTags)) {
    return problem.problemTags;
  }
  
  return [];
};

// Helper function to get tag names for a problem
const getTagsForProblem = (problem: ProblemsRecord): string[] => {
  const tagIds = getProblemTagIds(problem);
  
  return tagIds
    .map((tagId) => problemTags.value.find((tag) => tag.id === tagId))
    .filter((tag) => tag !== undefined)
    .map((tag) => tag!.name!)
    .filter((name) => name !== undefined);
};

// Computed: Problems filtered by search and selected tags
const filteredProblems = computed(() => {
  let filtered = [...problems.value];
  
  // Filter by search query
  if (searchQuery.value.trim() !== "") {
    const query = searchQuery.value.toLowerCase();
    filtered = filtered.filter(problem => {
      // Search in title
      if (problem.name?.toLowerCase().includes(query)) return true;
      
      // Search in description
      if (problem.description?.toLowerCase().includes(query)) return true;
      
      // Search in tags
      const tagNames = getTagsForProblem(problem);
      if (tagNames.some(tagName => tagName.toLowerCase().includes(query))) return true;
      
      return false;
    });
  }
  
  // Filter by selected tags (AND logic - problem must have ALL selected tags)
  if (selectedTags.value.length > 0) {
    filtered = filtered.filter(problem => {
      const problemTagIds = getProblemTagIds(problem);
      return selectedTags.value.every(tagId => problemTagIds.includes(tagId));
    });
  }
  
  return filtered;
});

// Computed: Available tags based on filtered problems (CORRECT CROSS-FILTERING)
const availableTags = computed(() => {
  // Collect all unique tag IDs from the currently filtered problems
  const tagIdsInFilteredProblems = new Set<string>();
  
  filteredProblems.value.forEach(problem => {
    const tagIds = getProblemTagIds(problem);
    tagIds.forEach(tagId => tagIdsInFilteredProblems.add(tagId));
  });
  
  // Return only tags that exist in the filtered problems
  return problemTags.value.filter(tag => tagIdsInFilteredProblems.has(tag.id));
});

// Computed: Check if filters are active
const hasActiveFilters = computed(() => {
  return searchQuery.value.trim() !== "" || selectedTags.value.length > 0;
});

// Get count of problems using a specific tag (within filtered problems)
const getTagUsageCount = (tagId: string): number => {
  return filteredProblems.value.filter(problem => {
    const tagIds = getProblemTagIds(problem);
    return tagIds.includes(tagId);
  }).length;
};

// Check if a tag is selected
const isTagSelected = (tagId: string): boolean => {
  return selectedTags.value.includes(tagId);
};

// Check if a tag name is selected
const isTagNameSelected = (tagName: string): boolean => {
  const tag = problemTags.value.find(t => t.name === tagName);
  return tag ? isTagSelected(tag.id) : false;
};

// Toggle tag selection
const toggleTag = (tagId: string) => {
  const index = selectedTags.value.indexOf(tagId);
  if (index > -1) {
    selectedTags.value.splice(index, 1);
  } else {
    selectedTags.value.push(tagId);
  }
};

// Toggle tag by name
const toggleTagByName = (tagName: string) => {
  const tag = problemTags.value.find(t => t.name === tagName);
  if (tag) {
    toggleTag(tag.id);
  }
};

// Clear all selected tags
const clearTags = () => {
  selectedTags.value = [];
};

// Clear all filters
const clearAllFilters = () => {
  searchQuery.value = "";
  selectedTags.value = [];
};

// Handle search input
const onSearchInput = (event: CustomEvent) => {
  searchQuery.value = event.detail.value || "";
};

// Navigation functions
const viewProblem = async (e: MouseEvent) => {
  const target = e?.target as HTMLElement;
  if (target) {
    const problemId = target.dataset.id;

    // Get the problem data
    const db = await getDb();
    if (db) {
      const problem = await db.problems.byId(problemId);

      if (problem) {
        console.log('[ProblemSelect] Navigating to confirm-job for problem:', problem.name);

        // Navigate to confirm-job with problem ID (don't create job yet)
        router.push(`/confirm-job?problemId=${problem.id}`);
      }
    }
  }
};

const goToSession = () => {
  router.push("/service-call");
};

// Load data on mount
onMounted(async () => {
  const db = await getDb();
  if (db) {
    problems.value = (await db.selectAll("problems")) || [];
    problemTags.value = (await db.selectAll("problemTags")) || [];
  }
});
</script>

<style scoped>
.button-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.tags-container {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 12px;
}

.tag-chip {
  cursor: pointer;
  transition: all 0.2s ease;
}

.tag-chip:hover {
  transform: scale(1.05);
}

.problem-tag {
  cursor: pointer;
  margin-right: 4px;
  margin-bottom: 4px;
}

.problem-description {
  color: var(--ion-color-dark);
  margin-bottom: 12px;
  line-height: 1.4;
}

.problem-tags {
  margin-top: 8px;
}

.no-tags {
  color: var(--ion-color-medium);
  font-style: italic;
  font-size: 0.9em;
}

.selected-tags-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--ion-color-light);
}

.results-summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 16px;
}

.results-summary h3 {
  margin: 0;
  color: var(--ion-color-dark);
}

.no-results {
  text-align: center;
  padding: 40px 20px;
}

.no-results h3 {
  color: var(--ion-color-medium);
  margin-top: 16px;
  margin-bottom: 8px;
}

.no-results p {
  color: var(--ion-color-medium);
  margin-bottom: 24px;
}
</style>