Finished admin part

This commit is contained in:
lucast committed 2026-02-03 20:42:47 +01:00
1 parent e63b3fdbb4
commit 9e29d1d703
5 files changed
+107 -17

No files matched your search

+12 -3
View File
@@ -18,6 +18,15 @@
<div class="category"> <div class="category">
<p>Manage</p> <p>Manage</p>
<Button
full
variant="ghost"
to="/admin/genre"
type="link"
icon-name="lucide:disc-3"
>
Genres
</Button>
<Button <Button
full full
variant="ghost" variant="ghost"
@@ -25,7 +34,7 @@
type="link" type="link"
icon-name="lucide:mic-vocal" icon-name="lucide:mic-vocal"
> >
Manage Artists Artists
</Button> </Button>
<Button <Button
full full
@@ -34,7 +43,7 @@
type="link" type="link"
icon-name="lucide:gallery-vertical-end" icon-name="lucide:gallery-vertical-end"
> >
Manage Albums Albums
</Button> </Button>
<Button <Button
full full
@@ -43,7 +52,7 @@
type="link" type="link"
icon-name="lucide:music" icon-name="lucide:music"
> >
Manage Songs Songs
</Button> </Button>
</div> </div>
+1 -1
View File
@@ -14,7 +14,7 @@ const tableRows: Row[] = [
name: "file", name: "file",
displayName: "Cover", displayName: "Cover",
type: "image", type: "image",
imagePrefix: `${import.meta.env.VITE_PUBLIC_BACKEND}/static/`, prefix: `${BACKEND_SERVE}/static/`,
}, },
{ {
name: "name", name: "name",
+1 -1
View File
@@ -14,7 +14,7 @@ const tableRows: Row[] = [
name: "file", name: "file",
displayName: "Cover", displayName: "Cover",
type: "image", type: "image",
imagePrefix: `${import.meta.env.VITE_PUBLIC_BACKEND}/static/`, prefix: `${BACKEND_SERVE}/static/`,
}, },
{ {
name: "name", name: "name",
+3 -1
View File
@@ -1 +1,3 @@
<template>Hello</template> <script setup lang="ts"></script>
<template></template>
+90 -11
View File
@@ -2,17 +2,29 @@
import type { Field } from "~/components/Admin/ModalForm.vue" import type { Field } from "~/components/Admin/ModalForm.vue"
import type { Row } from "~/ui/Table.vue" import type { Row } from "~/ui/Table.vue"
const { data, status } = useApiFetch<Track[]>("/all/tracks") const { data, refresh } = useApiFetch<Album[]>("/all/tracks")
const toast = useToast()
const tableRows: Row[] = [ const tableRows: Row[] = [
{ {
name: "_id", name: "_id",
displayName: "ID", displayName: "ID",
}, },
{
name: "fileDir",
displayName: "Audio File",
type: "audio",
prefix: `${BACKEND_SERVE}/static/`,
suffix: "/original.m4a",
},
{ {
name: "name", name: "name",
displayName: "Name", displayName: "Name",
}, },
{
name: "album.name",
displayName: "Album",
},
{ {
name: "action", name: "action",
}, },
@@ -20,29 +32,77 @@ const tableRows: Row[] = [
import { type Option } from "~/ui/SearchSelect.vue" import { type Option } from "~/ui/SearchSelect.vue"
const options: Option[] = [ const { data: artistData } = useApiFetch<Genre[]>("/all/artists")
{ label: "A", value: "a" }, const artistFetchOptions = async (q?: string): Promise<Option[]> => {
{ label: "B", value: "b" }, return artistData.value.map<Option>((g) => ({
] label: g.name,
value: g._id,
}))
}
const fetchOptions = async (q: string): Promise<Option[]> => { const { data: albumData } = useApiFetch<Genre[]>("/all/albums")
return options const albumFetchOptions = async (q?: string): Promise<Option[]> => {
return albumData.value.map<Option>((g) => ({
label: g.name,
value: g._id,
}))
} }
const modalFields: Field[] = [ const modalFields: Field[] = [
{ key: "name", label: "Name" }, { key: "name", label: "Name" },
{ key: "album", label: "Album", type: "search-select", fetchOptions },
{ key: "file", label: "Audio file", type: "file" }, { 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 showModal = ref<boolean>(false)
const item = ref<Track | undefined>(undefined) const item = ref<Track | undefined>(undefined)
const save = (value: any) => { const save = async (value: any) => {
console.log(value) 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 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 * @param id Set it to a value to make it edit instead of creating a new one
@@ -51,6 +111,8 @@ const show = (id?: string) => {
if (!id) { if (!id) {
item.value = undefined item.value = undefined
showModal.value = true showModal.value = true
return
} }
const curItem: any = data.value.filter((i) => i._id == id)[0] const curItem: any = data.value.filter((i) => i._id == id)[0]
@@ -65,13 +127,23 @@ const show = (id?: string) => {
label: curItem.album.name, label: curItem.album.name,
} }
if (transformed.artists)
transformed.artists = transformed.artists.map((a: Artist) => ({
value: a._id,
label: a.name,
}))
item.value = transformed item.value = transformed
showModal.value = true showModal.value = true
} }
watch(data, () => console.log(data))
</script> </script>
<template> <template>
<h1 class="title">Manage songs</h1>
<Table :rows="tableRows" :data="data" class="table"> <Table :rows="tableRows" :data="data" class="table">
<template #action="{ item, index }"> <template #action="{ item, index }">
<div class="action"> <div class="action">
@@ -87,11 +159,18 @@ const show = (id?: string) => {
@save="save" @save="save"
:fields="modalFields" :fields="modalFields"
:item="item" :item="item"
title="Add new song" :title="item ? 'Edit song' : 'Add new song'"
/> />
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@use "@/styles/util" as *;
.title {
@include fontSize(24px);
margin-bottom: 0.4rem;
}
.action { .action {
display: flex; display: flex;
gap: 0.2rem; gap: 0.2rem;