1 parent
2d4b8ea9cf
commit
6af9993c4f
3 files changed
+72
-53
No files matched your search
@@ -3,19 +3,18 @@ import { computed } from 'vue'
|
|||||||
import AssigneeFilter from '@/components/AssigneeFilter.vue'
|
import AssigneeFilter from '@/components/AssigneeFilter.vue'
|
||||||
import TextFilter from '@/components/TextFilter.vue'
|
import TextFilter from '@/components/TextFilter.vue'
|
||||||
import StatusFilter from '@/components/StatusFilter.vue'
|
import StatusFilter from '@/components/StatusFilter.vue'
|
||||||
import ProjectMenu from '@/components/ProjectMenu.vue'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
text?: string | null
|
text?: string | null
|
||||||
assigneeId?: string | null
|
assigneeId?: string | null
|
||||||
status?: string | null
|
status?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), { text: null, assigneeId: null })
|
const props = withDefaults(defineProps<Props>(), { text: null, assigneeId: null, status: () => [] })
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:text': [value: string | null]
|
'update:text': [value: string | null]
|
||||||
'update:assigneeId': [value: string | null]
|
'update:assigneeId': [value: string | null]
|
||||||
'update:status': [value: string | null]
|
'update:status': [value: string[]]
|
||||||
'update:clear': []
|
'update:clear': []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -25,14 +24,13 @@ function onAssigneeChanged(value: string[]) {
|
|||||||
emit('update:assigneeId', value.at(-1) ?? null)
|
emit('update:assigneeId', value.at(-1) ?? null)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onStatusChanged(alias: string) {
|
function onStatusChanged(value: string[]) {
|
||||||
emit('update:status', alias)
|
emit('update:status', value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="filter-grid ga-4 align-center">
|
<div class="filter-grid ga-4 align-center">
|
||||||
<project-menu />
|
|
||||||
<text-filter :model-value="text" @update:model-value="emit('update:text', $event)" />
|
<text-filter :model-value="text" @update:model-value="emit('update:text', $event)" />
|
||||||
<status-filter :status="status" @changed="onStatusChanged" />
|
<status-filter :status="status" @changed="onStatusChanged" />
|
||||||
<assignee-filter :selected="selectedAssignees" @changed="onAssigneeChanged" />
|
<assignee-filter :selected="selectedAssignees" @changed="onAssigneeChanged" />
|
||||||
@@ -43,6 +41,6 @@ function onStatusChanged(alias: string) {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.filter-grid {
|
.filter-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto auto auto;
|
grid-template-columns: 1fr auto auto auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -3,19 +3,25 @@ import { useStatusStore } from '@/stores/status.ts'
|
|||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
status?: string | null
|
status?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const { status } = defineProps<Props>()
|
const { status = [] } = defineProps<Props>()
|
||||||
const emit = defineEmits<{ changed: [value: string] }>()
|
const emit = defineEmits<{ changed: [value: string[]] }>()
|
||||||
const statusStore = useStatusStore()
|
const statusStore = useStatusStore()
|
||||||
const selected = ref(status)
|
const selected = ref<string[]>([...status])
|
||||||
const onUpdate = (val: unknown) => emit('changed', val as string)
|
|
||||||
|
// Keep store order so the same filter always yields the same URL
|
||||||
|
const onUpdate = (val: unknown) => {
|
||||||
|
if (!Array.isArray(val)) return
|
||||||
|
const order = statusStore.statuses?.map((el) => el.alias) ?? []
|
||||||
|
emit('changed', [...val].sort((a, b) => order.indexOf(a) - order.indexOf(b)))
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => status,
|
() => status,
|
||||||
(val) => {
|
(val) => {
|
||||||
selected.value = val
|
selected.value = [...val]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
@@ -24,6 +30,7 @@ watch(
|
|||||||
<v-btn-toggle
|
<v-btn-toggle
|
||||||
v-model="selected"
|
v-model="selected"
|
||||||
divided
|
divided
|
||||||
|
multiple
|
||||||
border
|
border
|
||||||
size="default"
|
size="default"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, useTemplateRef, watch } from 'vue'
|
import { computed, ref, useTemplateRef, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useIssueStore } from '@/stores/issue'
|
import { useIssueStore } from '@/stores/issue'
|
||||||
import { useProjectStore } from '@/stores/project.ts'
|
import { useProjectStore } from '@/stores/project.ts'
|
||||||
@@ -30,7 +30,7 @@ const serverItems = ref<Issue[]>([])
|
|||||||
|
|
||||||
const text = ref<string | null>(null)
|
const text = ref<string | null>(null)
|
||||||
const assigneeId = ref<string | null>(null)
|
const assigneeId = ref<string | null>(null)
|
||||||
const status = ref<string | null>(null)
|
const status = ref<string[]>([])
|
||||||
const selected = ref<[string] | []>([])
|
const selected = ref<[string] | []>([])
|
||||||
const batch = ref<number[]>([])
|
const batch = ref<number[]>([])
|
||||||
const deleting = ref(false)
|
const deleting = ref(false)
|
||||||
@@ -50,84 +50,98 @@ async function fetch() {
|
|||||||
totalItems.value = res.meta.totalCount
|
totalItems.value = res.meta.totalCount
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
// The URL is the source of truth: handlers only write the query, this watcher loads the list
|
||||||
syncQuery()
|
|
||||||
await fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncQuery() {
|
function syncQuery() {
|
||||||
const query: Record<string, string> = { page: String(page.value) }
|
const query: Record<string, string> = { page: String(page.value) }
|
||||||
|
|
||||||
if (text.value) query.text = text.value
|
if (text.value) query.text = text.value
|
||||||
if (assigneeId.value) query.assignee = assigneeId.value
|
if (assigneeId.value) query.assignee = assigneeId.value
|
||||||
if (status.value) query.status = status.value
|
if (status.value.length) query.status = status.value.join(',')
|
||||||
if (selected.value.length) query.selected_issue = selected.value[0]
|
if (selected.value.length) query.selected_issue = selected.value[0]
|
||||||
|
|
||||||
router.replace({ query })
|
router.replace({ query })
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.query,
|
[
|
||||||
(query) => {
|
() => route.query.page,
|
||||||
page.value = query.page ? Number(query.page) : 1
|
() => route.query.text,
|
||||||
text.value = query.text ? String(query.text) : null
|
() => route.query.assignee,
|
||||||
assigneeId.value = query.assignee ? String(query.assignee) : null
|
() => route.query.status,
|
||||||
status.value = query.status ? String(query.status) : null
|
currentProjectId,
|
||||||
selected.value = query.selected_issue ? [String(query.selected_issue)] : []
|
],
|
||||||
|
async ([qPage, qText, qAssignee, qStatus, projectId], old) => {
|
||||||
|
// The route changes before this view unmounts when navigating away
|
||||||
|
if (route.name !== 'issues') return
|
||||||
|
|
||||||
|
page.value = qPage ? Number(qPage) : 1
|
||||||
|
text.value = qText ? String(qText) : null
|
||||||
|
assigneeId.value = qAssignee ? String(qAssignee) : null
|
||||||
|
status.value = qStatus ? String(qStatus).split(',') : []
|
||||||
|
|
||||||
|
if (old && projectId !== old[4]) {
|
||||||
|
clearBatch()
|
||||||
|
close()
|
||||||
|
// Resetting the page changes the query and re-runs this watcher
|
||||||
|
if (page.value !== 1) {
|
||||||
|
page.value = 1
|
||||||
|
syncQuery()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch()
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query.selected_issue,
|
||||||
|
(key) => {
|
||||||
|
if (route.name !== 'issues') return
|
||||||
|
selected.value = key ? [String(key)] : []
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(selected, () => syncQuery())
|
watch(selected, () => syncQuery())
|
||||||
|
|
||||||
watch(currentProjectId, async () => {
|
watch(currentProjectId, (projectId) => issueStore.getStat(projectId), { immediate: true })
|
||||||
page.value = 1
|
|
||||||
|
|
||||||
clearBatch()
|
|
||||||
close()
|
|
||||||
await load()
|
|
||||||
await issueStore.getStat(currentProjectId.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await load()
|
|
||||||
await issueStore.getStat(currentProjectId.value)
|
|
||||||
})
|
|
||||||
const splitContainer = useTemplateRef<HTMLElement>('splitContainer')
|
const splitContainer = useTemplateRef<HTMLElement>('splitContainer')
|
||||||
const split = useSplitPane(splitContainer, { storageKey: 'issues-list', min: 0.25, max: 0.75 })
|
const split = useSplitPane(splitContainer, { storageKey: 'issues-list', min: 0.25, max: 0.75 })
|
||||||
const gridTemplate = computed(() => (selected.value.length ? split.gridTemplate.value : '1fr'))
|
const gridTemplate = computed(() => (selected.value.length ? split.gridTemplate.value : '1fr'))
|
||||||
const goToIssue = (key: string[]) => router.push(`/issues/${key[0]}`)
|
const goToIssue = (key: string[]) => router.push(`/issues/${key[0]}`)
|
||||||
|
|
||||||
async function updatePage(val: number) {
|
function updatePage(val: number) {
|
||||||
page.value = val
|
page.value = val
|
||||||
await load()
|
syncQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onTextChanged(value: string | null) {
|
function onTextChanged(value: string | null) {
|
||||||
text.value = value
|
text.value = value
|
||||||
page.value = 1
|
page.value = 1
|
||||||
await load()
|
syncQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onAssigneeChanged(value: string | null) {
|
function onAssigneeChanged(value: string | null) {
|
||||||
assigneeId.value = value
|
assigneeId.value = value
|
||||||
page.value = 1
|
page.value = 1
|
||||||
await load()
|
syncQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onStatusChanged(value: string | null) {
|
function onStatusChanged(value: string[]) {
|
||||||
status.value = value
|
status.value = value
|
||||||
page.value = 1
|
page.value = 1
|
||||||
await load()
|
syncQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onClear() {
|
function onClear() {
|
||||||
text.value = null
|
text.value = null
|
||||||
assigneeId.value = null
|
assigneeId.value = null
|
||||||
status.value = null
|
status.value = []
|
||||||
page.value = 1
|
page.value = 1
|
||||||
|
syncQuery()
|
||||||
await load()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
|||||||
Reference in new issue
Block a user