Commit 07e41be4 by Nabiilah Putri Safa

edit app view

1 parent f645776c
...@@ -13,6 +13,17 @@ const applicationTableHeaders = [ ...@@ -13,6 +13,17 @@ const applicationTableHeaders = [
{ title: 'Link', key: 'link' }, { title: 'Link', key: 'link' },
] ]
const viewMode = ref('grid'); // Set default to 'grid'
const itemsPerPage = ref(8) // Default rows per page
const page = ref(1) // Default page number
const paginatedApplications = computed(() => {
const start = (page.value - 1) * itemsPerPage.value
return filteredApplications.value.slice(start, start + itemsPerPage.value)
})
const applications = [ const applications = [
{ {
logo: react, logo: react,
...@@ -79,8 +90,29 @@ const filteredApplications = computed(() => { ...@@ -79,8 +90,29 @@ const filteredApplications = computed(() => {
</script> </script>
<template> <template>
<VCard title="List Aplikasi untuk Dosen" class="projectList"> <VCard class="projectList">
<!-- 👉 User Project List Table --> <!-- Custom Header -->
<div class="d-flex justify-space-between align-center pa-4">
<div class="text-h5">List Aplikasi untuk Dosen</div>
<div class="d-flex gap-2">
<VBtn
icon
variant="text"
:color="viewMode === 'grid' ? 'primary' : 'default'"
@click="viewMode = 'grid'"
>
<VIcon icon="ri-layout-grid-line" />
</VBtn>
<VBtn
icon
variant="text"
:color="viewMode === 'horizontal' ? 'primary' : 'default'"
@click="viewMode = 'horizontal'"
>
<VIcon icon="ri-layout-horizontal-line" />
</VBtn>
</div>
</div>
<!-- Search Input --> <!-- Search Input -->
<div class="search-container mb-4 pl-2 pr-2"> <div class="search-container mb-4 pl-2 pr-2">
...@@ -89,10 +121,49 @@ const filteredApplications = computed(() => { ...@@ -89,10 +121,49 @@ const filteredApplications = computed(() => {
clearable @click:clear="searchQuery = ''" single-line hide-details dense outlined /> clearable @click:clear="searchQuery = ''" single-line hide-details dense outlined />
</div> </div>
<!-- Search Input -->
<!-- Grid View -->
<VRow v-if="viewMode === 'grid'" 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>
<!-- SECTION Datatable --> <!-- SECTION Datatable -->
<VDataTable :headers="applicationTableHeaders" :items="filteredApplications" hide-default-footer fixed-header <VDataTable v-if="viewMode === 'horizontal'" :headers="applicationTableHeaders" :items="paginatedApplications" hide-default-footer fixed-header
item-value="name"> item-value="name">
<!-- application --> <!-- application -->
<template #item.application="{ item }"> <template #item.application="{ item }">
<div class="d-flex align-center gap-x-3"> <div class="d-flex align-center gap-x-3">
...@@ -121,6 +192,26 @@ const filteredApplications = computed(() => { ...@@ -121,6 +192,26 @@ const filteredApplications = computed(() => {
<template #bottom /> <template #bottom />
</VDataTable> </VDataTable>
<!-- !SECTION --> <!-- !SECTION -->
<div class="pagination-container pt-4 px-4 mb-7">
<div class="d-flex flex-wrap align-center justify-space-between gap-4">
<VSelect
v-model="itemsPerPage"
:items="[4, 8, 16, 32]"
label="Rows per page:"
variant="underlined"
density="comfortable"
style="max-width: 10rem;"
/>
<VPagination
v-model="page"
:total-visible="5"
:length="Math.ceil(filteredApplications.length / itemsPerPage)"
/>
</div>
</div>
</VCard> </VCard>
</template> </template>
...@@ -147,4 +238,76 @@ const filteredApplications = computed(() => { ...@@ -147,4 +238,76 @@ const filteredApplications = computed(() => {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
} }
.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> </style>
...@@ -6,66 +6,127 @@ import react from '@images/icons/project-icons/react.png' ...@@ -6,66 +6,127 @@ import react from '@images/icons/project-icons/react.png'
import sketch from '@images/icons/project-icons/sketch.png' import sketch from '@images/icons/project-icons/sketch.png'
import vue from '@images/icons/project-icons/vue.png' import vue from '@images/icons/project-icons/vue.png'
import xamarin from '@images/icons/project-icons/xamarin.png' import xamarin from '@images/icons/project-icons/xamarin.png'
import { computed, ref } from 'vue'
const applications = [ // Project Table Header
{ logo: react, name: 'Human Resource Information System Universitas Indonesia', nameShort: 'HRIS UI', link: 'https://hris.ui.ac.id/' }, const applicationTableHeaders = [
{ logo: figma, name: 'Sistem Informasi Akademik NextGeneration', nameShort: 'SIAKNG', link: 'https://academic.ui.ac.id/' }, { title: 'UI App', key: 'application' },
{ logo: figma, name: 'E-learning Management System', nameShort: 'EMAS-2', link: 'https://emas2.ui.ac.id/' }, { title: 'Link', key: 'link' },
{ 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 viewMode = ref('grid'); // Set default to 'grid'
const itemsPerPage = ref(8) // Default rows per page const itemsPerPage = ref(8) // Default rows per page
const page = ref(1) // Default page number 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 paginatedApplications = computed(() => {
const start = (page.value - 1) * itemsPerPage.value const start = (page.value - 1) * itemsPerPage.value
return filteredApplications.value.slice(start, start + itemsPerPage.value) return filteredApplications.value.slice(start, start + itemsPerPage.value)
}) })
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: '#',
},
]
// Search query state
const searchQuery = ref('');
// Filter aplikasi berdasarkan pencarian
const filteredApplications = computed(() => {
const query = searchQuery.value.toLowerCase();
return applications.filter((app) =>
[app.name, app.nameShort, app.link].some((field) =>
field.toLowerCase().includes(query)
)
);
});
</script> </script>
<template> <template>
<VCard <VCard class="projectList">
title="List Aplikasi untuk Mahasiswa" <!-- Custom Header -->
class="project-list" <div class="d-flex justify-space-between align-center pa-4">
<div class="text-h5">List Aplikasi untuk Mahasiswa</div>
<div class="d-flex gap-2">
<VBtn
icon
variant="text"
:color="viewMode === 'grid' ? 'primary' : 'default'"
@click="viewMode = 'grid'"
>
<VIcon icon="ri-layout-grid-line" />
</VBtn>
<VBtn
icon
variant="text"
:color="viewMode === 'horizontal' ? 'primary' : 'default'"
@click="viewMode = 'horizontal'"
> >
<div class="content-container"> <VIcon icon="ri-layout-horizontal-line" />
</VBtn>
</div>
</div>
<!-- Search Input --> <!-- Search Input -->
<div class="search-container"> <div class="search-container mb-4 pl-2 pr-2">
<VTextField <!-- <VTextField v-model="searchQuery" label="Search" clearable variant="outlined" density="comfortable" /> -->
v-model="searchQuery" <VTextField v-model="searchQuery" label="Search" placeholder="Search ..." append-inner-icon="ri-search-line"
label="Search" clearable @click:clear="searchQuery = ''" single-line hide-details dense outlined />
placeholder="Search ..."
append-inner-icon="ri-search-line"
clearable
single-line
hide-details
dense
outlined
@click:clear="searchQuery = ''"
/>
</div> </div>
<!-- Applications Grid -->
<VRow class="app-grid"> <!-- Search Input -->
<!-- Grid View -->
<VRow v-if="viewMode === 'grid'" class="app-grid">
<VCol <VCol
v-for="app in paginatedApplications" v-for="app in paginatedApplications"
:key="app.name" :key="app.name"
...@@ -100,29 +161,85 @@ const paginatedApplications = computed(() => { ...@@ -100,29 +161,85 @@ const paginatedApplications = computed(() => {
</VCol> </VCol>
</VRow> </VRow>
<!-- Pagination Controls --> <!-- SECTION Datatable -->
<div class="pt-2"> <VDataTable v-if="viewMode === 'horizontal'" :headers="applicationTableHeaders" :items="paginatedApplications" hide-default-footer fixed-header
<div class="d-flex flex-wrap justify-center justify-sm-space-between gap-y-2 mt-2"> item-value="name">
<!-- application -->
<template #item.application="{ item }">
<div class="d-flex align-center gap-x-3">
<VAvatar :size="34" :image="item.logo" />
<div>
<h6 class="text-h6 text-no-wrap">
{{ item.name }}
</h6>
<div class="text-body-2">
{{ item.nameShort }}
</div>
</div>
</div>
</template>
<!-- link -->
<template #item.link="{ item }">
<div class="d-flex align-center gap-3">
<div class="text-high-emphasis">
<a :href="item.link" target="_blank" rel="noopener">{{ item.link }}</a>
</div>
</div>
</template>
<!-- TODO Refactor this after vuetify provides proper solution for removing default footer -->
<template #bottom />
</VDataTable>
<!-- !SECTION -->
<div class="pagination-container pt-4 px-4 mb-7">
<div class="d-flex flex-wrap align-center justify-space-between gap-4">
<VSelect <VSelect
v-model="itemsPerPage" v-model="itemsPerPage"
:items="[4, 8, 16, 32]" :items="[4, 8, 16, 32]"
label="Rows per page:" label="Rows per page:"
variant="underlined" variant="underlined"
style="max-inline-size: 8rem; min-inline-size: 5rem;" density="comfortable"
style="max-width: 10rem;"
/> />
<VPagination <VPagination
v-model="page" v-model="page"
:total-visible="3" :total-visible="5"
:length="Math.ceil(filteredApplications.length / itemsPerPage)" :length="Math.ceil(filteredApplications.length / itemsPerPage)"
/> />
</div> </div>
</div> </div>
</div>
</VCard> </VCard>
</template> </template>
<style scoped> <style lang="scss">
.projectList {
.v-table {
&--density-default {
.v-table__wrapper {
table {
tbody {
tr {
td {
block-size: 56px;
}
}
}
}
}
}
}
}
.search-container {
display: flex;
justify-content: flex-end;
}
.content-container { .content-container {
padding-block: 0 20px; padding-block: 0 20px;
padding-inline: 20px; /* No padding on top */ padding-inline: 20px; /* No padding on top */
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!