353 lines
10 KiB
Vue
353 lines
10 KiB
Vue
<template>
|
|
<pn-page-template @updateScrollPosition="onScroll">
|
|
<template #title>
|
|
<div class="q-pr-md ellipsis">
|
|
{{ $t('projects__title') }}
|
|
</div>
|
|
<q-btn
|
|
@click="goAccount"
|
|
flat rounded
|
|
no-caps
|
|
icon-right="mdi-chevron-right"
|
|
align="right"
|
|
dense
|
|
class="fix-btn ellipsis"
|
|
>
|
|
<pn-account-block-name/>
|
|
</q-btn>
|
|
</template>
|
|
<template #card-header>
|
|
<pn-action-bar
|
|
v-if="projects.length !== 0"
|
|
v-model="searchProject"
|
|
placeholder="projects__search"
|
|
:show-calendar-btn="false"
|
|
:show-filter-btn="false"
|
|
:shadows="isScrolled"
|
|
/>
|
|
</template>
|
|
|
|
<q-list v-if="projects.length !== 0">
|
|
<template
|
|
v-for="item in activeProjects"
|
|
:key="item.id"
|
|
>
|
|
<q-slide-item
|
|
@right="handleSlideRight($event, item.id)"
|
|
clickable
|
|
v-ripple
|
|
@click="goProject(item.id)"
|
|
right-color="red"
|
|
left-color="green"
|
|
>
|
|
<template #right>
|
|
<q-icon size="lg" name="mdi-briefcase-remove-outline"/>
|
|
</template>
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<pn-auto-avatar
|
|
:img="item.logo"
|
|
:name="item.name"
|
|
type="rounded"
|
|
size="lg"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1" class="text-bold">
|
|
{{ item.name }}
|
|
</q-item-label>
|
|
<q-item-label
|
|
caption lines="2"
|
|
style="max-width: -webkit-fill-available; white-space: pre-line"
|
|
>
|
|
{{ item.description }}
|
|
</q-item-label>
|
|
<q-item-label lines="1" caption>
|
|
<div class="flex items-center">
|
|
<div class="flex items-center">
|
|
<q-icon name="mdi-chat-outline"/>
|
|
<span>{{ item.chat_count }} </span>
|
|
</div>
|
|
<div class="flex items-center q-ml-sm">
|
|
<q-icon name="mdi-account-outline"/>
|
|
<span>{{ item.user_count }}</span>
|
|
</div>
|
|
</div>
|
|
</q-item-label>
|
|
</q-item-section>
|
|
<q-item-section side>
|
|
<q-item-label>
|
|
<q-btn
|
|
color="grey"
|
|
size="md"
|
|
dense flat round
|
|
icon="mdi-pencil"
|
|
class="q-ml-sm"
|
|
@click.stop="editProject(item.id)"
|
|
/>
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-slide-item>
|
|
</template>
|
|
</q-list>
|
|
|
|
<div
|
|
v-if="archiveProjects.length !== 0"
|
|
class="flex column items-center w100"
|
|
>
|
|
<q-btn
|
|
class="w100 rotate-icon-btn"
|
|
color="grey"
|
|
flat
|
|
no-caps
|
|
@click="showArchive = !showArchive"
|
|
icon-right="arrow_drop_down"
|
|
:class="{ 'rotate-icon': showArchive }"
|
|
>
|
|
<span class="text-caption">
|
|
{{ !showArchive
|
|
? $t('projects__show_archive') + ' (' + archiveProjects.length +')'
|
|
: $t('projects__hide_archive')
|
|
}}
|
|
</span>
|
|
</q-btn>
|
|
|
|
<div class="w100" style="overflow: hidden">
|
|
<transition
|
|
appear
|
|
enter-active-class="animated slideInDown"
|
|
leave-active-class="animated slideOutUp"
|
|
>
|
|
<q-list v-if="showArchive" class="w100">
|
|
<q-item
|
|
v-for = "item in archiveProjects"
|
|
:key="item.id"
|
|
clickable
|
|
v-ripple
|
|
@click="handleArchiveList(item.id)"
|
|
class="w100 text-grey"
|
|
>
|
|
<q-item-section avatar>
|
|
<pn-auto-avatar
|
|
:img="item.logo"
|
|
:name="item.name"
|
|
type="rounded"
|
|
size="lg"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1" class="text-bold">{{ item.name }}</q-item-label>
|
|
<q-item-label
|
|
caption lines="2"
|
|
style="max-width: -webkit-fill-available; white-space: pre-line"
|
|
>
|
|
{{ item.description }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</transition>
|
|
</div>
|
|
</div>
|
|
|
|
<pn-onboard-btn
|
|
v-if="projects.length===0 && projectsInit"
|
|
icon="mdi-briefcase-plus-outline"
|
|
message1="projects__lets_start"
|
|
message2="projects__lets_start_description"
|
|
@btn-click="createNewProject"
|
|
/>
|
|
<div
|
|
class="column justify-center items-center w100"
|
|
style="position: absolute; bottom: 0;"
|
|
v-if="!projectsInit"
|
|
>
|
|
<q-linear-progress indeterminate />
|
|
</div>
|
|
|
|
<template #card-fab>
|
|
<transition
|
|
appear
|
|
enter-active-class="animated zoomIn"
|
|
>
|
|
<pn-glass-btn
|
|
type="fab"
|
|
color="primary"
|
|
>
|
|
<q-btn
|
|
@click="createNewProject"
|
|
fab unelevated
|
|
class="shadow-t"
|
|
icon="add"
|
|
color="transparent"
|
|
/>
|
|
</pn-glass-btn>
|
|
</transition>
|
|
</template>
|
|
</pn-page-template>
|
|
|
|
<pn-small-dialog
|
|
v-model="showDialogArchiveProject"
|
|
icon="mdi-briefcase-remove-outline"
|
|
color="negative"
|
|
title="projects__dialog_archive_title"
|
|
message1="projects__dialog_archive_message"
|
|
message2="projects__dialog_archive_message2"
|
|
mainBtnLabel="projects__dialog_archive_ok"
|
|
@clickMainBtn="onConfirmArchiveProject()"
|
|
@close="onCancel()"
|
|
@before-hide="onDialogBeforeHide()"
|
|
/>
|
|
|
|
<pn-small-dialog
|
|
v-model="showDialogRestoreArchive"
|
|
icon="mdi-briefcase-upload-outline"
|
|
color="green"
|
|
title="projects__dialog_restore_title"
|
|
message1="projects__dialog_restore_message"
|
|
message2="projects__dialog_restore_message2"
|
|
mainBtnLabel="projects__dialog_restore_ok"
|
|
@clickMainBtn="restoreFromArchive()"
|
|
/>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, watch, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useProjectsStore } from 'stores/projects'
|
|
import pnGlassBtn from 'components/pnGlassBtn.vue'
|
|
import pnActionBar from 'components/pnActionBar.vue'
|
|
import type { Project } from 'types/Project'
|
|
|
|
const router = useRouter()
|
|
const projectsStore = useProjectsStore()
|
|
const projects = computed(() => projectsStore.projects)
|
|
const projectsInit = computed(() => projectsStore.isInit)
|
|
|
|
const searchProject = ref('')
|
|
const showArchive = ref(false)
|
|
const showDialogRestoreArchive = ref(false)
|
|
const restoreProjectId = ref<number | undefined> (undefined)
|
|
|
|
async function goProject (id: number) {
|
|
await router.push({ name: 'chats', params: { id }})
|
|
}
|
|
|
|
async function editProject (id: number) {
|
|
await router.push({ name: 'project_info', params: { id }})
|
|
}
|
|
|
|
async function goAccount () {
|
|
await router.push({ name: 'account' })
|
|
}
|
|
|
|
async function createNewProject () {
|
|
await router.push({ name: 'project_add' })
|
|
}
|
|
|
|
function handleArchiveList (id: number) {
|
|
showDialogRestoreArchive.value = true
|
|
restoreProjectId.value = id
|
|
}
|
|
|
|
async function restoreFromArchive () {
|
|
if (restoreProjectId.value) await projectsStore.restore(restoreProjectId.value)
|
|
}
|
|
|
|
const displayProjects = computed(() => {
|
|
if (!searchProject.value || !(searchProject.value && searchProject.value.trim())) return projects.value
|
|
const searchChatValue = searchProject.value.trim().toLowerCase()
|
|
const arrOut = projects.value
|
|
.filter((el: Project) =>
|
|
el.name.toLowerCase().includes(searchChatValue) ||
|
|
el.description && el.description.toLowerCase().includes(searchProject.value)
|
|
)
|
|
return arrOut
|
|
})
|
|
|
|
const activeProjects = computed(() => {
|
|
return displayProjects.value.filter((el: Project) => !el.is_archived)
|
|
})
|
|
|
|
const archiveProjects = computed(() => {
|
|
return displayProjects.value.filter((el: Project) => el.is_archived)
|
|
})
|
|
|
|
onMounted(async () => {
|
|
if (!projectsStore.isInit) await projectsStore.init()
|
|
})
|
|
|
|
watch(showDialogRestoreArchive, (newD :boolean) => {
|
|
if (!newD) restoreProjectId.value = undefined
|
|
})
|
|
|
|
interface SlideEvent {
|
|
reset: () => void
|
|
}
|
|
|
|
const currentSlideEvent = ref<SlideEvent | null>(null)
|
|
const showDialogArchiveProject = ref<boolean>(false)
|
|
const archiveProjectId = ref<number | undefined>(undefined)
|
|
const closedByUserAction = ref(false)
|
|
|
|
function handleSlideRight (event: SlideEvent, id: number) {
|
|
currentSlideEvent.value = event
|
|
showDialogArchiveProject.value = true
|
|
archiveProjectId.value = id
|
|
}
|
|
|
|
function onDialogBeforeHide () {
|
|
if (!closedByUserAction.value) {
|
|
onCancel()
|
|
}
|
|
closedByUserAction.value = false
|
|
}
|
|
|
|
async function onConfirmArchiveProject () {
|
|
closedByUserAction.value = true
|
|
if (archiveProjectId.value) {
|
|
await projectsStore.archive(archiveProjectId.value)
|
|
}
|
|
currentSlideEvent.value = null
|
|
}
|
|
|
|
function onCancel() {
|
|
closedByUserAction.value = true
|
|
if (currentSlideEvent.value) {
|
|
currentSlideEvent.value.reset()
|
|
currentSlideEvent.value = null
|
|
}
|
|
}
|
|
|
|
const isScrolled = ref(false)
|
|
const onScroll = (pos: number) => {
|
|
isScrolled.value = pos > 1
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
:deep(.q-slide-item__right) {
|
|
align-self: center;
|
|
height: 98%;
|
|
}
|
|
|
|
.fix-btn :deep(.q-btn__content) {
|
|
flex-wrap: nowrap;
|
|
}
|
|
|
|
.rotate-icon-btn {
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.rotate-icon-btn.rotate-icon :deep(.q-icon) {
|
|
transform: rotate(180deg);
|
|
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
|
|
}
|
|
|
|
.rotate-icon-btn :deep(.q-icon) {
|
|
transform: rotate(0deg);
|
|
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
|
|
}
|
|
</style>
|