Merge pull request #5156 from christianbeeznest/ofaj-21101-6
Social: Add social group UI enhancements and API integration - refs BT#21101pull/5168/head
commit
3905a6da0f
@ -0,0 +1,38 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard plain> |
||||||
|
<div class="p-4 text-center"> |
||||||
|
<img |
||||||
|
:src="groupInfo.image" |
||||||
|
class="mb-4 w-24 h-24 mx-auto rounded-full" |
||||||
|
alt="Group picture" |
||||||
|
/> |
||||||
|
<hr /> |
||||||
|
<BaseButton |
||||||
|
:label="t('Edit this group')" |
||||||
|
type="primary" |
||||||
|
class="mt-4" |
||||||
|
@click="editGroup" |
||||||
|
icon="edit" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { computed, inject, onMounted, ref, watch } from "vue" |
||||||
|
import { useStore } from 'vuex' |
||||||
|
import BaseCard from "../basecomponents/BaseCard.vue" |
||||||
|
import BaseButton from "../basecomponents/BaseButton.vue" |
||||||
|
import { useI18n } from "vue-i18n" |
||||||
|
import { useRoute } from "vue-router" |
||||||
|
|
||||||
|
const { t } = useI18n() |
||||||
|
const store = useStore() |
||||||
|
const route = useRoute() |
||||||
|
const groupInfo = inject('group-info') |
||||||
|
const isGroup = inject('is-group') |
||||||
|
|
||||||
|
const editGroup = () => { |
||||||
|
window.location = "/account/edit" |
||||||
|
} |
||||||
|
</script> |
@ -0,0 +1,60 @@ |
|||||||
|
<template> |
||||||
|
<BaseCard class="social-side-menu mt-4"> |
||||||
|
<template #header> |
||||||
|
<div class="px-4 py-2 -mb-2 bg-gray-15"> |
||||||
|
<h2 class="text-h5">{{ t('Social Group') }}</h2> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<hr class="-mt-2 mb-4 -mx-4"> |
||||||
|
<ul class="menu-list"> |
||||||
|
<li class="menu-item"> |
||||||
|
<router-link to="/social"> |
||||||
|
<i class="mdi mdi-home" aria-hidden="true"></i> |
||||||
|
{{ t("Home") }} |
||||||
|
</router-link> |
||||||
|
</li> |
||||||
|
<li class="menu-item"> |
||||||
|
<router-link :to="{ name: '', params: { group_id: groupInfo.id } }"> |
||||||
|
<i class="mdi mdi-account-multiple-outline" aria-hidden="true"></i> |
||||||
|
{{ t("Waiting list") }} |
||||||
|
</router-link> |
||||||
|
</li> |
||||||
|
<li class="menu-item"> |
||||||
|
<router-link :to="{ name: 'UserGroupInvite', params: { group_id: groupInfo.id } }"> |
||||||
|
<i class="mdi mdi-account-plus" aria-hidden="true"></i> |
||||||
|
{{ t("Invite friends") }} |
||||||
|
</router-link> |
||||||
|
</li> |
||||||
|
<li class="menu-item"> |
||||||
|
<router-link :to="{ name: '', params: { group_id: groupInfo.id } }"> |
||||||
|
<i class="mdi mdi-exit-to-app" aria-hidden="true"></i> |
||||||
|
{{ t("Leave group") }} |
||||||
|
</router-link> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</BaseCard> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import BaseCard from "../basecomponents/BaseCard.vue" |
||||||
|
import { useRoute } from 'vue-router' |
||||||
|
import { useI18n } from "vue-i18n" |
||||||
|
import { onMounted, computed, ref, inject, watchEffect } from "vue" |
||||||
|
import { useStore } from "vuex" |
||||||
|
import { useSecurityStore } from "../../store/securityStore" |
||||||
|
|
||||||
|
const { t } = useI18n() |
||||||
|
const route = useRoute() |
||||||
|
const store = useStore() |
||||||
|
const securityStore = useSecurityStore() |
||||||
|
|
||||||
|
const groupInfo = inject('group-info') |
||||||
|
const isGroup = inject('is-group') |
||||||
|
|
||||||
|
const isActive = (path, filterType = null) => { |
||||||
|
const pathMatch = route.path.startsWith(path) |
||||||
|
const hasQueryParams = Object.keys(route.query).length > 0 |
||||||
|
const filterMatch = filterType ? (route.query.filterType === filterType && hasQueryParams) : !hasQueryParams |
||||||
|
return pathMatch && filterMatch |
||||||
|
} |
||||||
|
</script> |
@ -0,0 +1,55 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<div class="discussions-header"> |
||||||
|
<h2>Discussions</h2> |
||||||
|
<a :href="threadCreationUrl" class="btn btn-primary create-thread-btn ajax"> |
||||||
|
<i class="pi pi-plus"></i> {{ t("Create thread") }} |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
<div class="discussion-item" v-for="discussion in discussions" :key="discussion.id"> |
||||||
|
<div class="discussion-content"> |
||||||
|
<div class="discussion-title" v-html="discussion.title"></div> |
||||||
|
<div class="discussion-details"> |
||||||
|
<i class="mdi mdi-message-reply-text icon"></i> |
||||||
|
<span>{{ discussion.repliesCount }} {{ t("Replies") }}</span> |
||||||
|
<i class="mdi mdi-clock-outline icon"></i> |
||||||
|
<span>Created {{ new Date(discussion.sendDate).toLocaleDateString() }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="discussion-author"> |
||||||
|
<img v-if="discussion.sender.illustrationUrl" :src="discussion.sender.illustrationUrl" class="author-avatar-icon"> |
||||||
|
<i v-else class="mdi mdi-account-circle-outline author-avatar-icon"></i> |
||||||
|
<span class="author-name">{{ discussion.sender.name }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted, computed } from 'vue' |
||||||
|
import { useRoute } from 'vue-router' |
||||||
|
import axios from 'axios' |
||||||
|
import { useI18n } from "vue-i18n" |
||||||
|
const route = useRoute() |
||||||
|
const discussions = ref([]) |
||||||
|
const groupId = ref(route.params.group_id) |
||||||
|
const { t } = useI18n() |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
if (groupId.value) { |
||||||
|
try { |
||||||
|
const response = await axios.get(`/api/messages/by-group/list?groupId=${groupId.value}`) |
||||||
|
discussions.value = response.data['hydra:member'].map(discussion => ({ |
||||||
|
...discussion, |
||||||
|
repliesCount: discussion.receiversTo.length + discussion.receiversCc.length |
||||||
|
})) |
||||||
|
} catch (error) { |
||||||
|
console.error('Error fetching discussions:', error) |
||||||
|
discussions.value = [] |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
const threadCreationUrl = computed(() => { |
||||||
|
return `/main/social/message_for_group_form.inc.php?view_panel=1&user_friend=1&group_id=${groupId.value}&action=add_message_group` |
||||||
|
}) |
||||||
|
</script> |
@ -0,0 +1,63 @@ |
|||||||
|
<template> |
||||||
|
<div class="group-members"> |
||||||
|
<div class="edit-members"> |
||||||
|
<BaseButton |
||||||
|
label="Edit members list" |
||||||
|
type="primary" |
||||||
|
class="edit-members-btn" |
||||||
|
icon="pi pi-plus" |
||||||
|
@click="editMembers" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div class="members-grid"> |
||||||
|
<div class="member-card" v-for="member in members" :key="member.id"> |
||||||
|
<div class="member-avatar"> |
||||||
|
<img v-if="member.avatar" :src="member.avatar" alt="Member avatar"> |
||||||
|
<i v-else class="mdi mdi-account-circle-outline"></i> |
||||||
|
</div> |
||||||
|
<div class="member-name"> |
||||||
|
{{ member.name }} |
||||||
|
<i v-if="member.isAdmin" class="mdi mdi-star-outline admin-icon"></i> |
||||||
|
</div> |
||||||
|
<div class="member-role" v-if="member.role">{{ member.role }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, onMounted } from 'vue' |
||||||
|
import { useRoute } from 'vue-router' |
||||||
|
import BaseButton from "../basecomponents/BaseButton.vue" |
||||||
|
import axios from "axios" |
||||||
|
|
||||||
|
const route = useRoute() |
||||||
|
const members = ref([]) |
||||||
|
const groupId = ref(route.params.group_id) |
||||||
|
|
||||||
|
const fetchMembers = async (groupId) => { |
||||||
|
if (groupId.value) { |
||||||
|
try { |
||||||
|
const response = await axios.get(`/api/usergroups/${groupId.value}/members`) |
||||||
|
members.value = response.data['hydra:member'].map(member => ({ |
||||||
|
id: member.id, |
||||||
|
name: member.username, |
||||||
|
role: member.relationType === 1 ? 'Admin' : 'Member', |
||||||
|
avatar: null, |
||||||
|
isAdmin: member.relationType === 1 |
||||||
|
})) |
||||||
|
} catch (error) { |
||||||
|
console.error('Error fetching group members:', error) |
||||||
|
members.value = [] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
const editMembers = () => { |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
if (groupId) { |
||||||
|
fetchMembers(groupId) |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
@ -1,9 +1,34 @@ |
|||||||
<template> |
<template> |
||||||
|
<div class="flex flex-col md:flex-row gap-4"> |
||||||
|
<div class="md:basis-1/3 lg:basis-1/4 2xl:basis-1/6 flex flex-col"> |
||||||
|
<UserProfileCard v-if="!isLoading && !isGroup" /> |
||||||
|
<GroupInfoCard v-if="!isLoading && isGroup" /> |
||||||
|
<SocialSideMenu v-if="!isLoading && !isGroup" /> |
||||||
|
<SocialGroupMenu v-if="!isLoading && isGroup" /> |
||||||
|
</div> |
||||||
|
<div class="md:basis-2/3 lg:basis-3/4 2xl:basis-5/6"> |
||||||
<router-view></router-view> |
<router-view></router-view> |
||||||
|
</div> |
||||||
|
</div> |
||||||
</template> |
</template> |
||||||
|
<script setup> |
||||||
|
import UserProfileCard from "../social/UserProfileCard.vue" |
||||||
|
import SocialSideMenu from "../social/SocialSideMenu.vue" |
||||||
|
import { useStore } from "vuex" |
||||||
|
import { useRoute } from "vue-router" |
||||||
|
import { onMounted, provide, readonly, ref, watch } from "vue" |
||||||
|
import { useSocialInfo } from "../../composables/useSocialInfo" |
||||||
|
import SocialGroupMenu from "../social/SocialGroupMenu.vue" |
||||||
|
import GroupInfoCard from "../social/GroupInfoCard.vue" |
||||||
|
|
||||||
|
const store = useStore() |
||||||
|
const route = useRoute() |
||||||
|
|
||||||
|
const { user, isCurrentUser, groupInfo, isGroup, loadGroup, loadUser, isLoading } = useSocialInfo() |
||||||
|
|
||||||
|
provide("social-user", user) |
||||||
|
provide("is-current-user", isCurrentUser) |
||||||
|
provide("group-info", groupInfo) |
||||||
|
provide("is-group", isGroup) |
||||||
|
|
||||||
<script> |
|
||||||
export default { |
|
||||||
name: 'UserGroupLayout' |
|
||||||
} |
|
||||||
</script> |
</script> |
||||||
|
@ -0,0 +1,80 @@ |
|||||||
|
import { ref, readonly, onMounted } from "vue"; |
||||||
|
import { useStore } from "vuex"; |
||||||
|
import { useRoute } from "vue-router"; |
||||||
|
import axios from "axios"; |
||||||
|
|
||||||
|
export function useSocialInfo() { |
||||||
|
const store = useStore(); |
||||||
|
const route = useRoute(); |
||||||
|
|
||||||
|
const user = ref({}); |
||||||
|
const isCurrentUser = ref(true); |
||||||
|
const groupInfo = ref({}); |
||||||
|
const isGroup = ref(false); |
||||||
|
|
||||||
|
const isLoading = ref(true); |
||||||
|
|
||||||
|
const loadGroup = async (groupId) => { |
||||||
|
isLoading.value = true; |
||||||
|
if (groupId) { |
||||||
|
try { |
||||||
|
const response = await axios.get(`/api/usergroup/${groupId}`); |
||||||
|
const groupData = response.data; |
||||||
|
const extractedId = groupData['@id'].split('/').pop(); |
||||||
|
|
||||||
|
groupInfo.value = { |
||||||
|
...groupData, |
||||||
|
id: extractedId |
||||||
|
}; |
||||||
|
|
||||||
|
isGroup.value = true; |
||||||
|
} catch (error) { |
||||||
|
console.error("Error loading group:", error); |
||||||
|
groupInfo.value = {}; |
||||||
|
isGroup.value = false; |
||||||
|
} |
||||||
|
isLoading.value = false; |
||||||
|
} else { |
||||||
|
isGroup.value = false; |
||||||
|
groupInfo.value = {}; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const loadUser = async () => { |
||||||
|
try { |
||||||
|
if (route.query.id) { |
||||||
|
user.value = await store.dispatch("user/load", '/api/users/' + route.query.id) |
||||||
|
isCurrentUser.value = false |
||||||
|
} else { |
||||||
|
user.value = store.getters["security/getUser"] |
||||||
|
isCurrentUser.value = true |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
user.value = {} |
||||||
|
isCurrentUser.value = true |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
try { |
||||||
|
//if (!route.params.group_id) {
|
||||||
|
await loadUser(); |
||||||
|
//}
|
||||||
|
if (route.params.group_id) { |
||||||
|
await loadGroup(route.params.group_id); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
isLoading.value = false; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return { |
||||||
|
user: readonly(user), |
||||||
|
isCurrentUser: readonly(isCurrentUser), |
||||||
|
groupInfo: readonly(groupInfo), |
||||||
|
isGroup: readonly(isGroup), |
||||||
|
loadGroup, |
||||||
|
loadUser, |
||||||
|
isLoading, |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,112 @@ |
|||||||
|
<template> |
||||||
|
<div class="invite-friends-container invite-friends"> |
||||||
|
<div class="invite-friends-header"> |
||||||
|
<h2>{{ t('Invite Friends to Group') }}</h2> |
||||||
|
</div> |
||||||
|
<div class="invite-friends-body"> |
||||||
|
<div class="friends-list"> |
||||||
|
<div class="list-header"> |
||||||
|
<h3>{{ t('Available Friends') }}</h3> |
||||||
|
</div> |
||||||
|
<div class="list-content"> |
||||||
|
<div class="friend-entry" v-for="friend in availableFriends" :key="friend.id"> |
||||||
|
<div class="friend-info"> |
||||||
|
<img :src="friend.avatar" alt="avatar" class="friend-avatar" /> |
||||||
|
<span class="friend-name">{{ friend.name }}</span> |
||||||
|
</div> |
||||||
|
<button @click="selectFriend(friend)" class="invite-btn">+</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="selected-friends-list"> |
||||||
|
<div class="list-header"> |
||||||
|
<h3>{{ t('Selected Friends') }}</h3> |
||||||
|
</div> |
||||||
|
<div class="list-content"> |
||||||
|
<div class="friend-entry" v-for="friend in selectedFriends" :key="friend.id"> |
||||||
|
<div class="friend-info"> |
||||||
|
<img :src="friend.avatar" alt="avatar" class="friend-avatar" /> |
||||||
|
<span class="friend-name">{{ friend.name }}</span> |
||||||
|
</div> |
||||||
|
<button @click="removeFriend(friend)" class="remove-btn">-</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="invite-friends-footer"> |
||||||
|
<button @click="sendInvitations" class="send-invites-btn">{{ t('Send Invitations') }}</button> |
||||||
|
</div> |
||||||
|
<div class="invited-friends-list mt-4"> |
||||||
|
<div class="list-header"> |
||||||
|
<h3>{{ t('Users Already Invited') }}</h3> |
||||||
|
</div> |
||||||
|
<div class="invited-users-grid mt-4"> |
||||||
|
<div class="user-card" v-for="user in invitedFriends" :key="user.id"> |
||||||
|
<img :src="user.avatar" alt="avatar" class="user-avatar" /> |
||||||
|
<span class="user-name">{{ user.name }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { inject, onMounted, ref, computed} from "vue" |
||||||
|
import { useI18n } from 'vue-i18n' |
||||||
|
import { useRoute } from 'vue-router' |
||||||
|
import { useStore } from "vuex" |
||||||
|
import axios from "axios" |
||||||
|
|
||||||
|
const { t } = useI18n() |
||||||
|
const route = useRoute() |
||||||
|
const store = useStore() |
||||||
|
const currentUser = computed(() => store.getters["security/getUser"]) |
||||||
|
const availableFriends = ref([]) |
||||||
|
const selectedFriends = ref([]) |
||||||
|
const invitedFriends = ref([]) |
||||||
|
const selectFriend = (friend) => { |
||||||
|
availableFriends.value = availableFriends.value.filter((f) => f.id !== friend.id) |
||||||
|
selectedFriends.value.push(friend) |
||||||
|
} |
||||||
|
const removeFriend = (friend) => { |
||||||
|
selectedFriends.value = selectedFriends.value.filter((f) => f.id !== friend.id) |
||||||
|
availableFriends.value.push(friend) |
||||||
|
} |
||||||
|
const loadAvailableFriends = async () => { |
||||||
|
const groupId = route.params.group_id |
||||||
|
const userId = currentUser.value.id |
||||||
|
try { |
||||||
|
const response = await axios.get(`/social-network/invite-friends/${userId}/${groupId}`) |
||||||
|
availableFriends.value = response.data.friends |
||||||
|
} catch (error) { |
||||||
|
console.error('Error loading available friends:', error) |
||||||
|
} |
||||||
|
} |
||||||
|
onMounted(() => { |
||||||
|
loadAvailableFriends() |
||||||
|
loadInvitedFriends() |
||||||
|
}) |
||||||
|
const sendInvitations = async () => { |
||||||
|
const groupId = route.params.group_id |
||||||
|
const userIds = selectedFriends.value.map(friend => friend.id) |
||||||
|
try { |
||||||
|
await axios.post(`/social-network/add-users-to-group/${groupId}`, { |
||||||
|
userIds, |
||||||
|
}) |
||||||
|
console.log('Users added to group successfully!') |
||||||
|
selectedFriends.value = [] |
||||||
|
loadInvitedFriends() |
||||||
|
} catch (error) { |
||||||
|
console.error('Error adding users to group:', error) |
||||||
|
} |
||||||
|
} |
||||||
|
const loadInvitedFriends = async () => { |
||||||
|
const groupId = route.params.group_id |
||||||
|
try { |
||||||
|
const response = await axios.get(`/social-network/group/${groupId}/invited-users`) |
||||||
|
invitedFriends.value = response.data.invitedUsers |
||||||
|
} catch (error) { |
||||||
|
console.error('Error loading invited friends:', error) |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
@ -1,642 +1,205 @@ |
|||||||
<template> |
<template> |
||||||
<Toolbar> |
<div class="p-grid p-nogutter social-groups"> |
||||||
<template v-slot:right> |
<div class="p-col-12"> |
||||||
<v-btn |
<div class="p-d-flex p-jc-between p-ai-center p-mb-4"> |
||||||
icon |
<h1>Social groups</h1> |
||||||
tile |
<Button label="Create a social group" icon="pi pi-plus" class="create-group-button" @click="showCreateGroupDialog = true" /> |
||||||
@click="composeHandler" |
|
||||||
> |
|
||||||
<v-icon icon="mdi-email-plus-outline" /> |
|
||||||
</v-btn> |
|
||||||
|
|
||||||
<v-btn |
|
||||||
:loading="isLoading" |
|
||||||
icon |
|
||||||
tile |
|
||||||
@click="reloadHandler" |
|
||||||
> |
|
||||||
<v-icon icon="mdi-refresh" /> |
|
||||||
</v-btn> |
|
||||||
|
|
||||||
<v-btn |
|
||||||
:class="[!selectedItems || !selectedItems.length ? 'hidden' : '']" |
|
||||||
icon |
|
||||||
tile |
|
||||||
@click="confirmDeleteMultiple" |
|
||||||
> |
|
||||||
<v-icon icon="mdi-delete" /> |
|
||||||
</v-btn> |
|
||||||
<!-- :disabled="!selectedItems || !selectedItems.length"--> |
|
||||||
<v-btn |
|
||||||
:class="[!selectedItems || !selectedItems.length ? 'hidden' : '']" |
|
||||||
icon |
|
||||||
tile |
|
||||||
@click="markAsUnReadMultiple" |
|
||||||
> |
|
||||||
<v-icon icon="mdi-email" /> |
|
||||||
</v-btn> |
|
||||||
|
|
||||||
<v-btn |
|
||||||
:class="[!selectedItems || !selectedItems.length ? 'hidden' : '']" |
|
||||||
icon |
|
||||||
tile |
|
||||||
> |
|
||||||
<v-icon icon="mdi-email-open" /> |
|
||||||
</v-btn> |
|
||||||
</template> |
|
||||||
</Toolbar> |
|
||||||
|
|
||||||
<div class="flex flex-row pt-2"> |
|
||||||
<div class="w-1/5"> |
|
||||||
<v-card |
|
||||||
max-width="300" |
|
||||||
tile |
|
||||||
> |
|
||||||
<v-list dense> |
|
||||||
<!-- v-model="selectedItem"--> |
|
||||||
<v-list-item-group color="primary"> |
|
||||||
<v-list-item @click="goToInbox"> |
|
||||||
<v-list-item-icon> |
|
||||||
<v-icon icon="mdi-inbox"></v-icon> |
|
||||||
</v-list-item-icon> |
|
||||||
<v-list-item-content> |
|
||||||
<v-list-item-title>Inbox</v-list-item-title> |
|
||||||
</v-list-item-content> |
|
||||||
</v-list-item> |
|
||||||
|
|
||||||
<v-list-item @click="goToSent"> |
|
||||||
<v-list-item-icon> |
|
||||||
<v-icon icon="mdi-send-outline"></v-icon> |
|
||||||
</v-list-item-icon> |
|
||||||
<v-list-item-content> |
|
||||||
<v-list-item-title>Sent</v-list-item-title> |
|
||||||
</v-list-item-content> |
|
||||||
</v-list-item> |
|
||||||
|
|
||||||
<v-list-item @click="goToUnread"> |
|
||||||
<v-list-item-icon> |
|
||||||
<v-icon icon="mdi-email-outline"></v-icon> |
|
||||||
</v-list-item-icon> |
|
||||||
<v-list-item-content> |
|
||||||
<v-list-item-title>Unread</v-list-item-title> |
|
||||||
</v-list-item-content> |
|
||||||
</v-list-item> |
|
||||||
|
|
||||||
<v-list-item |
|
||||||
v-for="(tag, i) in tags" |
|
||||||
:key="i" |
|
||||||
@click="goToTag(tag)" |
|
||||||
> |
|
||||||
<v-list-item-icon> |
|
||||||
<v-icon icon="mdi-label-outline"></v-icon> |
|
||||||
</v-list-item-icon> |
|
||||||
<v-list-item-content> |
|
||||||
<v-list-item-title v-text="tag.tag"></v-list-item-title> |
|
||||||
</v-list-item-content> |
|
||||||
</v-list-item> |
|
||||||
</v-list-item-group> |
|
||||||
</v-list> |
|
||||||
</v-card> |
|
||||||
</div> |
</div> |
||||||
<div class="w-4/5 pl-4"> |
<TabView class="social-group-tabs"> |
||||||
<div class="text-h4 q-mb-md">{{ title }}</div> |
<TabPanel header="Newest" headerClass="tab-header" :class="{ 'active-tab': activeTab === 'Newest' }"> |
||||||
<DataTable |
<div class="group-list"> |
||||||
v-model:filters="filters" |
<div class="group-item" v-for="group in newestGroups" :key="group['@id']"> |
||||||
v-model:selection="selectedItems" |
<i class="mdi mdi-account-group-outline group-icon"></i> |
||||||
:globalFilterFields="['title', 'sendDate']" |
<div class="group-details"> |
||||||
:lazy="true" |
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ group.title }}</a> |
||||||
:loading="isLoading" |
<div class="group-info"> |
||||||
:paginator="true" |
<span class="group-member-count">{{ group.memberCount }} Member</span> |
||||||
:rows="10" |
<span class="group-description">{{ group.description }}</span> |
||||||
:rowsPerPageOptions="[5, 10, 20, 50]" |
|
||||||
:totalRecords="totalItems" |
|
||||||
:value="items" |
|
||||||
class="p-datatable-sm" |
|
||||||
currentPageReportTemplate="Showing {first} to {last} of {totalRecords}" |
|
||||||
dataKey="id" |
|
||||||
filterDisplay="menu" |
|
||||||
paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown" |
|
||||||
responsiveLayout="scroll" |
|
||||||
sortBy="sendDate" |
|
||||||
sortOrder="asc" |
|
||||||
@page="onPage($event)" |
|
||||||
@sort="sortingChanged($event)" |
|
||||||
> |
|
||||||
<Column |
|
||||||
:exportable="false" |
|
||||||
selectionMode="multiple" |
|
||||||
style="width: 3rem" |
|
||||||
></Column> |
|
||||||
|
|
||||||
<Column |
|
||||||
:header="$t('From')" |
|
||||||
:sortable="false" |
|
||||||
field="sender" |
|
||||||
> |
|
||||||
<template #body="slotProps"> |
|
||||||
<q-avatar size="40px"> |
|
||||||
<img :src="slotProps.data.sender.illustrationUrl + '?w=80&h=80&fit=crop'" /> |
|
||||||
</q-avatar> |
|
||||||
|
|
||||||
<a |
|
||||||
v-if="slotProps.data" |
|
||||||
:class="[true === slotProps.data.read ? 'font-normal' : 'font-semibold']" |
|
||||||
class="cursor-pointer" |
|
||||||
@click="showHandler(slotProps.data)" |
|
||||||
> |
|
||||||
{{ slotProps.data.sender.username }} |
|
||||||
</a> |
|
||||||
</template> |
|
||||||
</Column> |
|
||||||
|
|
||||||
<Column |
|
||||||
:header="$t('Title')" |
|
||||||
:sortable="false" |
|
||||||
field="title" |
|
||||||
> |
|
||||||
<template #body="slotProps"> |
|
||||||
<a |
|
||||||
v-if="slotProps.data" |
|
||||||
class="cursor-pointer" |
|
||||||
v-bind:class="{ 'font-semibold': !slotProps.data.read }" |
|
||||||
@click="showHandler(slotProps.data)" |
|
||||||
> |
|
||||||
{{ slotProps.data.title }} |
|
||||||
</a> |
|
||||||
|
|
||||||
<div class="flex flex-row"> |
|
||||||
<v-chip v-for="tag in slotProps.data.tags"> |
|
||||||
{{ tag.tag }} |
|
||||||
</v-chip> |
|
||||||
</div> |
</div> |
||||||
</template> |
|
||||||
|
|
||||||
<!-- <template #filter="{filterModel}">--> |
|
||||||
<!-- <InputText type="text" v-model="filterModel.value" class="p-column-filter" placeholder="Search by name"/>--> |
|
||||||
<!-- </template>--> |
|
||||||
<!-- --> |
|
||||||
|
|
||||||
<!-- <template #filter="{filterModel}">--> |
|
||||||
<!-- <InputText type="text" v-model="filterModel.value" class="p-column-filter" placeholder="Search by title"/>--> |
|
||||||
<!-- </template>--> |
|
||||||
<!-- <template #filterclear="{filterCallback}">--> |
|
||||||
<!-- <Button type="button" icon="pi pi-times" @click="filterCallback()" class="p-button-secondary"></Button>--> |
|
||||||
<!-- </template>--> |
|
||||||
<!-- <template #filterapply="{filterCallback}">--> |
|
||||||
<!-- <Button type="button" icon="pi pi-check" @click="filterCallback()" class="p-button-success"></Button>--> |
|
||||||
<!-- </template>--> |
|
||||||
</Column> |
|
||||||
|
|
||||||
<Column |
|
||||||
:header="$t('Send date')" |
|
||||||
:sortable="true" |
|
||||||
field="sendDate" |
|
||||||
> |
|
||||||
<template #body="slotProps"> |
|
||||||
{{ relativeDatetime(slotProps.data.sendDate) }} |
|
||||||
</template> |
|
||||||
</Column> |
|
||||||
|
|
||||||
<Column :exportable="false"> |
|
||||||
<template #body="slotProps"> |
|
||||||
<div class="flex flex-row gap-2"> |
|
||||||
<v-btn |
|
||||||
icon |
|
||||||
tile |
|
||||||
@click="confirmDeleteItem(slotProps.data)" |
|
||||||
> |
|
||||||
<v-icon icon="mdi-delete" /> |
|
||||||
</v-btn> |
|
||||||
</div> |
</div> |
||||||
</template> |
|
||||||
</Column> |
|
||||||
</DataTable> |
|
||||||
</div> |
</div> |
||||||
</div> |
</div> |
||||||
|
</TabPanel> |
||||||
<!-- Dialogs--> |
<TabPanel header="Popular" headerClass="tab-header" :class="{ 'active-tab': activeTab === 'Popular' }"> |
||||||
|
<div class="group-list"> |
||||||
<Dialog |
<div class="group-item" v-for="group in popularGroups" :key="group['@id']"> |
||||||
v-model:visible="itemDialog" |
<i class="mdi mdi-account-group-outline group-icon"></i> |
||||||
:header="$t('New folder')" |
<div class="group-details"> |
||||||
:modal="true" |
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ group.title }}</a> |
||||||
:style="{ width: '450px' }" |
<div class="group-info"> |
||||||
class="p-fluid" |
<span class="group-member-count">{{ group.memberCount }} Member</span> |
||||||
> |
<span class="group-description">{{ group.description }}</span> |
||||||
<div class="p-field"> |
</div> |
||||||
<label for="title">{{ $t("Name") }}</label> |
</div> |
||||||
<InputText |
</div> |
||||||
id="title" |
</div> </TabPanel> |
||||||
v-model.trim="item.title" |
<TabPanel header="My groups" headerClass="tab-header" :class="{ 'active-tab': activeTab === 'My groups' }"> |
||||||
:class="{ 'p-invalid': submitted && !item.title }" |
<div class="group-list"> |
||||||
autocomplete="off" |
<div class="group-item" v-for="group in myGroups" :key="group['@id']"> |
||||||
autofocus |
<i class="mdi mdi-account-group-outline group-icon"></i> |
||||||
required="true" |
<div class="group-details"> |
||||||
/> |
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ group.title }}</a> |
||||||
<small |
<div class="group-info"> |
||||||
v-if="submitted && !item.title" |
<span class="group-member-count">{{ group.memberCount }} Member</span> |
||||||
class="p-error" |
<span class="group-description">{{ group.description }}</span> |
||||||
>$t('Title is required')</small |
</div> |
||||||
> |
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</TabPanel> |
||||||
|
</TabView> |
||||||
|
</div> |
||||||
</div> |
</div> |
||||||
|
|
||||||
<template #footer> |
<Dialog header="Add" :visible="showCreateGroupDialog" :modal="true" :closable="true" @hide="showCreateGroupDialog = false"> |
||||||
<Button |
<form @submit.prevent="createGroup"> |
||||||
class="p-button-text" |
<div class="p-fluid"> |
||||||
icon="pi pi-times" |
<BaseInputTextWithVuelidate |
||||||
label="Cancel" |
v-model="groupForm.name" |
||||||
@click="hideDialog" |
label="Name*" |
||||||
|
:vuelidate-property="v$.groupForm.name" |
||||||
/> |
/> |
||||||
<Button |
|
||||||
class="p-button-text" |
<BaseInputTextWithVuelidate |
||||||
icon="pi pi-check" |
v-model="groupForm.description" |
||||||
label="Save" |
label="Description" |
||||||
@click="saveItem" |
:vuelidate-property="v$.groupForm.description" |
||||||
|
as="textarea" |
||||||
|
rows="3" |
||||||
/> |
/> |
||||||
</template> |
|
||||||
</Dialog> |
|
||||||
|
|
||||||
<Dialog |
<BaseInputTextWithVuelidate |
||||||
v-model:visible="deleteItemDialog" |
v-model="groupForm.url" |
||||||
:modal="true" |
label="URL" |
||||||
:style="{ width: '450px' }" |
:vuelidate-property="v$.groupForm.url" |
||||||
header="Confirm" |
|
||||||
> |
|
||||||
<div class="confirmation-content"> |
|
||||||
<i |
|
||||||
class="pi pi-exclamation-triangle p-mr-3" |
|
||||||
style="font-size: 2rem" |
|
||||||
/> |
/> |
||||||
<span v-if="item" |
<BaseFileUpload |
||||||
>Are you sure you want to delete <b>{{ item.title }}</b |
:label="t('Add a picture')" |
||||||
>?</span |
accept="image" |
||||||
> |
size="small" |
||||||
</div> |
@file-selected="selectedFile = $event" |
||||||
<template #footer> |
|
||||||
<Button |
|
||||||
class="p-button-text" |
|
||||||
icon="pi pi-times" |
|
||||||
label="No" |
|
||||||
@click="deleteItemDialog = false" |
|
||||||
/> |
/> |
||||||
<Button |
<div class="p-field mt-2"> |
||||||
class="p-button-text" |
<label for="groupPermissions">Group Permissions</label> |
||||||
icon="pi pi-check" |
<Dropdown id="groupPermissions" v-model="groupForm.permissions" :options="permissionsOptions" optionLabel="label" placeholder="Select Permission" /> |
||||||
label="Yes" |
</div> |
||||||
@click="deleteItemButton" |
<div class="p-field-checkbox mt-2"> |
||||||
|
<BaseCheckbox |
||||||
|
id="leaveGroup" |
||||||
|
v-model="groupForm.allowLeave" |
||||||
|
:label="$t('Allow members to leave group')" |
||||||
|
name="leaveGroup" |
||||||
/> |
/> |
||||||
</template> |
</div> |
||||||
</Dialog> |
|
||||||
|
|
||||||
<Dialog |
|
||||||
v-model:visible="deleteMultipleDialog" |
|
||||||
:modal="true" |
|
||||||
:style="{ width: '450px' }" |
|
||||||
header="Confirm" |
|
||||||
> |
|
||||||
<div class="confirmation-content"> |
|
||||||
<i |
|
||||||
class="pi pi-exclamation-triangle p-mr-3" |
|
||||||
style="font-size: 2rem" |
|
||||||
/> |
|
||||||
<span v-if="item">Are you sure you want to delete the selected items?</span> |
|
||||||
</div> |
</div> |
||||||
<template #footer> |
<Button label="Add" icon="pi pi-check" class="p-button-rounded p-button-text" @click="createGroup" /> |
||||||
<Button |
</form> |
||||||
class="p-button-text" |
|
||||||
icon="pi pi-times" |
|
||||||
label="No" |
|
||||||
@click="deleteMultipleDialog = false" |
|
||||||
/> |
|
||||||
<Button |
|
||||||
class="p-button-text" |
|
||||||
icon="pi pi-check" |
|
||||||
label="Yes" |
|
||||||
@click="deleteMultipleItems" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</Dialog> |
</Dialog> |
||||||
</template> |
</template> |
||||||
|
|
||||||
<script> |
<script setup> |
||||||
import { mapActions, mapGetters, useStore } from "vuex" |
import Button from 'primevue/button' |
||||||
import { mapFields } from "vuex-map-fields" |
import TabView from 'primevue/tabview' |
||||||
import ListMixin from "../../mixins/ListMixin" |
import TabPanel from 'primevue/tabpanel' |
||||||
import ActionCell from "../../components/ActionCell.vue" |
import { ref, onMounted } from 'vue' |
||||||
import Toolbar from "../../components/Toolbar.vue" |
import useVuelidate from '@vuelidate/core' |
||||||
import ResourceIcon from "../../components/documents/ResourceIcon.vue" |
import { required } from '@vuelidate/validators' |
||||||
import ResourceFileLink from "../../components/documents/ResourceFileLink.vue" |
import BaseInputTextWithVuelidate from "../../components/basecomponents/BaseInputTextWithVuelidate.vue" |
||||||
import DataFilter from "../../components/DataFilter" |
import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue" |
||||||
import DocumentsFilterForm from "../../components/documents/Filter" |
import BaseCheckbox from "../../components/basecomponents/BaseCheckbox.vue" |
||||||
import { ref } from "vue" |
import { useI18n } from "vue-i18n" |
||||||
import axios from "axios" |
import axios from "axios" |
||||||
import { ENTRYPOINT } from "../../config/entrypoint" |
import { ENTRYPOINT } from "../../config/entrypoint" |
||||||
import { RESOURCE_LINK_PUBLISHED } from "../../components/resource_links/visibility" |
|
||||||
import { MESSAGE_TYPE_INBOX, MESSAGE_TYPE_OUTBOX } from "../../components/message/constants" |
|
||||||
import { useFormatDate } from "../../composables/formatDate" |
|
||||||
import { useI18n } from "vue-i18n" |
|
||||||
|
|
||||||
export default { |
|
||||||
name: "UserGroupList", |
|
||||||
servicePrefix: "usergroups", |
|
||||||
components: { |
|
||||||
Toolbar, |
|
||||||
ActionCell, |
|
||||||
ResourceIcon, |
|
||||||
ResourceFileLink, |
|
||||||
DocumentsFilterForm, |
|
||||||
DataFilter, |
|
||||||
}, |
|
||||||
mixins: [ListMixin], |
|
||||||
setup() { |
|
||||||
const {t} = useI18n() |
const {t} = useI18n() |
||||||
const { relativeDatetime } = useFormatDate() |
const newestGroups = ref([]) |
||||||
|
const popularGroups = ref([]) |
||||||
const store = useStore() |
const myGroups = ref([]) |
||||||
const filters = ref([]) |
const activeTab = ref('Newest') |
||||||
const filtersSent = ref([]) |
const showCreateGroupDialog = ref(false) |
||||||
const user = store.getters["security/getUser"] |
const selectedFile = ref(null) |
||||||
const tags = ref([]) |
|
||||||
const title = ref("Inbox") |
const groupForm = ref({ |
||||||
|
name: '', |
||||||
filtersSent.value = { |
description: '', |
||||||
msgType: MESSAGE_TYPE_OUTBOX, |
url: '', |
||||||
sender: user.id, |
picture: null, |
||||||
|
}) |
||||||
|
const v$ = useVuelidate({ |
||||||
|
groupForm: { |
||||||
|
name: { required }, |
||||||
|
description: {}, |
||||||
|
url: {}, |
||||||
} |
} |
||||||
|
}, { groupForm }) |
||||||
// inbox |
const permissionsOptions = [ |
||||||
filters.value = { |
{ label: 'Open', value: '1' }, |
||||||
msgType: MESSAGE_TYPE_INBOX, |
{ label: 'Closed', value: '2' }, |
||||||
userReceiver: user.id, |
] |
||||||
|
const createGroup = async () => { |
||||||
|
v$.value.$touch() |
||||||
|
if (!v$.value.$invalid) { |
||||||
|
const groupData = { |
||||||
|
title: groupForm.value.name, |
||||||
|
description: groupForm.value.description, |
||||||
|
url: groupForm.value.url, |
||||||
|
visibility: groupForm.value.permissions.value, |
||||||
|
allowMembersToLeaveGroup: groupForm.value.allowLeave ? 1 : 0, |
||||||
|
groupType: 1, |
||||||
} |
} |
||||||
|
try { |
||||||
// Get user tags. |
const response = await axios.post(ENTRYPOINT + 'usergroups', groupData, { |
||||||
axios |
headers: { |
||||||
.get(ENTRYPOINT + "message_tags", { |
'Content-Type': 'application/json', |
||||||
params: { |
|
||||||
user: user["@id"], |
|
||||||
}, |
}, |
||||||
}) |
}) |
||||||
.then((response) => { |
/*if (selectedFile.value && response.data && response.data.id) { |
||||||
let data = response.data |
const formData = new FormData() |
||||||
tags.value = data["hydra:member"] |
formData.append('picture', selectedFile.value) |
||||||
|
await axios.post(`/social-network/upload-group-picture/${response.data.id}`, formData, { |
||||||
|
headers: { |
||||||
|
'Content-Type': 'multipart/form-data', |
||||||
|
}, |
||||||
}) |
}) |
||||||
|
}*/ |
||||||
|
|
||||||
function goToInbox() { |
showCreateGroupDialog.value = false |
||||||
title.value = "Inbox" |
await updateGroupsList() |
||||||
filters.value = { |
} catch (error) { |
||||||
msgType: MESSAGE_TYPE_INBOX, |
console.error('Failed to create group or upload picture:', error.response.data) |
||||||
userReceiver: user.id, |
|
||||||
} |
|
||||||
store.dispatch("message/resetList") |
|
||||||
store.dispatch("message/fetchAll", filters.value) |
|
||||||
} |
|
||||||
|
|
||||||
function goToUnread() { |
|
||||||
title.value = "Unread" |
|
||||||
filters.value = { |
|
||||||
msgType: MESSAGE_TYPE_INBOX, |
|
||||||
userReceiver: user.id, |
|
||||||
read: false, |
|
||||||
} |
} |
||||||
store.dispatch("message/resetList") |
|
||||||
store.dispatch("message/fetchAll", filters.value) |
|
||||||
} |
} |
||||||
|
|
||||||
function goToSent() { |
|
||||||
title.value = "Sent" |
|
||||||
filters.value = { |
|
||||||
msgType: MESSAGE_TYPE_OUTBOX, |
|
||||||
sender: user.id, |
|
||||||
} |
} |
||||||
store.dispatch("message/resetList") |
const fetchGroups = async (endpoint) => { |
||||||
store.dispatch("message/fetchAll", filters.value) |
try { |
||||||
|
const response = await fetch(ENTRYPOINT + `${endpoint}`) |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error('Failed to fetch groups') |
||||||
} |
} |
||||||
|
const data = await response.json() |
||||||
|
console.log('hidra menber ::: ', data['hydra:member']) |
||||||
|
|
||||||
function goToTag(tag) { |
return data['hydra:member'] |
||||||
title.value = tag.tag |
} catch (error) { |
||||||
filters.value = { |
console.error(error) |
||||||
msgType: MESSAGE_TYPE_INBOX, |
return [] |
||||||
userReceiver: user.id, |
|
||||||
tags: [tag], |
|
||||||
} |
} |
||||||
store.dispatch("message/resetList") |
|
||||||
store.dispatch("message/fetchAll", filters.value) |
|
||||||
} |
} |
||||||
|
const updateGroupsList = async () => { |
||||||
return { |
newestGroups.value = await fetchGroups('usergroup/list/newest') |
||||||
goToInbox, |
popularGroups.value = await fetchGroups('usergroup/list/popular') |
||||||
goToSent, |
myGroups.value = await fetchGroups('usergroup/list/my') |
||||||
goToTag, |
|
||||||
goToUnread, |
|
||||||
tags, |
|
||||||
filters, |
|
||||||
title, |
|
||||||
relativeDatetime, |
|
||||||
} |
|
||||||
}, |
|
||||||
data() { |
|
||||||
const {t} = useI18n() |
|
||||||
|
|
||||||
return { |
|
||||||
columns: [ |
|
||||||
{ label: t("Title"), field: "title", name: "title", sortable: true }, |
|
||||||
{ label: t("Sender"), field: "sender", name: "userSender", sortable: true }, |
|
||||||
{ label: t("Modified"), field: "sendDate", name: "updatedAt", sortable: true }, |
|
||||||
{ label: t("Actions"), name: "action", sortable: false }, |
|
||||||
], |
|
||||||
pageOptions: [10, 20, 50, t("All")], |
|
||||||
selected: [], |
|
||||||
isBusy: false, |
|
||||||
options: { |
|
||||||
sortBy: "sendDate", |
|
||||||
sortDesc: "asc", |
|
||||||
}, |
|
||||||
selectedItems: [], |
|
||||||
// prime vue |
|
||||||
itemDialog: false, |
|
||||||
deleteItemDialog: false, |
|
||||||
deleteMultipleDialog: false, |
|
||||||
item: {}, |
|
||||||
submitted: false, |
|
||||||
} |
} |
||||||
}, |
|
||||||
mounted() { |
|
||||||
this.onUpdateOptions(this.options) |
|
||||||
}, |
|
||||||
computed: { |
|
||||||
// From crud.js list function |
|
||||||
...mapGetters("resourcenode", { |
|
||||||
resourceNode: "getResourceNode", |
|
||||||
}), |
|
||||||
...mapGetters({ |
|
||||||
isAuthenticated: "security/isAuthenticated", |
|
||||||
isAdmin: "security/isAdmin", |
|
||||||
currentUser: "security/getUser", |
|
||||||
}), |
|
||||||
|
|
||||||
...mapGetters("message", { |
|
||||||
items: "list", |
|
||||||
}), |
|
||||||
|
|
||||||
//...getters |
|
||||||
|
|
||||||
// From ListMixin |
|
||||||
...mapFields("message", { |
|
||||||
deletedItem: "deleted", |
|
||||||
error: "error", |
|
||||||
isLoading: "isLoading", |
|
||||||
resetList: "resetList", |
|
||||||
totalItems: "totalItems", |
|
||||||
view: "view", |
|
||||||
}), |
|
||||||
}, |
|
||||||
methods: { |
|
||||||
composeHandler() { |
|
||||||
let folderParams = this.$route.query |
|
||||||
this.$router.push({ name: `${this.$options.servicePrefix}Create`, query: folderParams }) |
|
||||||
}, |
|
||||||
|
|
||||||
// prime |
|
||||||
onPage(event) { |
|
||||||
this.options.itemsPerPage = event.rows |
|
||||||
this.options.page = event.page + 1 |
|
||||||
this.options.sortBy = event.sortField |
|
||||||
this.options.sortDesc = event.sortOrder === -1 |
|
||||||
|
|
||||||
this.onUpdateOptions(this.options) |
|
||||||
}, |
|
||||||
sortingChanged(event) { |
|
||||||
console.log("sortingChanged") |
|
||||||
console.log(event) |
|
||||||
this.options.sortBy = event.sortField |
|
||||||
this.options.sortDesc = event.sortOrder === -1 |
|
||||||
|
|
||||||
this.onUpdateOptions(this.options) |
|
||||||
// ctx.sortBy ==> Field key for sorting by (or null for no sorting) |
|
||||||
// ctx.sortDesc ==> true if sorting descending, false otherwise |
|
||||||
}, |
|
||||||
openNew() { |
|
||||||
this.item = {} |
|
||||||
this.submitted = false |
|
||||||
this.itemDialog = true |
|
||||||
}, |
|
||||||
hideDialog() { |
|
||||||
this.itemDialog = false |
|
||||||
this.submitted = false |
|
||||||
}, |
|
||||||
saveItem() { |
|
||||||
this.submitted = true |
|
||||||
|
|
||||||
if (this.item.title.trim()) { |
|
||||||
if (this.item.id) { |
|
||||||
} else { |
|
||||||
//this.products.push(this.product); |
|
||||||
this.item.filetype = "folder" |
|
||||||
this.item.parentResourceNodeId = this.$route.params.node |
|
||||||
this.item.resourceLinkList = JSON.stringify([ |
|
||||||
{ |
|
||||||
gid: this.$route.query.gid, |
|
||||||
sid: this.$route.query.sid, |
|
||||||
cid: this.$route.query.cid, |
|
||||||
visibility: RESOURCE_LINK_PUBLISHED, // visible by default |
|
||||||
}, |
|
||||||
]) |
|
||||||
|
|
||||||
this.create(this.item) |
const extractGroupId = (group) => { |
||||||
this.showMessage("Saved") |
const match = group['@id'].match(/\/api\/usergroup\/(\d+)/) |
||||||
|
return match ? match[1] : null |
||||||
} |
} |
||||||
|
const redirectToGroupDetails = (groupId) => { |
||||||
this.itemDialog = false |
router.push({ name: 'UserGroupShow', params: { group_id: groupId } }) |
||||||
this.item = {} |
|
||||||
} |
} |
||||||
}, |
onMounted(async () => { |
||||||
editItem(item) { |
await updateGroupsList() |
||||||
this.item = { ...item } |
|
||||||
this.itemDialog = true |
|
||||||
}, |
|
||||||
confirmDeleteItem(item) { |
|
||||||
this.item = item |
|
||||||
this.deleteItemDialog = true |
|
||||||
}, |
|
||||||
confirmDeleteMultiple() { |
|
||||||
this.deleteMultipleDialog = true |
|
||||||
}, |
|
||||||
markAsReadMultiple() { |
|
||||||
console.log("markAsReadMultiple") |
|
||||||
this.selectedItems.forEach((message) => { |
|
||||||
message.read = true |
|
||||||
this.update(message) |
|
||||||
}) |
|
||||||
this.selectedItems = null |
|
||||||
this.resetList = true |
|
||||||
}, |
|
||||||
reloadHandler() { |
|
||||||
this.onUpdateOptions(this.options) |
|
||||||
}, |
|
||||||
markAsUnReadMultiple() { |
|
||||||
console.log("markAsUnReadMultiple") |
|
||||||
this.selectedItems.forEach((message) => { |
|
||||||
message.read = false |
|
||||||
this.update(message) |
|
||||||
}) |
}) |
||||||
this.selectedItems = null |
|
||||||
this.resetList = true |
|
||||||
//this.onUpdateOptions(this.options); |
|
||||||
}, |
|
||||||
deleteMultipleItems() { |
|
||||||
console.log("deleteMultipleItems") |
|
||||||
console.log(this.selectedItems) |
|
||||||
this.deleteMultipleAction(this.selectedItems) |
|
||||||
this.onRequest({ |
|
||||||
pagination: this.pagination, |
|
||||||
}) |
|
||||||
this.deleteMultipleDialog = false |
|
||||||
this.selectedItems = null |
|
||||||
//this.onUpdateOptions(this.options); |
|
||||||
}, |
|
||||||
deleteItemButton() { |
|
||||||
console.log("deleteItem") |
|
||||||
this.deleteItem(this.item) |
|
||||||
//this.items = this.items.filter(val => val.iid !== this.item.iid); |
|
||||||
this.deleteItemDialog = false |
|
||||||
this.item = {} |
|
||||||
this.onUpdateOptions(this.options) |
|
||||||
}, |
|
||||||
onRowSelected(items) { |
|
||||||
this.selected = items |
|
||||||
}, |
|
||||||
selectAllRows() { |
|
||||||
this.$refs.selectableTable.selectAllRows() |
|
||||||
}, |
|
||||||
clearSelected() { |
|
||||||
this.$refs.selectableTable.clearSelected() |
|
||||||
}, |
|
||||||
async deleteSelected() { |
|
||||||
console.log("deleteSelected") |
|
||||||
/*for (let i = 0; i < this.selected.length; i++) { |
|
||||||
let item = this.selected[i]; |
|
||||||
//this.deleteHandler(item); |
|
||||||
this.deleteItem(item); |
|
||||||
}*/ |
|
||||||
|
|
||||||
this.deleteMultipleAction(this.selected) |
|
||||||
this.onRequest({ |
|
||||||
pagination: this.pagination, |
|
||||||
}) |
|
||||||
}, |
|
||||||
//...actions, |
|
||||||
// From ListMixin |
|
||||||
...mapActions("message", { |
|
||||||
getPage: "fetchAll", |
|
||||||
create: "create", |
|
||||||
update: "update", |
|
||||||
deleteItem: "del", |
|
||||||
deleteMultipleAction: "delMultiple", |
|
||||||
}), |
|
||||||
...mapActions("resourcenode", { |
|
||||||
findResourceNode: "findResourceNode", |
|
||||||
}), |
|
||||||
}, |
|
||||||
} |
|
||||||
</script> |
</script> |
||||||
|
@ -0,0 +1,69 @@ |
|||||||
|
<template> |
||||||
|
<div class="search-container social-groups"> |
||||||
|
<div class="search-header"> |
||||||
|
<h2>{{ t('Results and feedback') }} {{ searchTerm }}</h2> |
||||||
|
<div class="p-inputgroup"> |
||||||
|
<BaseInputText |
||||||
|
v-model="searchTerm" |
||||||
|
placeholder="Search term..." |
||||||
|
class="search-term-input" |
||||||
|
label="Search term ..."/> |
||||||
|
<BaseButton |
||||||
|
label="Search" |
||||||
|
icon="pi pi-search" |
||||||
|
@click="performSearch" |
||||||
|
type="button"/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="p-grid search-results"> |
||||||
|
<div class="p-col-12 p-md-4" v-for="group in searchResults" :key="group.id"> |
||||||
|
<div class="group-card"> |
||||||
|
<div class="group-image"> |
||||||
|
<i v-if="!group.pictureUrl" class="pi pi-users large-icon"></i> |
||||||
|
<img v-else :src="group.pictureUrl" alt="Group" /> |
||||||
|
</div> |
||||||
|
<div class="group-details"> |
||||||
|
<h4 class="group-title">{{ group.title }}</h4> |
||||||
|
<p class="group-description">{{ group.description }}</p> |
||||||
|
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ t('See more') }}</a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { onMounted, ref } from "vue" |
||||||
|
import { useI18n } from 'vue-i18n' |
||||||
|
import BaseInputText from "../../components/basecomponents/BaseInputText.vue" |
||||||
|
import BaseButton from "../../components/basecomponents/BaseButton.vue" |
||||||
|
import axios from 'axios' |
||||||
|
import { useRoute } from "vue-router" |
||||||
|
|
||||||
|
const { t } = useI18n() |
||||||
|
const route = useRoute() |
||||||
|
const searchTerm = ref('') |
||||||
|
const searchResults = ref([]) |
||||||
|
onMounted(() => { |
||||||
|
if (route.query.q) { |
||||||
|
searchTerm.value = route.query.q |
||||||
|
performSearch() |
||||||
|
} |
||||||
|
}) |
||||||
|
const performSearch = async () => { |
||||||
|
try { |
||||||
|
const response = await axios.get('/api/usergroups/search', { |
||||||
|
params: { search: searchTerm.value }, |
||||||
|
}) |
||||||
|
searchResults.value = response.data['hydra:member'] |
||||||
|
} catch (error) { |
||||||
|
console.error('Error performing search:', error) |
||||||
|
searchResults.value = [] |
||||||
|
} |
||||||
|
} |
||||||
|
const extractGroupId = (group) => { |
||||||
|
const match = group['@id'].match(/\/api\/usergroup\/(\d+)/) |
||||||
|
return match ? match[1] : null |
||||||
|
} |
||||||
|
</script> |
@ -1,262 +1,40 @@ |
|||||||
<template> |
<template> |
||||||
<div v-if="item"> |
<div class="social-group-show"> |
||||||
<Toolbar :handle-delete="del"> |
<div class="group-header"> |
||||||
<template v-slot:right> |
<h1 class="group-title">mi grupo 0002</h1> |
||||||
<!-- <v-toolbar-title v-if="item">--> |
<p class="group-description">test</p> |
||||||
<!-- {{--> |
</div> |
||||||
<!-- `${$options.servicePrefix} ${item['@id']}`--> |
|
||||||
<!-- }}--> |
|
||||||
<!-- </v-toolbar-title>--> |
|
||||||
|
|
||||||
<v-btn |
|
||||||
:loading="isLoading" |
|
||||||
icon |
|
||||||
tile |
|
||||||
@click="reply" |
|
||||||
> |
|
||||||
<v-icon icon="mdi-reply" /> |
|
||||||
</v-btn> |
|
||||||
</template> |
|
||||||
</Toolbar> |
|
||||||
|
|
||||||
<VueMultiselect |
|
||||||
v-model="item.tags" |
|
||||||
:internal-search="false" |
|
||||||
:loading="isLoadingSelect" |
|
||||||
:multiple="true" |
|
||||||
:options="tags" |
|
||||||
:searchable="true" |
|
||||||
:taggable="true" |
|
||||||
label="tag" |
|
||||||
placeholder="Tags" |
|
||||||
tag-placeholder="Add this as new tag" |
|
||||||
track-by="id" |
|
||||||
@remove="removeTagFromMessage" |
|
||||||
@select="addTagToMessage" |
|
||||||
@tag="addTag" |
|
||||||
@search-change="asyncFind" |
|
||||||
/> |
|
||||||
|
|
||||||
<p class="text-lg"> |
|
||||||
From: |
|
||||||
<q-avatar size="32px"> |
|
||||||
<img :src="item['sender']['illustrationUrl'] + '?w=80&h=80&fit=crop'" /> |
|
||||||
</q-avatar> |
|
||||||
{{ item["sender"]["username"] }} |
|
||||||
</p> |
|
||||||
|
|
||||||
<p class="text-lg"> |
|
||||||
{{ relativeDatetime(item["sendDate"]) }} |
|
||||||
</p> |
|
||||||
|
|
||||||
<h3 class="text-lg">{{ item.title }}</h3> |
<ul class="tabs"> |
||||||
|
<li :class="{ active: activeTab === 'discussions' }" @click="activeTab = 'discussions'">Discussions</li> |
||||||
|
<li :class="{ active: activeTab === 'members' }" @click="activeTab = 'members'">Members</li> |
||||||
|
</ul> |
||||||
|
|
||||||
<div class="flex flex-row"> |
<div class="tab-content"> |
||||||
<div class="w-full"> |
<GroupDiscussions v-if="activeTab === 'discussions'" :group-id="groupId" /> |
||||||
<p v-html="item.content" /> |
<GroupMembers v-if="activeTab === 'members'" :group-id="groupId" /> |
||||||
</div> |
|
||||||
</div> |
</div> |
||||||
<Loading :visible="isLoading" /> |
|
||||||
</div> |
</div> |
||||||
</template> |
</template> |
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style> |
<script setup> |
||||||
|
import { ref, onMounted } from 'vue' |
||||||
<script> |
import { useRoute } from 'vue-router' |
||||||
import { mapActions, mapGetters, useStore } from "vuex" |
import axios from 'axios' |
||||||
import { mapFields } from "vuex-map-fields" |
import GroupDiscussions from "../../components/usergroup/GroupDiscussions.vue" |
||||||
import Loading from "../../components/Loading.vue" |
import GroupMembers from "../../components/usergroup/GroupMembers.vue" |
||||||
import ShowMixin from "../../mixins/ShowMixin" |
|
||||||
import Toolbar from "../../components/Toolbar.vue" |
|
||||||
import VueMultiselect from "vue-multiselect" |
|
||||||
import { ref } from "vue" |
|
||||||
import isEmpty from "lodash/isEmpty" |
|
||||||
import axios from "axios" |
|
||||||
import { ENTRYPOINT } from "../../config/entrypoint" |
|
||||||
import useVuelidate from "@vuelidate/core" |
|
||||||
import { useRoute, useRouter } from "vue-router" |
|
||||||
import NotificationMixin from "../../mixins/NotificationMixin" |
|
||||||
import { useFormatDate } from "../../composables/formatDate" |
|
||||||
|
|
||||||
const servicePrefix = "usergroups" |
|
||||||
|
|
||||||
export default { |
|
||||||
name: "UserGroupShow", |
|
||||||
components: { |
|
||||||
Loading, |
|
||||||
Toolbar, |
|
||||||
VueMultiselect, |
|
||||||
}, |
|
||||||
setup() { |
|
||||||
const tags = ref([]) |
|
||||||
const isLoadingSelect = ref(false) |
|
||||||
const store = useStore() |
|
||||||
const user = store.getters["security/getUser"] |
|
||||||
const find = store.getters["message/find"] |
|
||||||
const route = useRoute() |
const route = useRoute() |
||||||
const router = useRouter() |
const activeTab = ref('discussions') |
||||||
|
const groupId = ref(route.params.group_id) |
||||||
const { relativeDatetime } = useFormatDate() |
const group = ref(null) |
||||||
|
onMounted(async () => { |
||||||
let id = route.params.id |
if (groupId.value) { |
||||||
if (isEmpty(id)) { |
try { |
||||||
id = route.query.id |
const response = await axios.get(`/api/usergroup/${groupId.value}`) |
||||||
} |
group.value = response.data |
||||||
|
} catch (error) { |
||||||
console.log(id) |
console.error('Error fetching group details:', error) |
||||||
console.log(decodeURIComponent(id)) |
|
||||||
|
|
||||||
let item = find(decodeURIComponent(id)) |
|
||||||
|
|
||||||
// Change to read |
|
||||||
if (false === item.read) { |
|
||||||
axios |
|
||||||
.put(ENTRYPOINT + "messages/" + item.id, { |
|
||||||
read: true, |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
console.log(response) |
|
||||||
}) |
|
||||||
.catch(function (error) { |
|
||||||
console.log(error) |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
function addTag(newTag) { |
|
||||||
axios |
|
||||||
.post(ENTRYPOINT + "message_tags", { |
|
||||||
user: user["@id"], |
|
||||||
tag: newTag, |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
addTagToMessage(response.data) |
|
||||||
//this.showMessage('Added'); |
|
||||||
item.tags.push(response.data) |
|
||||||
console.log(response) |
|
||||||
isLoadingSelect.value = false |
|
||||||
}) |
|
||||||
.catch(function (error) { |
|
||||||
isLoadingSelect.value = false |
|
||||||
console.log(error) |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
function addTagToMessage(newTag) { |
|
||||||
console.log("addTagToMessage") |
|
||||||
let tagsToUpdate = [] |
|
||||||
item.tags.forEach((tagItem) => { |
|
||||||
tagsToUpdate.push(tagItem["@id"]) |
|
||||||
}) |
|
||||||
tagsToUpdate.push(newTag["@id"]) |
|
||||||
console.log(tagsToUpdate) |
|
||||||
|
|
||||||
axios |
|
||||||
.put(ENTRYPOINT + "messages/" + item.id, { |
|
||||||
tags: tagsToUpdate, |
|
||||||
}) |
}) |
||||||
.then((response) => { |
|
||||||
//this.showMessage('Added'); |
|
||||||
console.log(response) |
|
||||||
isLoadingSelect.value = false |
|
||||||
}) |
|
||||||
.catch(function (error) { |
|
||||||
isLoadingSelect.value = false |
|
||||||
console.log(error) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function removeTagFromMessage() { |
|
||||||
let tagsToUpdate = [] |
|
||||||
item.tags.forEach((tagItem) => { |
|
||||||
tagsToUpdate.push(tagItem["@id"]) |
|
||||||
}) |
|
||||||
|
|
||||||
axios |
|
||||||
.put(ENTRYPOINT + "messages/" + item.id, { |
|
||||||
tags: tagsToUpdate, |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
console.log(response) |
|
||||||
isLoadingSelect.value = false |
|
||||||
}) |
|
||||||
.catch(function (error) { |
|
||||||
isLoadingSelect.value = false |
|
||||||
console.log(error) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
axios |
|
||||||
.get(ENTRYPOINT + "message_tags", { |
|
||||||
params: { |
|
||||||
user: user["@id"], |
|
||||||
}, |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
isLoadingSelect.value = false |
|
||||||
let data = response.data |
|
||||||
tags.value = data["hydra:member"] |
|
||||||
}) |
|
||||||
|
|
||||||
function reply() { |
|
||||||
let params = route.query |
|
||||||
router.push({ name: `${servicePrefix}Reply`, query: params }) |
|
||||||
} |
|
||||||
|
|
||||||
function asyncFind(query) { |
|
||||||
if (query.toString().length < 3) { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
isLoadingSelect.value = true |
|
||||||
axios |
|
||||||
.get(ENTRYPOINT + "message_tags", { |
|
||||||
params: { |
|
||||||
user: user["@id"], |
|
||||||
}, |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
isLoadingSelect.value = false |
|
||||||
let data = response.data |
|
||||||
tags.value = data["hydra:member"] |
|
||||||
}) |
|
||||||
.catch(function (error) { |
|
||||||
isLoadingSelect.value = false |
|
||||||
console.log(error) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
v$: useVuelidate(), |
|
||||||
tags, |
|
||||||
isLoadingSelect, |
|
||||||
item, |
|
||||||
addTag, |
|
||||||
addTagToMessage, |
|
||||||
removeTagFromMessage, |
|
||||||
asyncFind, |
|
||||||
reply, |
|
||||||
relativeDatetime, |
|
||||||
} |
|
||||||
}, |
|
||||||
mixins: [ShowMixin, NotificationMixin], |
|
||||||
computed: { |
|
||||||
...mapFields("message", { |
|
||||||
isLoading: "isLoading", |
|
||||||
}), |
|
||||||
...mapGetters("message", ["find"]), |
|
||||||
...mapGetters({ |
|
||||||
isAuthenticated: "security/isAuthenticated", |
|
||||||
isAdmin: "security/isAdmin", |
|
||||||
currentUser: "security/getUser", |
|
||||||
}), |
|
||||||
}, |
|
||||||
methods: { |
|
||||||
...mapActions("message", { |
|
||||||
deleteItem: "del", |
|
||||||
reset: "resetShow", |
|
||||||
retrieve: "loadWithQuery", |
|
||||||
}), |
|
||||||
}, |
|
||||||
servicePrefix, |
|
||||||
} |
|
||||||
</script> |
</script> |
||||||
|
@ -0,0 +1,39 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\DataProvider; |
||||||
|
|
||||||
|
use ApiPlatform\Metadata\Operation; |
||||||
|
use ApiPlatform\State\ProviderInterface; |
||||||
|
use Chamilo\CoreBundle\Entity\Usergroup; |
||||||
|
use Doctrine\ORM\EntityManagerInterface; |
||||||
|
|
||||||
|
final class GroupMembersDataProvider implements ProviderInterface |
||||||
|
{ |
||||||
|
private EntityManagerInterface $entityManager; |
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager) |
||||||
|
{ |
||||||
|
$this->entityManager = $entityManager; |
||||||
|
} |
||||||
|
|
||||||
|
public function supports(Operation $operation, array $uriVariables = [], array $context = []): bool |
||||||
|
{ |
||||||
|
return Usergroup::class === $operation->getClass() && 'get_group_members' === $operation->getName(); |
||||||
|
} |
||||||
|
|
||||||
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable |
||||||
|
{ |
||||||
|
$groupId = $uriVariables['id'] ?? null; |
||||||
|
|
||||||
|
if (null === $groupId) { |
||||||
|
return []; |
||||||
|
} |
||||||
|
|
||||||
|
$usergroupRepository = $this->entityManager->getRepository(Usergroup::class); |
||||||
|
$users = $usergroupRepository->getUsersByGroup((int)$groupId); |
||||||
|
|
||||||
|
return $users; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
<?php |
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\DataProvider; |
||||||
|
|
||||||
|
use ApiPlatform\Metadata\Operation; |
||||||
|
use ApiPlatform\State\ProviderInterface; |
||||||
|
use Chamilo\CoreBundle\Entity\Message; |
||||||
|
use Chamilo\CoreBundle\Repository\MessageRepository; |
||||||
|
|
||||||
|
final class MessageByGroupDataProvider implements ProviderInterface |
||||||
|
{ |
||||||
|
private MessageRepository $messageRepository; |
||||||
|
|
||||||
|
public function __construct(MessageRepository $messageRepository) |
||||||
|
{ |
||||||
|
$this->messageRepository = $messageRepository; |
||||||
|
} |
||||||
|
|
||||||
|
public function supports(Operation $operation, array $uriVariables = [], array $context = []): bool |
||||||
|
{ |
||||||
|
return Message::class === $operation->getClass() && 'get_messages_by_group' === $operation->getName(); |
||||||
|
} |
||||||
|
|
||||||
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable |
||||||
|
{ |
||||||
|
$groupId = $context['filters']['groupId'] ?? null; |
||||||
|
|
||||||
|
if (null === $groupId) { |
||||||
|
|
||||||
|
return []; |
||||||
|
} |
||||||
|
|
||||||
|
return $this->messageRepository->findByGroupId((int) $groupId); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
<?php |
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* For licensing terms, see /license.txt */ |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\DataProvider; |
||||||
|
|
||||||
|
use ApiPlatform\Metadata\Operation; |
||||||
|
use ApiPlatform\State\ProviderInterface; |
||||||
|
use Chamilo\CoreBundle\Entity\Usergroup; |
||||||
|
use Chamilo\CoreBundle\Repository\Node\IllustrationRepository; |
||||||
|
use Chamilo\CoreBundle\Repository\Node\UsergroupRepository; |
||||||
|
use Symfony\Component\Security\Core\Security; |
||||||
|
|
||||||
|
|
||||||
|
final class UsergroupDataProvider implements ProviderInterface |
||||||
|
{ |
||||||
|
private $security; |
||||||
|
private $usergroupRepository; |
||||||
|
private $illustrationRepository; |
||||||
|
|
||||||
|
public function __construct(Security $security, UsergroupRepository $usergroupRepository, IllustrationRepository $illustrationRepository) |
||||||
|
{ |
||||||
|
$this->security = $security; |
||||||
|
$this->usergroupRepository = $usergroupRepository; |
||||||
|
$this->illustrationRepository = $illustrationRepository; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param Operation $operation |
||||||
|
* @param array $uriVariables |
||||||
|
* @param array $context |
||||||
|
* @return iterable |
||||||
|
*/ |
||||||
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable |
||||||
|
{ |
||||||
|
$operationName = $operation->getName(); |
||||||
|
if ($operationName === 'get_usergroup') { |
||||||
|
$groupId = $uriVariables['id'] ?? null; |
||||||
|
|
||||||
|
if (!$groupId) { |
||||||
|
throw new \Exception("Group ID is required for 'get_usergroup' operation"); |
||||||
|
} |
||||||
|
|
||||||
|
$group = $this->usergroupRepository->findGroupById($groupId); |
||||||
|
|
||||||
|
if (!$group) { |
||||||
|
throw new \Exception("Group not found"); |
||||||
|
} |
||||||
|
|
||||||
|
$this->setGroupDetails($group); |
||||||
|
|
||||||
|
return [$group]; |
||||||
|
} |
||||||
|
|
||||||
|
if ($operationName === 'search_usergroups') { |
||||||
|
$searchTerm = $context['filters']['search'] ?? ''; |
||||||
|
$groups = $this->usergroupRepository->searchGroups($searchTerm); |
||||||
|
foreach ($groups as $group) { |
||||||
|
$this->setGroupDetails($group); |
||||||
|
} |
||||||
|
return $groups; |
||||||
|
} |
||||||
|
|
||||||
|
switch ($operationName) { |
||||||
|
case 'get_my_usergroups': |
||||||
|
$userId = $context['request_attributes']['_api_filters']['userId'] ?? null; |
||||||
|
if (!$userId) { |
||||||
|
$user = $this->security->getUser(); |
||||||
|
$userId = $user ? $user->getId() : null; |
||||||
|
} |
||||||
|
if (!$userId) { |
||||||
|
throw new \Exception("User ID is required"); |
||||||
|
} |
||||||
|
$groups = $this->usergroupRepository->getGroupsByUser($userId, 0); |
||||||
|
break; |
||||||
|
|
||||||
|
case 'get_newest_usergroups': |
||||||
|
$groups = $this->usergroupRepository->getNewestGroups(); |
||||||
|
break; |
||||||
|
|
||||||
|
case 'get_popular_usergroups': |
||||||
|
$groups = $this->usergroupRepository->getPopularGroups(); |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
$groups = []; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (in_array($operationName, ['get_my_usergroups', 'get_newest_usergroups', 'get_popular_usergroups'])) { |
||||||
|
/* @var Usergroup $group */ |
||||||
|
foreach ($groups as $group) { |
||||||
|
$this->setGroupDetails($group); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $groups; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public function supports(Operation $operation, array $uriVariables = [], array $context = []): bool |
||||||
|
{ |
||||||
|
return Usergroup::class === $operation->getClass(); |
||||||
|
} |
||||||
|
|
||||||
|
private function setGroupDetails(Usergroup $group): void |
||||||
|
{ |
||||||
|
$memberCount = $this->usergroupRepository->countMembers($group->getId()); |
||||||
|
$group->setMemberCount($memberCount); |
||||||
|
|
||||||
|
if ($this->illustrationRepository->hasIllustration($group)) { |
||||||
|
$picture = $this->illustrationRepository->getIllustrationUrl($group); |
||||||
|
$group->setPictureUrl($picture); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\State; |
||||||
|
|
||||||
|
use ApiPlatform\Metadata\Operation; |
||||||
|
use ApiPlatform\State\ProcessorInterface; |
||||||
|
use Chamilo\CoreBundle\Entity\Usergroup; |
||||||
|
use Chamilo\CoreBundle\Entity\UsergroupRelUser; |
||||||
|
use Doctrine\ORM\EntityManagerInterface; |
||||||
|
use Symfony\Component\HttpFoundation\RequestStack; |
||||||
|
use Symfony\Component\Security\Core\Security; |
||||||
|
|
||||||
|
class UsergroupPostProcessor implements ProcessorInterface |
||||||
|
{ |
||||||
|
private ProcessorInterface $processor; |
||||||
|
private EntityManagerInterface $entityManager; |
||||||
|
private Security $security; |
||||||
|
private RequestStack $requestStack; |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
ProcessorInterface $processor, |
||||||
|
EntityManagerInterface $entityManager, |
||||||
|
Security $security, |
||||||
|
RequestStack $requestStack |
||||||
|
) { |
||||||
|
$this->processor = $processor; |
||||||
|
$this->entityManager = $entityManager; |
||||||
|
$this->security = $security; |
||||||
|
$this->requestStack = $requestStack; |
||||||
|
} |
||||||
|
|
||||||
|
public function process($data, Operation $operation, array $uriVariables = [], array $context = []) |
||||||
|
{ |
||||||
|
/** @var Usergroup $usergroup */ |
||||||
|
$usergroup = $this->processor->process($data, $operation, $uriVariables, $context); |
||||||
|
|
||||||
|
if ($usergroup instanceof Usergroup) { |
||||||
|
$this->associateCurrentUser($usergroup); |
||||||
|
$this->entityManager->flush(); |
||||||
|
} |
||||||
|
|
||||||
|
return $usergroup; |
||||||
|
} |
||||||
|
|
||||||
|
private function associateCurrentUser(Usergroup $usergroup) |
||||||
|
{ |
||||||
|
$currentUser = $this->security->getUser(); |
||||||
|
if ($currentUser) { |
||||||
|
$usergroupRelUser = new UsergroupRelUser(); |
||||||
|
$usergroupRelUser->setUsergroup($usergroup); |
||||||
|
$usergroupRelUser->setUser($currentUser); |
||||||
|
$usergroupRelUser->setRelationType(Usergroup::GROUP_USER_PERMISSION_ADMIN); |
||||||
|
|
||||||
|
$this->entityManager->persist($usergroupRelUser); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue