StudentApp.vue 4.99 KB
<script setup lang="ts">
import figma from '@images/icons/project-icons/figma.png'
import html5 from '@images/icons/project-icons/html5.png'
import python from '@images/icons/project-icons/python.png'
import react from '@images/icons/project-icons/react.png'
import sketch from '@images/icons/project-icons/sketch.png'
import vue from '@images/icons/project-icons/vue.png'
import xamarin from '@images/icons/project-icons/xamarin.png'
import { computed, ref } from 'vue'

const applications = [
  { logo: react, name: 'Human Resource Information System Universitas Indonesia', nameShort: 'HRIS UI', link: 'https://hris.ui.ac.id/' },
  { logo: figma, name: 'Sistem Informasi Akademik NextGeneration', nameShort: 'SIAKNG', link: 'https://academic.ui.ac.id/' },
  { logo: figma, name: 'E-learning Management System', nameShort: 'EMAS-2', link: 'https://emas2.ui.ac.id/' },
  { logo: vue, name: 'Dashboard App', nameShort: 'Vuejs Project', link: '#' },
  { logo: xamarin, name: 'Foodista mobile app', nameShort: 'Xamarin Project', link: '#' },
  { logo: python, name: 'Dojo Email App', nameShort: 'Python Project', link: '#' },
  { logo: sketch, name: 'Blockchain App', nameShort: 'Sketch Project', link: '#' },
  { logo: html5, name: 'Hoffman App', nameShort: 'HTML Project', link: '#' },
]

const searchQuery = ref('')
const itemsPerPage = ref(8) // Default rows per page
const page = ref(1) // Default page number

// Filtered applications based on search query
const filteredApplications = computed(() => {
  const query = searchQuery.value.toLowerCase()

  return applications.filter(app =>
    [app.name, app.nameShort, app.link].some(field =>
      field.toLowerCase().includes(query),
    ),
  )
})

// Paginate the filtered applications
const paginatedApplications = computed(() => {
  const start = (page.value - 1) * itemsPerPage.value

  return filteredApplications.value.slice(start, start + itemsPerPage.value)
})
</script>

<template>
  <VCard
    title="List Aplikasi untuk Mahasiswa"
    class="project-list"
  >
    <div class="content-container">
      <!-- Search Input -->
      <div class="search-container">
        <VTextField
          v-model="searchQuery"
          label="Search"
          placeholder="Search ..."
          append-inner-icon="ri-search-line"
          clearable
          single-line
          hide-details
          dense
          outlined
          @click:clear="searchQuery = ''"
        />
      </div>

      <!-- Applications Grid -->
      <VRow class="app-grid">
        <VCol
          v-for="app in paginatedApplications"
          :key="app.name"
          cols="12"
          sm="6"
          md="3"
        >
          <VCard
            class="app-card"
            :href="app.link"
            target="_blank"
            rel="noopener"
          >
            <VImg
              :src="app.logo"
              class="app-image"
            />
            <VCardText class="app-text">
              <a
                :href="app.link"
                target="_blank"
                class="app-link"
              >{{ app.link }} ></a>
              <div class="app-short">
                {{ app.nameShort }}
              </div>
              <div class="app-name">
                {{ app.name }}
              </div>
            </VCardText>
          </VCard>
        </VCol>
      </VRow>

      <!-- Pagination Controls -->
      <div class="pt-2">
        <div class="d-flex flex-wrap justify-center justify-sm-space-between gap-y-2 mt-2">
          <VSelect
            v-model="itemsPerPage"
            :items="[4, 8, 16, 32]"
            label="Rows per page:"
            variant="underlined"
            style="max-inline-size: 8rem; min-inline-size: 5rem;"
          />

          <VPagination
            v-model="page"
            :total-visible="3"
            :length="Math.ceil(filteredApplications.length / itemsPerPage)"
          />
        </div>
      </div>
    </div>
  </VCard>
</template>

<style scoped>
.content-container {
  padding-block: 0 20px;
  padding-inline: 20px; /* No padding on top */
}

.project-list {
  padding: 0;
}

.search-container {
  display: flex;
  justify-content: flex-end;
  margin-block-end: 20px;
}

.app-card {
  display: flex;
  overflow: hidden;
  flex-direction: column;
  block-size: 100%;
  cursor: pointer;
  transition: transform 0.2s ease-in-out;
}

.app-card:hover {
  transform: scale(1.02);
}

.app-image {
  overflow: hidden;
  inline-size: 100%;
  max-block-size: 100px;
  object-fit: cover;
}

.app-text {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  justify-content: space-between;
  padding-block: 8px;
  padding-inline: 12px;
}

.app-link {
  display: block;
  color: rgb(var(--v-global-theme-primary));
  font-size: 14px;
  text-decoration: none;
}

.app-name {
  overflow: hidden;
  color: gray;
  font-size: 14px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-card:hover .app-name {
  overflow: visible;
  text-overflow: unset;
  white-space: normal;
}

.app-short {
  font-size: 16px;
  font-weight: bold;
  margin-block-start: 4px;
}
</style>