Hello from MCP server

List Files | Just Commands | Repo | Logs

← back |
<template>
  <BaseLayout title="Service Call Review">
    <!-- Toolbar at top of screen -->
    <Toolbar @help-clicked="openInfoModal" />

    <div class="review-container">
      <h2 class="review-title">Service Call Review</h2>

      <!-- Loading State -->
      <div v-if="isLoading" class="loading-section">
        <ion-spinner name="crescent"></ion-spinner>
        <p class="loading-text">Loading service calls...</p>
      </div>

      <!-- Service Calls List -->
      <div v-else-if="serviceCalls.length > 0" class="section">
        <p class="section-message">{{ serviceCalls.length }} service call{{ serviceCalls.length !== 1 ? 's' : '' }} found</p>
        <div class="calls-list">
          <div
            v-for="call in serviceCalls"
            :key="call.id"
            class="call-item"
            @click="navigateToDetails(call.id)"
          >
            <div class="call-header">
              <ion-icon :icon="documentTextOutline" class="call-icon"></ion-icon>
              <div class="call-info">
                <div class="call-date">{{ formatDate(call.created) }}</div>
                <div class="call-meta">
                  <span class="call-tech">{{ call.expand?.user?.name || 'Unknown Tech' }}</span>
                  <span v-if="callDetails(call).jobCount > 0" class="call-jobs">
                    {{ callDetails(call).jobCount }} job{{ callDetails(call).jobCount !== 1 ? 's' : '' }}
                  </span>
                </div>
              </div>
              <div class="call-amount">
                <show-currency v-if="callDetails(call).totalAmount > 0" :currencyIn="callDetails(call).totalAmount" />
                <span v-else class="no-amount">—</span>
              </div>
            </div>
            <ion-icon :icon="chevronForwardOutline" class="arrow-icon"></ion-icon>
          </div>
        </div>
      </div>

      <!-- Empty State -->
      <div v-else class="empty-section">
        <ion-icon :icon="documentTextOutline" class="empty-icon"></ion-icon>
        <p class="empty-message">No service calls found</p>
        <p class="empty-submessage">Service calls will appear here after they are saved</p>
      </div>
    </div>

    <!-- Info Modal -->
    <ion-modal :is-open="isInfoModalOpen" @didDismiss="closeInfoModal">
      <ion-header>
        <ion-toolbar>
          <ion-title>Service Calls Data</ion-title>
          <ion-buttons slot="end">
            <ion-button @click="closeInfoModal">Close</ion-button>
          </ion-buttons>
        </ion-toolbar>
      </ion-header>
      <ion-content class="ion-padding">
        <div class="info-content">
          <pre>{{ JSON.stringify(serviceCalls, null, 2) }}</pre>
        </div>
      </ion-content>
    </ion-modal>
  </BaseLayout>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import {
  IonIcon,
  IonSpinner,
  IonModal,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonButtons,
  IonButton,
  IonContent,
} from '@ionic/vue';
import { documentTextOutline, chevronForwardOutline } from 'ionicons/icons';
import BaseLayout from '@/components/BaseLayout.vue';
import Toolbar from '@/components/Toolbar.vue';
import ShowCurrency from '@/components/ShowCurrency.vue';
import { getApi } from '@/dataAccess/getApi';

const router = useRouter();
const serviceCalls = ref<any[]>([]);
const isLoading = ref(true);
const isInfoModalOpen = ref(false);

const openInfoModal = () => {
  isInfoModalOpen.value = true;
};

const closeInfoModal = () => {
  isInfoModalOpen.value = false;
};

const formatDate = (dateString: string) => {
  const date = new Date(dateString);
  return date.toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit'
  });
};

const callDetails = (call: any) => {
  let details = { jobCount: 0, totalAmount: 0 };

  try {
    if (call.details) {
      const parsed = typeof call.details === 'string' ? JSON.parse(call.details) : call.details;
      details.jobCount = parsed.jobs?.length || 0;
      details.totalAmount = parsed.totalAmount || 0;
    }
  } catch (error) {
    console.error('[ServiceCallReview] Error parsing call details:', error);
  }

  return details;
};

const navigateToDetails = (id: string) => {
  router.push(`/service-call-review/${id}`);
};

const loadServiceCalls = async () => {
  isLoading.value = true;
  try {
    const { pb } = await getApi();

    // Get service calls for the current user's organization
    const records = await pb.collection('serviceCalls').getFullList({
      sort: '-created',
      expand: 'user',
      filter: `org = "${pb.authStore.record?.activeOrg}"`,
    });

    serviceCalls.value = records;
    console.log('[ServiceCallReview] Loaded service calls:', records.length);
  } catch (error) {
    console.error('[ServiceCallReview] Failed to load service calls:', error);
  } finally {
    isLoading.value = false;
  }
};

onMounted(async () => {
  await loadServiceCalls();
});
</script>

<style scoped>
:deep(.ion-page) {
  animation: none !important;
  transform: none !important;
  transition: none !important;
}

.review-container {
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
  animation: none !important;
  transform: none !important;
  transition: none !important;
}

.review-title {
  margin: 0 0 32px 0;
  color: var(--ion-text-color);
  font-size: 32px;
  font-weight: 600;
  text-align: center;
  font-variant: small-caps;
}

.loading-section {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  gap: 16px;
}

.loading-text {
  color: var(--ion-color-medium);
  font-size: 16px;
  font-style: italic;
}

.section {
  margin-bottom: 40px;
}

.section-message {
  text-align: center;
  color: var(--ion-color-medium);
  font-size: 16px;
  font-style: italic;
  margin: 0 0 20px 0;
}

.calls-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.call-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px;
  background-color: var(--ion-background-color-step-50);
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  cursor: pointer;
  transition: background 0.2s;
}

.call-item:hover {
  background: rgba(118, 221, 84, 0.1);
}

.call-header {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: 1;
}

.call-icon {
  font-size: 32px;
  color: var(--ion-color-primary);
  flex-shrink: 0;
}

.call-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.call-date {
  color: var(--ion-text-color);
  font-size: 18px;
  font-weight: 600;
}

.call-meta {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  align-items: center;
}

.call-tech {
  color: var(--ion-color-medium);
  font-size: 14px;
}

.call-jobs {
  color: var(--ion-color-primary);
  font-size: 14px;
  font-weight: 600;
}

.call-amount {
  font-size: 24px;
  font-weight: 700;
  color: var(--ion-color-success);
  margin-right: 8px;
  flex-shrink: 0;
}

.no-amount {
  color: var(--ion-color-medium);
}

.arrow-icon {
  font-size: 24px;
  color: var(--ion-color-medium);
  flex-shrink: 0;
}

.empty-section {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  text-align: center;
}

.empty-icon {
  font-size: 80px;
  color: var(--ion-color-medium);
  opacity: 0.5;
  margin-bottom: 24px;
}

.empty-message {
  color: var(--ion-text-color);
  font-size: 20px;
  font-weight: 600;
  margin: 0 0 8px 0;
}

.empty-submessage {
  color: var(--ion-color-medium);
  font-size: 16px;
  font-style: italic;
  margin: 0;
}

/* Info Modal Styles */
.info-content {
  color: var(--ion-color-dark);
}

.info-content pre {
  background: var(--ion-color-light);
  padding: 16px;
  border-radius: 8px;
  overflow-x: auto;
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-wrap: break-word;
}

/* Mobile adjustments */
@media (max-width: 767px) {
  .review-container {
    padding: 16px;
  }

  .review-title {
    font-size: 28px;
  }

  .call-date {
    font-size: 16px;
  }

  .call-amount {
    font-size: 20px;
  }
}
</style>