diff --git a/app/components/BottomNav.vue b/app/components/BottomNav.vue new file mode 100644 index 0000000..4a33f74 --- /dev/null +++ b/app/components/BottomNav.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/app/layouts/default.vue b/app/layouts/default.vue index a5edab2..459591d 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -1,25 +1,44 @@ diff --git a/app/pages/admin/groups/[id].vue b/app/pages/admin/groups/[id].vue index 31c11d1..1be038e 100644 --- a/app/pages/admin/groups/[id].vue +++ b/app/pages/admin/groups/[id].vue @@ -36,6 +36,56 @@ + +
+
+

+ 👤 Добавить участника +

+ +
+
+ +
+ +
+
+
+
+
+ + +
+
+
+ {{ u.username }} + {{ u.email }} +
+ +
+
+
+ Пользователи не найдены +
+
+ +
+ +
+
+
@@ -50,6 +100,58 @@ const auth = useAuth() const group = ref(null) const members = ref([]) const showAddMemberModal = ref(false) +const searchQuery = ref('') +const searchResults = ref([]) +const searching = ref(false) + +const searchUsers = async () => { + if (searchQuery.value.length < 2) { + searchResults.value = [] + return + } + searching.value = true + try { + const data = await $fetch('/api/users/search', { + params: { q: searchQuery.value }, + headers: { Authorization: `Bearer ${auth.token.value}` } + }) + searchResults.value = data.users || [] + } catch (e) { + console.error('Search failed', e) + } finally { + searching.value = false + } +} + +const addMember = async (userId) => { + try { + await $fetch(`/api/groups/${route.params.id}/add-member`, { + method: 'POST', + body: { userId, role: 'member' }, + headers: { Authorization: `Bearer ${auth.token.value}` } + }) + showAddMemberModal.value = false + searchQuery.value = '' + searchResults.value = [] + await fetchGroup() + } catch (e) { + alert(e.data?.message || 'Ошибка при добавлении') + } +} + +const removeMember = async (userId) => { + if (!confirm('Удалить участника из группы?')) return + try { + await $fetch(`/api/groups/${route.params.id}/members`, { + method: 'DELETE', + body: { userId }, + headers: { Authorization: `Bearer ${auth.token.value}` } + }) + await fetchGroup() + } catch (e) { + alert('Ошибка при удалении') + } +} const fetchGroup = async () => { try { diff --git a/app/pages/canvases.vue b/app/pages/canvases.vue index 6c919a6..aa88b49 100644 --- a/app/pages/canvases.vue +++ b/app/pages/canvases.vue @@ -25,8 +25,8 @@ - - + + +
+ +
- @@ -85,33 +96,97 @@ -
-
-

Создать Новую Карту

-
+
+
+

+ ✨ Новая Карта +

+
- - + +
- -
- -
-
- - +
+ +
+
+ + +
+
+

+ ⚙️ Настройки доступа +

+ +
+
+ + +
+ +
+ +
+ + +
+

+ {{ editCanvas.isPublic ? 'Любой пользователь сможет просматривать эту карту.' : 'Карта доступна только вам и членам выбранной группы.' }} +

+
+ +
+ + +
+ +
+ +
@@ -154,6 +229,47 @@ const activeTab = ref('personal') groupId: '' }) +const showSettingsModal = ref(false) +const saving = ref(false) +const editCanvas = reactive({ + id: '', + name: '', + isPublic: false, + groupId: null +}) + +const openSettings = (canvas) => { + editCanvas.id = canvas.id + editCanvas.name = canvas.name + editCanvas.isPublic = canvas.isPublic + editCanvas.groupId = canvas.groupId + showSettingsModal.value = true +} + +const closeSettings = () => { + showSettingsModal.value = false +} + +const saveSettings = async () => { + saving.value = true + try { + await authFetch(`/api/canvases/${editCanvas.id}`, { + method: 'PATCH', + body: { + name: editCanvas.name, + isPublic: editCanvas.isPublic, + groupId: editCanvas.groupId + } + }) + await loadData() + closeSettings() + } catch (error) { + alert('Ошибка при сохранении: ' + (error.data?.message || error.message)) + } finally { + saving.value = false + } +} + const currentUserId = computed(() => user.value?.id || '') // Load data @@ -173,14 +289,13 @@ const loadData = async () => { // Filter canvases based on active tab const filteredCanvases = computed(() => { const userId = currentUserId.value + if (!userId) return [] + switch (activeTab.value) { case 'personal': - return canvases.value.filter(c => !c.groupId) - case 'shared': - return canvases.value.filter(c => - c.groupId && userGroups.value.some(g => g.id === c.groupId) || - c.ownerId !== userId && c.isPublic - ) + return canvases.value.filter(c => c.ownerId === userId && !c.groupId) + case 'team': + return canvases.value.filter(c => !!c.groupId) case 'public': return canvases.value.filter(c => c.isPublic) default: diff --git a/app/pages/index.vue b/app/pages/index.vue index 235467b..5b580df 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -1,18 +1,21 @@