Add Album and Song management pages; implement AlbumSlider component and update Card component for genre handling

This commit is contained in:
lucast committed 2026-02-03 20:53:08 +01:00
1 parent a9cb8c45d5
commit 4a54f8b2a2
6 files changed
+359 -15

No files matched your search

+131
View File
@@ -0,0 +1,131 @@
<script setup lang="ts">
import type { Field } from "~/components/Admin/ModalForm.vue"
import type { Row } from "~/ui/Table.vue"
const { data, refresh } = useApiFetch<Track[]>("/all/artists")
const toast = useToast()
const tableRows: Row[] = [
{
name: "_id",
displayName: "ID",
},
{
name: "file",
displayName: "Cover",
type: "image",
prefix: `${BACKEND_SERVE}`,
},
{
name: "name",
displayName: "Name",
},
{
name: "action",
},
]
const modalFields: Field[] = [
{ key: "name", label: "Name" },
{ key: "file", label: "Cover file", type: "file" },
]
const showModal = ref<boolean>(false)
const item = ref<Track | undefined>(undefined)
const save = async (value: any) => {
if (value._id) {
try {
await cfetch("/admin/artist", {
method: "PATCH",
data: value,
params: { id: value._id },
forceFormData: true,
})
toast.show("Artist updated successfully", "success")
} catch {
toast.show("Failed to update artist", "error")
return false
}
} else {
try {
await cfetch("/admin/artist", {
method: "POST",
data: value,
})
toast.show("Artist added successfully", "success")
} catch {
toast.show("Failed to add artist", "error")
return false
}
}
refresh()
return true
}
/**
* @param id Set it to a value to make it edit instead of creating a new one
*/
const show = (id?: string) => {
if (!id) {
item.value = undefined
showModal.value = true
return
}
const curItem: any = data.value.filter((i) => i._id == id)[0]
if (!curItem) return
item.value = curItem
showModal.value = true
}
</script>
<template>
<h1 class="title">Manage artists</h1>
<Table :rows="tableRows" :data="data" class="table">
<template #action="{ item, index }">
<div class="action">
<Button @click="show(item._id)">Edit</Button>
<Button @click="">Delete</Button>
</div>
</template>
</Table>
<Button @click="show()">Add a new artist</Button>
<AdminModalForm
v-model:open="showModal"
@save="save"
:fields="modalFields"
:item="item"
:title="item ? 'Edit artist' : 'Add new artist'"
/>
</template>
<style lang="scss" scoped>
@use "@/styles/util" as *;
.title {
@include fontSize(24px);
margin-bottom: 0.4rem;
}
.action {
display: flex;
gap: 0.2rem;
}
.table {
margin-bottom: 0.4rem;
}
</style>
+182
View File
@@ -0,0 +1,182 @@
<script setup lang="ts">
import type { Field } from "~/components/Admin/ModalForm.vue"
import type { Row } from "~/ui/Table.vue"
const { data, refresh } = useApiFetch<Album[]>("/all/tracks")
const toast = useToast()
const tableRows: Row[] = [
{
name: "_id",
displayName: "ID",
},
{
name: "fileDir",
displayName: "Audio File",
type: "audio",
prefix: `${BACKEND_SERVE}`,
suffix: "/original.m4a",
},
{
name: "name",
displayName: "Name",
},
{
name: "album.name",
displayName: "Album",
},
{
name: "action",
},
]
import { type Option } from "~/ui/SearchSelect.vue"
const { data: artistData } = useApiFetch<Genre[]>("/all/artists")
const artistFetchOptions = async (q?: string): Promise<Option[]> => {
return artistData.value.map<Option>((g) => ({
label: g.name,
value: g._id,
}))
}
const { data: albumData } = useApiFetch<Genre[]>("/all/albums")
const albumFetchOptions = async (q?: string): Promise<Option[]> => {
return albumData.value.map<Option>((g) => ({
label: g.name,
value: g._id,
}))
}
const modalFields: Field[] = [
{ key: "name", label: "Name" },
{ key: "file", label: "Audio file", type: "file" },
{
key: "artists",
label: "Artists",
type: "search-select-array",
fetchOptions: artistFetchOptions,
},
{
key: "album",
label: "Album",
type: "search-select",
fetchOptions: albumFetchOptions,
},
]
const showModal = ref<boolean>(false)
const item = ref<Track | undefined>(undefined)
const save = async (value: any) => {
if (value._id) {
try {
await cfetch("/admin/track", {
method: "PATCH",
data: value,
params: { id: value._id },
forceFormData: true,
})
toast.show("Song updated successfully", "success")
} catch {
toast.show("Failed to update song", "error")
return false
}
} else {
try {
await cfetch("/admin/track", {
method: "POST",
data: value,
})
toast.show("Song added successfully", "success")
} catch {
toast.show("Failed to add song", "error")
return false
}
}
refresh()
return true
}
/**
* @param id Set it to a value to make it edit instead of creating a new one
*/
const show = (id?: string) => {
if (!id) {
item.value = undefined
showModal.value = true
return
}
const curItem: any = data.value.filter((i) => i._id == id)[0]
if (!curItem) return
const transformed = { ...curItem }
if (transformed.album)
transformed.album = {
value: curItem.album._id,
label: curItem.album.name,
}
if (transformed.artists)
transformed.artists = transformed.artists.map((a: Artist) => ({
value: a._id,
label: a.name,
}))
item.value = transformed
showModal.value = true
}
watch(data, () => console.log(data))
</script>
<template>
<h1 class="title">Manage songs</h1>
<Table :rows="tableRows" :data="data" class="table">
<template #action="{ item, index }">
<div class="action">
<Button @click="show(item._id)">Edit</Button>
<Button @click="">Delete</Button>
</div>
</template>
</Table>
<Button @click="show()">Add a new song</Button>
<AdminModalForm
v-model:open="showModal"
@save="save"
:fields="modalFields"
:item="item"
:title="item ? 'Edit song' : 'Add new song'"
/>
</template>
<style lang="scss" scoped>
@use "@/styles/util" as *;
.title {
@include fontSize(24px);
margin-bottom: 0.4rem;
}
.action {
display: flex;
gap: 0.2rem;
}
.table {
margin-bottom: 0.4rem;
}
</style>