init
This commit is contained in:
@@ -0,0 +1,115 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import {ref, watch} from 'vue'
|
||||||
|
import {useChannelStore, type Channel} from '@/stores/channel'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: boolean
|
||||||
|
channel: Channel | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'updated', channel: Channel): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const channelStore = useChannelStore()
|
||||||
|
const formData = ref({name: '', channel_type: 'text'})
|
||||||
|
const isSubmitting = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
const channelTypeOptions = [
|
||||||
|
{title: 'Text', value: 'text'},
|
||||||
|
{title: 'Voice', value: 'voice'},
|
||||||
|
{title: 'DM', value: 'dm'},
|
||||||
|
]
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formData.value = {
|
||||||
|
name: props.channel?.name ?? '',
|
||||||
|
channel_type: props.channel?.channel_type ?? 'text',
|
||||||
|
}
|
||||||
|
error.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
const channel = props.channel
|
||||||
|
const name = formData.value.name.trim()
|
||||||
|
if (!channel || !name) return
|
||||||
|
|
||||||
|
isSubmitting.value = true
|
||||||
|
error.value = null
|
||||||
|
try {
|
||||||
|
const updatedChannel = await channelStore.updateChannel(channel.id, {
|
||||||
|
name,
|
||||||
|
channel_type: formData.value.channel_type,
|
||||||
|
server_id: channel.server_id ?? null,
|
||||||
|
category_id: channel.category_id ?? null,
|
||||||
|
})
|
||||||
|
emit('updated', updatedChannel)
|
||||||
|
handleClose()
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Failed to update channel'
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => [props.modelValue, props.channel] as const, ([isOpen]) => {
|
||||||
|
if (isOpen) resetForm()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-dialog :model-value="modelValue" width="400" @update:model-value="handleClose">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>Edit Channel</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-alert v-if="error" class="mb-4" density="compact" type="error" variant="tonal">
|
||||||
|
{{ error }}
|
||||||
|
</v-alert>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<v-text-field
|
||||||
|
v-model="formData.name"
|
||||||
|
density="compact"
|
||||||
|
label="Channel Name"
|
||||||
|
outlined
|
||||||
|
@keyup.enter="handleSubmit"
|
||||||
|
/>
|
||||||
|
<v-select
|
||||||
|
v-model="formData.channel_type"
|
||||||
|
:items="channelTypeOptions"
|
||||||
|
density="compact"
|
||||||
|
label="Channel Type"
|
||||||
|
outlined
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn :disabled="isSubmitting" variant="text" @click="handleClose">Cancel</v-btn>
|
||||||
|
<v-btn
|
||||||
|
:disabled="!formData.name.trim()"
|
||||||
|
:loading="isSubmitting"
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.space-y-4 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,12 +3,14 @@ import {storeToRefs} from 'pinia'
|
|||||||
import {useChannelStore} from '@/stores/channel'
|
import {useChannelStore} from '@/stores/channel'
|
||||||
import {useCategoryStore} from '@/stores/category'
|
import {useCategoryStore} from '@/stores/category'
|
||||||
import {computed, onMounted, onUnmounted, ref, watch} from 'vue'
|
import {computed, onMounted, onUnmounted, ref, watch} from 'vue'
|
||||||
import {useRoute} from 'vue-router'
|
import {useRoute, useRouter} from 'vue-router'
|
||||||
import CreateChannelDialog from '@/components/channel/CreateChannelDialog.vue'
|
import CreateChannelDialog from '@/components/channel/CreateChannelDialog.vue'
|
||||||
|
import EditChannelDialog from '@/components/channel/EditChannelDialog.vue'
|
||||||
import CreateCategoryDialog from '@/components/category/CreateCategoryDialog.vue'
|
import CreateCategoryDialog from '@/components/category/CreateCategoryDialog.vue'
|
||||||
import {type MenuItem, useContextMenu} from '@/composables/useContextMenu'
|
import {type MenuItem, useContextMenu} from '@/composables/useContextMenu'
|
||||||
import {useUserStore} from "@/stores/user.ts";
|
import {useUserStore} from "@/stores/user.ts";
|
||||||
import {type OrderedResourceType, useServerStore} from "@/stores/server.ts";
|
import {type OrderedResourceType, useServerStore} from "@/stores/server.ts";
|
||||||
|
import type {Channel} from '@/stores/channel'
|
||||||
import ChannelPermissionsDialog from '@/components/permissions/ChannelPermissionsDialog.vue'
|
import ChannelPermissionsDialog from '@/components/permissions/ChannelPermissionsDialog.vue'
|
||||||
import {useAuthStore} from '@/stores/auth'
|
import {useAuthStore} from '@/stores/auth'
|
||||||
import ServerSettingsDialog from '@/components/server/ServerSettingsDialog.vue'
|
import ServerSettingsDialog from '@/components/server/ServerSettingsDialog.vue'
|
||||||
@@ -20,6 +22,7 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
const channelStore = useChannelStore()
|
const channelStore = useChannelStore()
|
||||||
const categoryStore = useCategoryStore()
|
const categoryStore = useCategoryStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@@ -29,6 +32,8 @@ const {openContextMenu} = useContextMenu()
|
|||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const showPermissionsDialog = ref(false)
|
const showPermissionsDialog = ref(false)
|
||||||
const selectedChannel = ref<any | null>(null)
|
const selectedChannel = ref<any | null>(null)
|
||||||
|
const channelToEdit = ref<Channel | null>(null)
|
||||||
|
const showEditChannelDialog = ref(false)
|
||||||
const showServerSettings = ref(false)
|
const showServerSettings = ref(false)
|
||||||
const serverName = computed(() => serverStore.servers.find(server => server.id === props.serverId)?.name || 'Serveur')
|
const serverName = computed(() => serverStore.servers.find(server => server.id === props.serverId)?.name || 'Serveur')
|
||||||
|
|
||||||
@@ -195,12 +200,26 @@ function onCategoryContextMenu(event: MouseEvent, category: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Right click menu (channel)
|
// Right click menu (channel)
|
||||||
async function openEditDialog(channel: any) {
|
function openEditDialog(channel: Channel) {
|
||||||
console.log("edit dialog clicked")
|
channelToEdit.value = channel
|
||||||
|
showEditChannelDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteChannel(channelId: string) {
|
async function deleteChannel(channelId: string) {
|
||||||
console.log("delete channel clicked")
|
const channel = channelStore.channels.find(item => item.id === channelId)
|
||||||
|
const channelName = channel?.name || 'ce canal'
|
||||||
|
if (!window.confirm(`Supprimer « ${channelName} » ? Cette action est irréversible.`)) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await channelStore.deleteChannel(channelId)
|
||||||
|
if (route.params.channelId === channelId) {
|
||||||
|
await router.push(`/server/${props.serverId}`)
|
||||||
|
}
|
||||||
|
await refreshServerTree()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete channel:', error)
|
||||||
|
alert(error instanceof Error ? error.message : 'Failed to delete channel')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChannelContextMenu(event: MouseEvent, channel: any) {
|
function onChannelContextMenu(event: MouseEvent, channel: any) {
|
||||||
@@ -342,6 +361,12 @@ function onChannelContextMenu(event: MouseEvent, channel: any) {
|
|||||||
@created="refreshServerTree"
|
@created="refreshServerTree"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<EditChannelDialog
|
||||||
|
v-model="showEditChannelDialog"
|
||||||
|
:channel="channelToEdit"
|
||||||
|
@updated="refreshServerTree"
|
||||||
|
/>
|
||||||
|
|
||||||
<CreateCategoryDialog
|
<CreateCategoryDialog
|
||||||
v-model="showCategoryDialog"
|
v-model="showCategoryDialog"
|
||||||
:server-id="serverId"
|
:server-id="serverId"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
import {useApi} from "@/composables/useApi.ts";
|
import {useApi} from "@/composables/useApi.ts";
|
||||||
|
|
||||||
interface Channel {
|
export interface Channel {
|
||||||
id: string
|
id: string
|
||||||
name?: string
|
name?: string
|
||||||
channel_type: string
|
channel_type: string
|
||||||
@@ -60,6 +60,52 @@ export const useChannelStore = defineStore('channel', {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async updateChannel(channelId: string, payload: {
|
||||||
|
name?: string;
|
||||||
|
channel_type: string;
|
||||||
|
server_id?: string | null;
|
||||||
|
category_id?: string | null;
|
||||||
|
}) {
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
try {
|
||||||
|
const response = await useApi().put(`/channels/${channelId}`, payload);
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => null);
|
||||||
|
throw new Error(error?.message || 'Failed to update channel');
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedChannel: Channel = await response.json();
|
||||||
|
const index = this.channels.findIndex(channel => channel.id === channelId);
|
||||||
|
if (index >= 0) {
|
||||||
|
updatedChannel.unread_count ??= this.channels[index].unread_count ?? 0;
|
||||||
|
this.channels[index] = updatedChannel;
|
||||||
|
}
|
||||||
|
return updatedChannel;
|
||||||
|
} catch (err) {
|
||||||
|
this.error = err instanceof Error ? err.message : 'Unknown error';
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteChannel(channelId: string) {
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
try {
|
||||||
|
const response = await useApi().delete(`/channels/${channelId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => null);
|
||||||
|
throw new Error(error?.message || 'Failed to delete channel');
|
||||||
|
}
|
||||||
|
this.channels = this.channels.filter(channel => channel.id !== channelId);
|
||||||
|
} catch (err) {
|
||||||
|
this.error = err instanceof Error ? err.message : 'Unknown error';
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
reset() {
|
reset() {
|
||||||
this.channels = [];
|
this.channels = [];
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user