Resolved merge conflicts with upstream/master

pull/5319/head
christianbeeznst 1 year ago
commit 6ea53ae962
  1. 18
      assets/vue/components/StudentViewButton.vue
  2. 85
      assets/vue/components/resource_links/EditLinks.vue
  3. 56
      assets/vue/components/social/Actions.vue
  4. 78
      assets/vue/components/social/GroupInfoCard.vue
  5. 149
      assets/vue/components/social/SocialSideMenu.vue
  6. 52
      assets/vue/components/social/SocialWallCommentForm.vue
  7. 88
      assets/vue/components/social/SocialWallPost.vue
  8. 93
      assets/vue/components/social/SocialWallPostForm.vue
  9. 39
      assets/vue/components/social/SocialWallPostList.vue
  10. 101
      assets/vue/components/social/UserProfileCard.vue
  11. 14
      assets/vue/components/systemannouncement/SystemAnnouncementCardList.vue
  12. 115
      assets/vue/components/usergroup/GroupDiscussionTopics.vue
  13. 55
      assets/vue/components/usergroup/GroupMembers.vue
  14. 11
      assets/vue/composables/useSocialInfo.js
  15. 5
      assets/vue/config/api.js
  16. 23
      assets/vue/constants/entity/userreluser.js
  17. 263
      assets/vue/mixins/ListMixin.js
  18. 10
      assets/vue/services/adminService.js
  19. 65
      assets/vue/services/baseService.js
  20. 33
      assets/vue/services/cToolIntroService.js
  21. 13
      assets/vue/services/courseRelUserService.js
  22. 65
      assets/vue/services/courseService.js
  23. 8
      assets/vue/services/glossaryService.js
  24. 45
      assets/vue/services/linkService.js
  25. 26
      assets/vue/services/messageTagService.js
  26. 6
      assets/vue/services/pageCategoryService.js
  27. 11
      assets/vue/services/permissionService.js
  28. 8
      assets/vue/services/resourceLinkService.js
  29. 13
      assets/vue/services/sessionRelCourseRelUserService.js
  30. 11
      assets/vue/services/sessionRelUserService.js
  31. 12
      assets/vue/services/sessionService.js
  32. 138
      assets/vue/services/socialService.js
  33. 50
      assets/vue/services/userRelUserService.js
  34. 31
      assets/vue/services/userService.js
  35. 60
      assets/vue/services/usergroupService.js
  36. 8
      assets/vue/store/cidReq.js
  37. 10
      assets/vue/store/enrolledStore.js
  38. 18
      assets/vue/store/platformConfig.js
  39. 7
      assets/vue/store/socialStore.js
  40. 85
      assets/vue/views/admin/AdminConfigureColors.vue
  41. 45
      assets/vue/views/ccalendarevent/CCalendarEventList.vue
  42. 34
      assets/vue/views/course/CourseHome.vue
  43. 23
      assets/vue/views/ctoolintro/Create.vue
  44. 17
      assets/vue/views/ctoolintro/Update.vue
  45. 99
      assets/vue/views/documents/CreateFile.vue
  46. 103
      assets/vue/views/documents/DocumentsList.vue
  47. 38
      assets/vue/views/message/MessageCreate.vue
  48. 22
      assets/vue/views/message/MessageShow.vue
  49. 56
      assets/vue/views/user/courses/List.vue
  50. 81
      assets/vue/views/usergroup/Invite.vue
  51. 235
      assets/vue/views/usergroup/List.vue
  52. 59
      assets/vue/views/userreluser/UserRelUserAdd.vue
  53. 89
      assets/vue/views/userreluser/UserRelUserSearch.vue
  54. 34
      var/vue_templates/components/SidebarLogin.vue

@ -15,10 +15,10 @@ import { computed } from "vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { useStore } from "vuex" import { useStore } from "vuex"
import { usePlatformConfig } from "../store/platformConfig" import { usePlatformConfig } from "../store/platformConfig"
import axios from "axios"
import { storeToRefs } from "pinia" import { storeToRefs } from "pinia"
import { useCidReqStore } from "../store/cidReq" import { useCidReqStore } from "../store/cidReq"
import { useSecurityStore } from "../store/securityStore" import { useSecurityStore } from "../store/securityStore"
import permissionService from "../services/permissionService"
const emit = defineEmits(["change"]) const emit = defineEmits(["change"])
@ -30,15 +30,11 @@ const securityStore = useSecurityStore()
const isStudentView = computed({ const isStudentView = computed({
async set() { async set() {
try { const studentView = await permissionService.toogleStudentView()
const { data } = await axios.get(`${window.location.origin}/toggle_student_view`)
platformConfigStore.studentView = data platformConfigStore.studentView = studentView
emit("change", data) emit("change", studentView)
} catch (e) {
console.log(e)
}
}, },
get() { get() {
return platformConfigStore.isStudentViewActive return platformConfigStore.isStudentViewActive
@ -52,9 +48,11 @@ const { course, userIsCoach } = storeToRefs(cidReqStore)
const user = computed(() => store.getters["security/getUser"]) const user = computed(() => store.getters["security/getUser"])
const showButton = computed(() => { const showButton = computed(() => {
return securityStore.isAuthenticated && return (
securityStore.isAuthenticated &&
course.value && course.value &&
(isCourseAdmin.value || isAdmin.value || userIsCoach.value(user.value.id, 0, false)) && (isCourseAdmin.value || isAdmin.value || userIsCoach.value(user.value.id, 0, false)) &&
"true" === platformConfigStore.getSetting("course.student_view_enabled"); "true" === platformConfigStore.getSetting("course.student_view_enabled")
)
}) })
</script> </script>

@ -10,13 +10,13 @@
v-if="showShareWithUser" v-if="showShareWithUser"
v-model="selectedUsers" v-model="selectedUsers"
:internal-search="false" :internal-search="false"
:limit="3"
:loading="isLoading" :loading="isLoading"
:multiple="true" :multiple="true"
:options="users" :options="users"
:placeholder="$t('Share with User')" :placeholder="$t('Share with User')"
:searchable="true" :searchable="true"
label="username" label="username"
:limit="3"
track-by="id" track-by="id"
@select="addUser" @select="addUser"
@search-change="asyncFind" @search-change="asyncFind"
@ -27,12 +27,17 @@
<script setup> <script setup>
import ShowLinks from "../../components/resource_links/ShowLinks.vue" import ShowLinks from "../../components/resource_links/ShowLinks.vue"
import { ref } from "vue" import { ref } from "vue"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import VueMultiselect from "vue-multiselect" import VueMultiselect from "vue-multiselect"
import isEmpty from "lodash/isEmpty" import isEmpty from "lodash/isEmpty"
import { RESOURCE_LINK_PUBLISHED } from "./visibility.js" import { RESOURCE_LINK_PUBLISHED } from "./visibility.js"
import { useSecurityStore } from "../../store/securityStore" import { useSecurityStore } from "../../store/securityStore"
import userService from "../../services/userService"
import userRelUserService from "../../services/userRelUserService"
import { useCidReqStore } from "../../store/cidReq"
import { storeToRefs } from "pinia"
import sessionRelCourseRelUserService from "../../services/sessionRelCourseRelUserService"
import sessionRelUserService from "../../services/sessionRelUserService"
import courseRelUserService from "../../services/courseRelUserService"
// eslint-disable-next-line vue/require-prop-types // eslint-disable-next-line vue/require-prop-types
const model = defineModel() const model = defineModel()
@ -70,6 +75,9 @@ const selectedUsers = ref([])
const isLoading = ref(false) const isLoading = ref(false)
const securityStore = useSecurityStore() const securityStore = useSecurityStore()
const cidReqStore = useCidReqStore()
const { course, session } = storeToRefs(cidReqStore)
function addUser(userResult) { function addUser(userResult) {
if (isEmpty(model.value[props.linkListName])) { if (isEmpty(model.value[props.linkListName])) {
@ -90,39 +98,17 @@ function addUser(userResult) {
} }
function findUsers(query) { function findUsers(query) {
axios userService
.get(ENTRYPOINT + "users", { .findByUsername(query)
params: { .then(({ items }) => (users.value = items))
username: query, .finally(() => (isLoading.value = false))
},
})
.then((response) => {
isLoading.value = false
let data = response.data
users.value = data["hydra:member"]
})
.catch(function (error) {
isLoading.value = false
console.log(error)
})
} }
function findUserRelUsers(query) { function findUserRelUsers(query) {
axios userRelUserService
.get(ENTRYPOINT + "user_rel_users", { .searchRelationshipByUsername(securityStore.user["@id"], query)
params: { .then(({ items }) => (users.value = items.map((relationship) => relationship.friend)))
user: securityStore.user["id"], .finally(() => (isLoading.value = false))
"friend.username": query,
},
})
.then((response) => {
isLoading.value = false
users.value = response.data["hydra:member"].map((member) => member.friend)
})
.catch(function () {
isLoading.value = false
})
} }
function findStudentsInCourse(query) { function findStudentsInCourse(query) {
@ -130,42 +116,35 @@ function findStudentsInCourse(query) {
const cId = parseInt(searchParams.get("cid")) const cId = parseInt(searchParams.get("cid"))
const sId = parseInt(searchParams.get("sid")) const sId = parseInt(searchParams.get("sid"))
if (!cId && !sId) { if (!course.value && !session.value) {
return return
} }
let endpoint = ENTRYPOINT
let params = { let params = {
"user.username": query, "user.username": query,
} }
if (sId) { if (session.value) {
params.session = endpoint + `sessions/${sId}` params.session = session.value["@id"]
} }
let service
if (cId) { if (cId) {
params.course = course.value["@id"]
if (sId) { if (sId) {
endpoint += `session_rel_course_rel_users` service = sessionRelCourseRelUserService.findAll
params.course = endpoint + `courses/${cId}`
} else { } else {
endpoint += `courses/${cId}/users` service = courseRelUserService.findAll
} }
} else { } else {
endpoint += `session_rel_users` service = sessionRelUserService.findAll
} }
axios service(params)
.get(endpoint, { .then(({ items }) => (users.value = items.map((membership) => membership.user)))
params, .finally(() => (isLoading.value = false))
})
.then((response) => {
isLoading.value = false
users.value = response.data["hydra:member"].map((member) => member.user)
})
.catch(function () {
isLoading.value = false
})
} }
function asyncFind(query) { function asyncFind(query) {

@ -39,9 +39,9 @@
</template> </template>
<script> <script>
import { reactive, ref } from "vue"; import { reactive } from "vue"
import axios from "axios"; import { usePlatformConfig } from "../../store/platformConfig"
import { usePlatformConfig } from "../../store/platformConfig"; import socialService from "../../services/socialService"
export default { export default {
name: "WallActions", name: "WallActions",
@ -57,53 +57,49 @@ export default {
}, },
emits: ["post-deleted"], emits: ["post-deleted"],
setup(props, { emit }) { setup(props, { emit }) {
const platformConfigStore = usePlatformConfig(); const platformConfigStore = usePlatformConfig()
const isLoading = reactive({ const isLoading = reactive({
like: false, like: false,
dislike: false, dislike: false,
delete: false, delete: false,
}); })
function onLikeComment() { function onLikeComment() {
isLoading.like = true; isLoading.like = true
axios socialService
.post(props.socialPost["@id"] + "/like", {}) .sendPostLike(props.socialPost["@id"])
.then(({ data }) => { .then((like) => {
props.socialPost.countFeedbackLikes = data.countFeedbackLikes; props.socialPost.countFeedbackLikes = like.countFeedbackLikes
props.socialPost.countFeedbackDislikes = data.countFeedbackDislikes; props.socialPost.countFeedbackDislikes = like.countFeedbackDislikes
}) })
.finally(() => (isLoading.like = false)); .finally(() => (isLoading.like = false))
} }
function onDisikeComment() { function onDisikeComment() {
isLoading.dislike = true; isLoading.dislike = true
axios socialService
.post(props.socialPost["@id"] + "/dislike", {}) .sendPostDislike(props.socialPost["@id"])
.then(({ data }) => { .then((like) => {
props.socialPost.countFeedbackLikes = data.countFeedbackLikes; props.socialPost.countFeedbackLikes = like.countFeedbackLikes
props.socialPost.countFeedbackDislikes = data.countFeedbackDislikes; props.socialPost.countFeedbackDislikes = like.countFeedbackDislikes
}) })
.finally(() => (isLoading.dislike = false)); .finally(() => (isLoading.dislike = false))
} }
function onDeleteComment() { function onDeleteComment() {
isLoading.delete = true; isLoading.delete = true
axios socialService
.delete(props.socialPost["@id"]) .delete(props.socialPost["@id"])
.then(() => emit("post-deleted", props.socialPost)) .then(() => emit("post-deleted", props.socialPost))
.finally(() => (isLoading.delete = false)); .finally(() => (isLoading.delete = false))
} }
const enableFeedback = const enableFeedback = "true" === platformConfigStore.getSetting("social.social_enable_messages_feedback")
"true" === const disableDislike = "true" === platformConfigStore.getSetting("social.disable_dislike_option")
platformConfigStore.getSetting("social.social_enable_messages_feedback");
const disableDislike =
"true" ===
platformConfigStore.getSetting("social.disable_dislike_option");
return { return {
enableFeedback, enableFeedback,
@ -112,7 +108,7 @@ export default {
onLikeComment, onLikeComment,
onDisikeComment, onDisikeComment,
onDeleteComment, onDeleteComment,
}; }
}, },
}; }
</script> </script>

@ -3,42 +3,47 @@
<div class="p-4 text-center"> <div class="p-4 text-center">
<img <img
:src="groupInfo.image" :src="groupInfo.image"
class="mb-4 w-24 h-24 mx-auto rounded-full"
alt="Group picture" alt="Group picture"
class="mb-4 w-24 h-24 mx-auto rounded-full"
/> />
<hr /> <hr />
<BaseButton <BaseButton
v-if="groupInfo.isModerator" v-if="groupInfo.isModerator"
:label="t('Edit this group')" :label="t('Edit this group')"
type="primary"
class="mt-4" class="mt-4"
@click="showEditGroupDialog = true"
icon="edit" icon="edit"
type="primary"
@click="showEditGroupDialog = true"
/> />
</div> </div>
</BaseCard> </BaseCard>
<Dialog header="Edit Group" v-model:visible="showEditGroupDialog" :modal="true" :closable="true"> <Dialog
v-model:visible="showEditGroupDialog"
:closable="true"
:modal="true"
header="Edit Group"
>
<form @submit.prevent="submitGroupEdit"> <form @submit.prevent="submitGroupEdit">
<div class="p-fluid"> <div class="p-fluid">
<BaseInputTextWithVuelidate <BaseInputTextWithVuelidate
v-model="editGroupForm.name" v-model="editGroupForm.name"
label="Name*"
:vuelidate-property="v$.editGroupForm.name" :vuelidate-property="v$.editGroupForm.name"
label="Name*"
/> />
<BaseInputTextWithVuelidate <BaseInputTextWithVuelidate
v-model="editGroupForm.description" v-model="editGroupForm.description"
label="Description"
:vuelidate-property="v$.editGroupForm.description" :vuelidate-property="v$.editGroupForm.description"
as="textarea" as="textarea"
label="Description"
rows="3" rows="3"
/> />
<BaseInputTextWithVuelidate <BaseInputTextWithVuelidate
v-model="editGroupForm.url" v-model="editGroupForm.url"
label="URL"
:vuelidate-property="v$.editGroupForm.url" :vuelidate-property="v$.editGroupForm.url"
label="URL"
/> />
<BaseFileUpload <BaseFileUpload
@ -50,7 +55,13 @@
<div class="p-field mt-2"> <div class="p-field mt-2">
<label for="groupPermissions">Group Permissions</label> <label for="groupPermissions">Group Permissions</label>
<Dropdown id="groupPermissions" v-model="editGroupForm.permissions" :options="permissionsOptions" optionLabel="label" placeholder="Select Permission" /> <Dropdown
id="groupPermissions"
v-model="editGroupForm.permissions"
:options="permissionsOptions"
option-label="label"
placeholder="Select Permission"
/>
</div> </div>
<div class="p-field-checkbox mt-2"> <div class="p-field-checkbox mt-2">
@ -62,15 +73,24 @@
/> />
</div> </div>
</div> </div>
<Button label="Save" icon="pi pi-check" class="p-button-rounded p-button-text" @click="submitGroupEdit" /> <Button
<Button label="Close" class="p-button-text" @click="closeEditDialog" /> class="p-button-rounded p-button-text"
icon="pi pi-check"
label="Save"
@click="submitGroupEdit"
/>
<Button
class="p-button-text"
label="Close"
@click="closeEditDialog"
/>
</form> </form>
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { computed, inject, onMounted, ref, watch } from "vue" import { inject, ref } from "vue"
import { useStore } from 'vuex' import { useStore } from "vuex"
import BaseCard from "../basecomponents/BaseCard.vue" import BaseCard from "../basecomponents/BaseCard.vue"
import BaseButton from "../basecomponents/BaseButton.vue" import BaseButton from "../basecomponents/BaseButton.vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
@ -79,7 +99,7 @@ import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVueli
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue" import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
import BaseFileUpload from "../basecomponents/BaseFileUpload.vue" import BaseFileUpload from "../basecomponents/BaseFileUpload.vue"
import useVuelidate from "@vuelidate/core" import useVuelidate from "@vuelidate/core"
import { required } from '@vuelidate/validators' import { required } from "@vuelidate/validators"
import axios from "axios" import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint" import { ENTRYPOINT } from "../../config/entrypoint"
@ -87,33 +107,36 @@ const { t } = useI18n()
const store = useStore() const store = useStore()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const groupInfo = inject('group-info') const groupInfo = inject("group-info")
const isGroup = inject('is-group') const isGroup = inject("is-group")
const showEditGroupDialog = ref(false) const showEditGroupDialog = ref(false)
const selectedFile = ref(null) const selectedFile = ref(null)
const permissionsOptions = [ const permissionsOptions = [
{ label: 'Open', value: 1 }, { label: "Open", value: 1 },
{ label: 'Closed', value: 2 }, { label: "Closed", value: 2 },
] ]
const editGroupForm = ref({ const editGroupForm = ref({
name: groupInfo.value.title, name: groupInfo.value.title,
description: groupInfo.value.description, description: groupInfo.value.description,
url: groupInfo.value.url, url: groupInfo.value.url,
permissions: permissionsOptions.find(option => option.value === groupInfo.value.visibility), permissions: permissionsOptions.find((option) => option.value === groupInfo.value.visibility),
allowLeave: Boolean(groupInfo.value.allowMembersToLeaveGroup), allowLeave: Boolean(groupInfo.value.allowMembersToLeaveGroup),
}) })
const v$ = useVuelidate({ const v$ = useVuelidate(
{
editGroupForm: { editGroupForm: {
name: { required }, name: { required },
description: {}, description: {},
url: {}, url: {},
permissions: { required }, permissions: { required },
} },
}, { editGroupForm }) },
{ editGroupForm },
)
const submitGroupEdit = () => { const submitGroupEdit = () => {
v$.value.$touch() v$.value.$touch()
@ -126,30 +149,31 @@ const submitGroupEdit = () => {
allowMembersToLeaveGroup: editGroupForm.value.allowLeave ? 1 : 0, allowMembersToLeaveGroup: editGroupForm.value.allowLeave ? 1 : 0,
} }
axios.put(`${ENTRYPOINT}usergroups/${groupInfo.value.id}`, updatedGroupData, { axios
.put(`${ENTRYPOINT}usergroups/${groupInfo.value.id}`, updatedGroupData, {
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
}, },
}) })
.then((response) => { .then((response) => {
if (selectedFile.value && response.data && response.data.id) { if (selectedFile.value && response.data && response.data.id) {
const formData = new FormData() const formData = new FormData()
formData.append('picture', selectedFile.value) formData.append("picture", selectedFile.value)
return axios.post(`/social-network/upload-group-picture/${response.data.id}`, formData, { return axios.post(`/social-network/upload-group-picture/${response.data.id}`, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', "Content-Type": "multipart/form-data",
}, },
}) })
} }
}) })
.then(() => { .then(() => {
showEditGroupDialog.value = false showEditGroupDialog.value = false
router.push('/dummy').then(() => { router.push("/dummy").then(() => {
router.go(-1) router.go(-1)
}) })
}) })
.catch((error) => { .catch((error) => {
console.error('Error updating group:', error) console.error("Error updating group:", error)
}) })
} }
} }

@ -2,82 +2,140 @@
<BaseCard class="social-side-menu mt-4"> <BaseCard class="social-side-menu mt-4">
<template #header> <template #header>
<div class="px-4 py-2 -mb-2 bg-gray-15"> <div class="px-4 py-2 -mb-2 bg-gray-15">
<h2 class="text-h5">{{ t('Social network') }}</h2> <h2 class="text-h5">{{ t("Social network") }}</h2>
</div> </div>
</template> </template>
<hr class="-mt-2 mb-4 -mx-4"> <hr class="-mt-2 mb-4 -mx-4" />
<ul v-if="isCurrentUser" class="menu-list"> <ul
<li :class="['menu-item', { 'active': isActive('/social') }]"> v-if="isCurrentUser"
class="menu-list"
>
<li :class="['menu-item', { active: isActive('/social') }]">
<router-link to="/social"> <router-link to="/social">
<i class="mdi mdi-home" aria-hidden="true"></i> <i
aria-hidden="true"
class="mdi mdi-home"
></i>
{{ t("Home") }} {{ t("Home") }}
</router-link> </router-link>
</li> </li>
<li :class="['menu-item', { 'active': isActive('/resources/messages') }]"> <li :class="['menu-item', { active: isActive('/resources/messages') }]">
<router-link to="/resources/messages"> <router-link to="/resources/messages">
<i class="mdi mdi-email" aria-hidden="true"></i> <i
aria-hidden="true"
class="mdi mdi-email"
></i>
{{ t("Messages") }} {{ t("Messages") }}
<span class="badge badge-warning" v-if="unreadMessagesCount > 0">{{ unreadMessagesCount }}</span> <span
v-if="unreadMessagesCount > 0"
class="badge badge-warning"
>{{ unreadMessagesCount }}</span
>
</router-link> </router-link>
</li> </li>
<li :class="['menu-item', { 'active': isActive('/resources/friends/invitations') }]"> <li :class="['menu-item', { active: isActive('/resources/friends/invitations') }]">
<router-link :to="{ name: 'Invitations' }"> <router-link :to="{ name: 'Invitations' }">
<i class="mdi mdi-mailbox" aria-hidden="true"></i> <i
aria-hidden="true"
class="mdi mdi-mailbox"
></i>
{{ t("Invitations") }} {{ t("Invitations") }}
<span class="badge badge-warning" v-if="invitationsCount > 0">{{ invitationsCount }}</span> <span
v-if="invitationsCount > 0"
class="badge badge-warning"
>{{ invitationsCount }}</span
>
</router-link> </router-link>
</li> </li>
<li :class="['menu-item', { 'active': isActive('/resources/friends') }]"> <li :class="['menu-item', { active: isActive('/resources/friends') }]">
<router-link :to="{ name: 'UserRelUserList' }"> <router-link :to="{ name: 'UserRelUserList' }">
<i class="mdi mdi-handshake" aria-hidden="true"></i> <i
aria-hidden="true"
class="mdi mdi-handshake"
></i>
{{ t("My friends") }} {{ t("My friends") }}
</router-link> </router-link>
</li> </li>
<li :class="['menu-item', { 'active': isActive(groupLink) }]"> <li :class="['menu-item', { active: isActive(groupLink) }]">
<a v-if="isValidGlobalForumsCourse" :href="groupLink" rel="noopener noreferrer"> <a
<i class="mdi mdi-group" aria-hidden="true"></i> v-if="isValidGlobalForumsCourse"
:href="groupLink"
rel="noopener noreferrer"
>
<i
aria-hidden="true"
class="mdi mdi-group"
></i>
{{ t("Social groups") }} {{ t("Social groups") }}
</a> </a>
<router-link v-else :to="groupLink"> <router-link
<i class="mdi mdi-group" aria-hidden="true"></i> v-else
:to="groupLink"
>
<i
aria-hidden="true"
class="mdi mdi-group"
></i>
{{ t("Social groups") }} {{ t("Social groups") }}
</router-link> </router-link>
</li> </li>
<li :class="['menu-item', { 'active': isActive('/social/search') }]"> <li :class="['menu-item', { active: isActive('/social/search') }]">
<router-link to="/social/search"> <router-link to="/social/search">
<i class="mdi mdi-magnify" aria-hidden="true"></i> <i
aria-hidden="true"
class="mdi mdi-magnify"
></i>
{{ t("Search") }} {{ t("Search") }}
</router-link> </router-link>
</li> </li>
<li :class="['menu-item', { 'active': isActive('/resources/personal_files') }]"> <li :class="['menu-item', { active: isActive('/resources/personal_files') }]">
<router-link :to="{ name: 'PersonalFileList', params: { node: currentNodeId } }"> <router-link :to="{ name: 'PersonalFileList', params: { node: currentNodeId } }">
<i class="mdi mdi-briefcase"></i> <i class="mdi mdi-briefcase"></i>
{{ t("My files") }} {{ t("My files") }}
</router-link> </router-link>
</li> </li>
<li :class="['menu-item', { 'active': isActive('/resources/users/personal_data') }]"> <li :class="['menu-item', { active: isActive('/resources/users/personal_data') }]">
<router-link to="/resources/users/personal_data"> <router-link to="/resources/users/personal_data">
<i class="mdi mdi-account" aria-hidden="true"></i> <i
aria-hidden="true"
class="mdi mdi-account"
></i>
{{ t("Personal data") }} {{ t("Personal data") }}
</router-link> </router-link>
</li> </li>
<li :class="['menu-item', { 'active': isActive('/social', 'promoted') }]"> <li :class="['menu-item', { active: isActive('/social', 'promoted') }]">
<router-link :to="{ path: '/social', query: { filterType: 'promoted' } }"> <router-link :to="{ path: '/social', query: { filterType: 'promoted' } }">
<i class="mdi mdi-star" aria-hidden="true"></i> <i
aria-hidden="true"
class="mdi mdi-star"
></i>
{{ t("Promoted messages") }} {{ t("Promoted messages") }}
</router-link> </router-link>
</li> </li>
</ul> </ul>
<ul v-else class="menu-list"> <ul
v-else
class="menu-list"
>
<li class="menu-item"> <li class="menu-item">
<router-link to="/social"> <router-link to="/social">
<i class="mdi mdi-home" aria-hidden="true"></i> <i
aria-hidden="true"
class="mdi mdi-home"
></i>
{{ t("Home") }} {{ t("Home") }}
</router-link> </router-link>
</li> </li>
<li class="menu-item"> <li class="menu-item">
<a href="/main/inc/ajax/user_manager.ajax.php?a=get_user_popup&user_id={{user.id}}" class="ajax" rel="noopener noreferrer"> <a
<i class="mdi mdi-email" aria-hidden="true"></i> class="ajax"
href="/main/inc/ajax/user_manager.ajax.php?a=get_user_popup&user_id={{user.id}}"
rel="noopener noreferrer"
>
<i
aria-hidden="true"
class="mdi mdi-email"
></i>
{{ t("Send message") }} {{ t("Send message") }}
</a> </a>
</li> </li>
@ -87,10 +145,10 @@
<script setup> <script setup>
import BaseCard from "../basecomponents/BaseCard.vue" import BaseCard from "../basecomponents/BaseCard.vue"
import { useRoute } from 'vue-router' import { useRoute } from "vue-router"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { useMessageRelUserStore } from "../../store/messageRelUserStore" import { useMessageRelUserStore } from "../../store/messageRelUserStore"
import { onMounted, computed, ref, inject, watchEffect } from "vue" import { computed, inject, onMounted, ref, watchEffect } from "vue"
import { useStore } from "vuex" import { useStore } from "vuex"
import { useSecurityStore } from "../../store/securityStore" import { useSecurityStore } from "../../store/securityStore"
import axios from "axios" import axios from "axios"
@ -105,9 +163,9 @@ const messageRelUserStore = useMessageRelUserStore()
const unreadMessagesCount = computed(() => messageRelUserStore.countUnread) const unreadMessagesCount = computed(() => messageRelUserStore.countUnread)
const invitationsCount = ref(0) const invitationsCount = ref(0)
const user = inject('social-user') const user = inject("social-user")
const isCurrentUser = inject('is-current-user') const isCurrentUser = inject("is-current-user")
const groupLink = ref({ name: 'UserGroupShow' }) const groupLink = ref({ name: "UserGroupShow" })
const platformConfigStore = usePlatformConfig() const platformConfigStore = usePlatformConfig()
const globalForumsCourse = computed(() => platformConfigStore.getSetting("forum.global_forums_course_id")) const globalForumsCourse = computed(() => platformConfigStore.getSetting("forum.global_forums_course_id"))
const isValidGlobalForumsCourse = computed(() => { const isValidGlobalForumsCourse = computed(() => {
@ -116,15 +174,15 @@ const isValidGlobalForumsCourse = computed(() => {
}) })
const getGroupLink = async () => { const getGroupLink = async () => {
try { try {
const response = await axios.get('/social-network/get-forum-link') const response = await axios.get("/social-network/get-forum-link")
if (isValidGlobalForumsCourse.value) { if (isValidGlobalForumsCourse.value) {
groupLink.value = response.data.go_to groupLink.value = response.data.go_to
} else { } else {
groupLink.value = { name: 'UserGroupList' } groupLink.value = { name: "UserGroupList" }
} }
} catch (error) { } catch (error) {
console.error('Error fetching forum link:', error) console.error("Error fetching forum link:", error)
groupLink.value = { name: 'UserGroupList' } groupLink.value = { name: "UserGroupList" }
} }
} }
@ -134,7 +192,7 @@ const fetchInvitationsCount = async (userId) => {
const { data } = await axios.get(`/social-network/invitations/count/${userId}`) const { data } = await axios.get(`/social-network/invitations/count/${userId}`)
invitationsCount.value = data.totalInvitationsCount invitationsCount.value = data.totalInvitationsCount
} catch (error) { } catch (error) {
console.error('Error fetching invitations count:', error) console.error("Error fetching invitations count:", error)
} }
} }
watchEffect(() => { watchEffect(() => {
@ -152,19 +210,24 @@ watchEffect(() => {
fetchInvitationsCount(user.value.id) fetchInvitationsCount(user.value.id)
} }
} catch (e) { } catch (e) {
console.error('Error loading user:', e) console.error("Error loading user:", e)
} }
}) })
const isActive = (path, filterType = null) => { const isActive = (path, filterType = null) => {
if (path === '/resources/friends/invitations' || path === '/social/search') { if (path === "/resources/friends/invitations" || path === "/social/search") {
return route.path === path return route.path === path
} }
const pathMatch = route.path.startsWith(path) const pathMatch = route.path.startsWith(path)
const hasQueryParams = Object.keys(route.query).length > 0 const hasQueryParams = Object.keys(route.query).length > 0
const filterMatch = filterType ? (route.query.filterType === filterType && hasQueryParams) : !hasQueryParams const filterMatch = filterType ? route.query.filterType === filterType && hasQueryParams : !hasQueryParams
return pathMatch && filterMatch && !route.path.startsWith('/resources/friends/invitations') && !route.path.startsWith('/social/search') return (
pathMatch &&
filterMatch &&
!route.path.startsWith("/resources/friends/invitations") &&
!route.path.startsWith("/social/search")
)
} }
onMounted(async () => { onMounted(async () => {

@ -2,68 +2,68 @@
<form class="flex flex-wrap items-start gap-2"> <form class="flex flex-wrap items-start gap-2">
<BaseInputText <BaseInputText
v-model="comment" v-model="comment"
class="grow mb-0"
:label="$t('Write new comment')"
:aria-placeholder="$t('Write new comment')" :aria-placeholder="$t('Write new comment')"
:is-invalid="error !== ''"
:error-text="error" :error-text="error"
:is-invalid="error !== ''"
:label="$t('Write new comment')"
class="grow mb-0"
/> />
<BaseButton <BaseButton
:label="$t('Post')" :label="$t('Post')"
class="ml-auto" class="ml-auto"
type="primary"
icon="send" icon="send"
size="small" size="small"
type="primary"
@click="sendComment" @click="sendComment"
/> />
</form> </form>
</template> </template>
<script setup> <script setup>
import {ref} from "vue" import { ref } from "vue"
import {useStore} from "vuex" import { useStore } from "vuex"
import axios from "axios" import axios from "axios"
import {ENTRYPOINT} from "../../config/entrypoint" import { ENTRYPOINT } from "../../config/entrypoint"
import {SOCIAL_TYPE_WALL_COMMENT} from "./constants" import { SOCIAL_TYPE_WALL_COMMENT } from "./constants"
import BaseInputText from "../basecomponents/BaseInputText.vue"; import BaseInputText from "../basecomponents/BaseInputText.vue"
import {useI18n} from "vue-i18n"; import { useI18n } from "vue-i18n"
import BaseButton from "../basecomponents/BaseButton.vue"; import BaseButton from "../basecomponents/BaseButton.vue"
const store = useStore() const store = useStore()
const {t} = useI18n() const { t } = useI18n()
const props = defineProps({ const props = defineProps({
post: { post: {
type: Object, type: Object,
required: true, required: true,
} },
}) })
const emit = defineEmits(['comment-posted']) const emit = defineEmits(["comment-posted"])
const currentUser = store.getters['security/getUser'] const currentUser = store.getters["security/getUser"]
const comment = ref('') const comment = ref("")
const error = ref('') const error = ref("")
const isLoading = ref(false) const isLoading = ref(false)
function sendComment() { function sendComment() {
if (comment.value === '') { if (comment.value === "") {
error.value = t('The comment is required') error.value = t("The comment is required")
return return
} }
isLoading.value = true isLoading.value = true
axios axios
.post(ENTRYPOINT + 'social_posts', { .post(ENTRYPOINT + "social_posts", {
content: comment.value, content: comment.value,
type: SOCIAL_TYPE_WALL_COMMENT, type: SOCIAL_TYPE_WALL_COMMENT,
sender: currentUser['@id'], sender: currentUser["@id"],
parent: props.post['@id'], parent: props.post["@id"],
}) })
.then(response => { .then((response) => {
emit('comment-posted', response.data) emit("comment-posted", response.data)
comment.value = '' comment.value = ""
error.value = '' error.value = ""
}) })
.finally(() => { .finally(() => {
isLoading.value = false isLoading.value = false

@ -1,26 +1,30 @@
<template> <template>
<BaseCard <BaseCard
:class="{ 'border-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE }"
class="mb-4" class="mb-4"
:class="{ 'border-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE}"
plain plain
> >
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex gap-2 mb-4"> <div class="flex gap-2 mb-4">
<img class="h-12 w-12 border border-gray-25" :src="post.sender.illustrationUrl" :alt="post.sender.username"> <img
:alt="post.sender.username"
:src="post.sender.illustrationUrl"
class="h-12 w-12 border border-gray-25"
/>
<div class="flex flex-col"> <div class="flex flex-col">
<div v-if="null === post.userReceiver || post.sender['@id'] === post.userReceiver['@id']"> <div v-if="null === post.userReceiver || post.sender['@id'] === post.userReceiver['@id']">
<router-link :to="{ name: 'SocialWall', query: { id: post.sender['@id']} }"> <router-link :to="{ name: 'SocialWall', query: { id: post.sender['@id'] } }">
{{ post.sender.fullName }} {{ post.sender.fullName }}
</router-link> </router-link>
</div> </div>
<div v-else> <div v-else>
<router-link :to="{ name: 'SocialWall', query: { id: post.sender['@id']} }"> <router-link :to="{ name: 'SocialWall', query: { id: post.sender['@id'] } }">
{{ post.sender.fullName }} {{ post.sender.fullName }}
</router-link> </router-link>
&raquo; &raquo;
<router-link :to="{ name: 'SocialWall', query: { id: post.userReceiver['@id']} }"> <router-link :to="{ name: 'SocialWall', query: { id: post.userReceiver['@id'] } }">
{{ post.userReceiver.fullName }} {{ post.userReceiver.fullName }}
</router-link> </router-link>
</div> </div>
@ -31,36 +35,45 @@
</div> </div>
<WallActions <WallActions
class="ml-auto"
:is-owner="isOwner" :is-owner="isOwner"
:social-post="post" :social-post="post"
class="ml-auto"
@post-deleted="onPostDeleted(post)" @post-deleted="onPostDeleted(post)"
/> />
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div v-for="(attachment, index) in computedAttachments" :key="index"> <div
v-for="(attachment, index) in computedAttachments"
:key="index"
>
<img <img
v-if="isImageAttachment(attachment)" v-if="isImageAttachment(attachment)"
:src="attachment.path"
:alt="attachment.filename" :alt="attachment.filename"
> :src="attachment.path"
/>
<video v-if="isVideoAttachment(attachment)" controls> <video
<source :src="attachment.path" :type="attachment.mimeType"> v-if="isVideoAttachment(attachment)"
controls
>
<source
:src="attachment.path"
:type="attachment.mimeType"
/>
</video> </video>
</div> </div>
<div v-html="post.content"/> <div v-html="post.content" />
<hr :class="{'text-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE}"> <hr :class="{ 'text-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE }" />
<div <div
v-if="comments.length" v-if="comments.length"
:class="{'text-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE}" :class="{ 'text-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE }"
class="border-t-0" class="border-t-0"
> >
<div>{{ $t('Comments') }}</div> <div>{{ $t("Comments") }}</div>
<WallComment <WallComment
v-for="(comment, index) in comments" v-for="(comment, index) in comments"
:key="index" :key="index"
@ -80,34 +93,35 @@
<script setup> <script setup>
import WallCommentForm from "./SocialWallCommentForm.vue" import WallCommentForm from "./SocialWallCommentForm.vue"
import { ref, computed, onMounted, reactive, inject } from "vue" import { computed, inject, onMounted, reactive, ref } from "vue"
import WallComment from "./SocialWallComment.vue" import WallComment from "./SocialWallComment.vue"
import WallActions from "./Actions" import WallActions from "./Actions"
import axios from "axios" import axios from "axios"
import {ENTRYPOINT} from "../../config/entrypoint" import { ENTRYPOINT } from "../../config/entrypoint"
import {useStore} from "vuex" import { useStore } from "vuex"
import BaseCard from "../basecomponents/BaseCard.vue" import BaseCard from "../basecomponents/BaseCard.vue"
import {SOCIAL_TYPE_PROMOTED_MESSAGE} from "./constants" import { SOCIAL_TYPE_PROMOTED_MESSAGE } from "./constants"
import { useFormatDate } from "../../composables/formatDate" import { useFormatDate } from "../../composables/formatDate"
import { useSecurityStore } from "../../store/securityStore"
const props = defineProps({ const props = defineProps({
post: { post: {
type: Object, type: Object,
required: true required: true,
} },
}) })
const emit = defineEmits(["post-deleted"]) const emit = defineEmits(["post-deleted"])
const store = useStore() const store = useStore()
import { useSecurityStore } from "../../store/securityStore"
const { relativeDatetime } = useFormatDate() const { relativeDatetime } = useFormatDate()
let comments = reactive([]) let comments = reactive([])
const attachments = ref([]) const attachments = ref([])
const securityStore = useSecurityStore() const securityStore = useSecurityStore()
const currentUser = inject('social-user') const currentUser = inject("social-user")
const isCurrentUser = inject('is-current-user') const isCurrentUser = inject("is-current-user")
const isOwner = computed(() => currentUser['@id'] === props.post.sender['@id']) const isOwner = computed(() => currentUser["@id"] === props.post.sender["@id"])
onMounted(async () => { onMounted(async () => {
loadComments() loadComments()
@ -116,6 +130,7 @@ onMounted(async () => {
const computedAttachments = computed(() => { const computedAttachments = computed(() => {
return attachments.value return attachments.value
}) })
async function loadAttachments() { async function loadAttachments() {
try { try {
const postIri = props.post["@id"] const postIri = props.post["@id"]
@ -129,19 +144,18 @@ async function loadAttachments() {
function loadComments() { function loadComments() {
axios axios
.get(ENTRYPOINT + 'social_posts', { .get(ENTRYPOINT + "social_posts", {
params: { params: {
parent: props.post['@id'], parent: props.post["@id"],
'order[sendDate]': 'desc', "order[sendDate]": "desc",
itemsPerPage: 3, itemsPerPage: 3,
} },
}) })
.then(response => comments.push(...response.data['hydra:member'])) .then((response) => comments.push(...response.data["hydra:member"]))
} }
function onCommentDeleted(event) { function onCommentDeleted(event) {
const index = comments.findIndex(comment => comment['@id'] === event.comment['@id']) const index = comments.findIndex((comment) => comment["@id"] === event.comment["@id"])
if (-1 !== index) { if (-1 !== index) {
comments.splice(index, 1) comments.splice(index, 1)
} }
@ -152,21 +166,21 @@ function onCommentPosted(newComment) {
} }
function onPostDeleted(post) { function onPostDeleted(post) {
emit('post-deleted', post) emit("post-deleted", post)
} }
const isImageAttachment = (attachment) => { const isImageAttachment = (attachment) => {
if (attachment.filename) { if (attachment.filename) {
const fileExtension = attachment.filename.split('.').pop().toLowerCase() const fileExtension = attachment.filename.split(".").pop().toLowerCase()
return ['jpg', 'jpeg', 'png', 'gif'].includes(fileExtension) return ["jpg", "jpeg", "png", "gif"].includes(fileExtension)
} }
return false return false
} }
const isVideoAttachment = (attachment) => { const isVideoAttachment = (attachment) => {
if (attachment.filename) { if (attachment.filename) {
const fileExtension = attachment.filename.split('.').pop().toLowerCase() const fileExtension = attachment.filename.split(".").pop().toLowerCase()
return ['mp4', 'webm', 'ogg'].includes(fileExtension) return ["mp4", "webm", "ogg"].includes(fileExtension)
} }
return false return false

@ -3,9 +3,9 @@
<form> <form>
<BaseInputTextWithVuelidate <BaseInputTextWithVuelidate
v-model="content" v-model="content"
class="mb-2"
:label="textPlaceholder" :label="textPlaceholder"
:vuelidate-property="v$.content" :vuelidate-property="v$.content"
class="mb-2"
/> />
<div class="mb-2"> <div class="mb-2">
@ -16,7 +16,10 @@
:label="$t('Mark as promoted message')" :label="$t('Mark as promoted message')"
name="is-promoted" name="is-promoted"
/> />
<div v-if="isPromotedPage" class="text-info"> <div
v-if="isPromotedPage"
class="text-info"
>
{{ $t("All messages here are automatically marked as promoted.") }} {{ $t("All messages here are automatically marked as promoted.") }}
</div> </div>
</div> </div>
@ -33,9 +36,9 @@
<BaseButton <BaseButton
:label="$t('Post')" :label="$t('Post')"
class="ml-auto" class="ml-auto"
type="primary"
icon="send" icon="send"
size="small" size="small"
type="primary"
@click="sendPost" @click="sendPost"
/> />
</div> </div>
@ -44,53 +47,63 @@
</template> </template>
<script setup> <script setup>
import {inject, onMounted, reactive, ref, toRefs, watch, computed} from "vue" import { computed, inject, onMounted, reactive, ref, toRefs, watch } from "vue"
import {useStore} from "vuex" import { useStore } from "vuex"
import {SOCIAL_TYPE_PROMOTED_MESSAGE, SOCIAL_TYPE_WALL_POST} from "./constants" import { SOCIAL_TYPE_PROMOTED_MESSAGE, SOCIAL_TYPE_WALL_POST } from "./constants"
import useVuelidate from "@vuelidate/core" import useVuelidate from "@vuelidate/core"
import {required} from "@vuelidate/validators" import { required } from "@vuelidate/validators"
import {useI18n} from "vue-i18n" import { useI18n } from "vue-i18n"
import BaseCard from "../basecomponents/BaseCard.vue" import BaseCard from "../basecomponents/BaseCard.vue"
import BaseButton from "../basecomponents/BaseButton.vue" import BaseButton from "../basecomponents/BaseButton.vue"
import BaseFileUpload from "../basecomponents/BaseFileUpload.vue" import BaseFileUpload from "../basecomponents/BaseFileUpload.vue"
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue" import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVuelidate.vue" import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVuelidate.vue"
import axios from "axios" import axios from "axios"
import { useRoute } from 'vue-router' import { useRoute } from "vue-router"
const emit = defineEmits(['post-created']) const emit = defineEmits(["post-created"])
const store = useStore() const store = useStore()
const {t} = useI18n() const { t } = useI18n()
const route = useRoute() const route = useRoute()
const isPromotedPage = computed(() => { const isPromotedPage = computed(() => {
return route.query.filterType === 'promoted' return route.query.filterType === "promoted"
}) })
const user = inject('social-user') const user = inject("social-user")
const currentUser = store.getters['security/getUser'] const currentUser = store.getters["security/getUser"]
const userIsAdmin = store.getters['security/isAdmin'] const userIsAdmin = store.getters["security/isAdmin"]
const selectedFile = ref(null) const selectedFile = ref(null)
const postState = reactive({ const postState = reactive({
content: '', content: "",
attachment: null, attachment: null,
isPromoted: false, isPromoted: false,
textPlaceholder: '', textPlaceholder: "",
}) })
const {content, attachment, isPromoted, textPlaceholder} = toRefs(postState) const { content, attachment, isPromoted, textPlaceholder } = toRefs(postState)
const v$ = useVuelidate({ const v$ = useVuelidate(
content: {required}, {
}, postState) content: { required },
},
postState,
)
watch(() => user.value, () => { watch(
() => user.value,
() => {
showTextPlaceholder() showTextPlaceholder()
showCheckboxPromoted() showCheckboxPromoted()
}) },
)
watch(isPromotedPage, (newVal) => { watch(
isPromotedPage,
(newVal) => {
if (newVal) { if (newVal) {
postState.isPromoted = true postState.isPromoted = true
} }
}, { immediate: true }) },
{ immediate: true },
)
onMounted(() => { onMounted(() => {
showTextPlaceholder() showTextPlaceholder()
@ -98,14 +111,16 @@ onMounted(() => {
}) })
function showTextPlaceholder() { function showTextPlaceholder() {
postState.textPlaceholder = currentUser['@id'] === user.value['@id'] postState.textPlaceholder =
? t('What are you thinking about?') currentUser["@id"] === user.value["@id"]
: t('Write something to {0}', [user.value.fullName]) ? t("What are you thinking about?")
: t("Write something to {0}", [user.value.fullName])
} }
const allowCreatePromoted = ref(false) const allowCreatePromoted = ref(false)
function showCheckboxPromoted() { function showCheckboxPromoted() {
allowCreatePromoted.value = userIsAdmin && currentUser['@id'] === user.value['@id'] allowCreatePromoted.value = userIsAdmin && currentUser["@id"] === user.value["@id"]
} }
async function sendPost() { async function sendPost() {
@ -123,34 +138,34 @@ async function sendPost() {
} }
try { try {
await store.dispatch('socialpost/create', { await store.dispatch("socialpost/create", {
content: postState.content, content: postState.content,
type: postState.isPromoted ? SOCIAL_TYPE_PROMOTED_MESSAGE : SOCIAL_TYPE_WALL_POST, type: postState.isPromoted ? SOCIAL_TYPE_PROMOTED_MESSAGE : SOCIAL_TYPE_WALL_POST,
sender: currentUser['@id'], sender: currentUser["@id"],
userReceiver: currentUser['@id'] === user.value['@id'] ? null : user.value['@id'], userReceiver: currentUser["@id"] === user.value["@id"] ? null : user.value["@id"],
}) })
if (selectedFile.value) { if (selectedFile.value) {
const formData = new FormData() const formData = new FormData()
const post = store.state.socialpost.created const post = store.state.socialpost.created
let idUrl = post["@id"] let idUrl = post["@id"]
let parts = idUrl.split('/') let parts = idUrl.split("/")
let socialPostId = parts[parts.length - 1] let socialPostId = parts[parts.length - 1]
formData.append('file', selectedFile.value) formData.append("file", selectedFile.value)
formData.append('messageId', socialPostId) formData.append("messageId", socialPostId)
const endpoint = '/api/social_post_attachments' const endpoint = "/api/social_post_attachments"
const fileUploadResponse = await axios.post(endpoint, formData, { const fileUploadResponse = await axios.post(endpoint, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', "Content-Type": "multipart/form-data",
}, },
}) })
} }
postState.content = '' postState.content = ""
postState.attachment = null postState.attachment = null
postState.isPromoted = false postState.isPromoted = false
postState.isPromoted = isPromotedPage.value postState.isPromoted = isPromotedPage.value
v$.value.$reset() v$.value.$reset()
emit('post-created') emit("post-created")
} catch (error) { } catch (error) {
console.error("There was an error creating the post:", error) console.error("There was an error creating the post:", error)
} }

@ -13,24 +13,27 @@
<script setup> <script setup>
import SocialWallPost from "./SocialWallPost.vue" import SocialWallPost from "./SocialWallPost.vue"
import {inject, onMounted, reactive, ref, watch} from "vue" import { inject, onMounted, reactive, ref, watch } from "vue"
import Loading from "../Loading" import Loading from "../Loading"
import axios from "axios" import axios from "axios"
import {ENTRYPOINT} from "../../config/entrypoint" import { ENTRYPOINT } from "../../config/entrypoint"
import { useRoute } from 'vue-router' import { useRoute } from "vue-router"
import { SOCIAL_TYPE_PROMOTED_MESSAGE } from "./constants" import { SOCIAL_TYPE_PROMOTED_MESSAGE } from "./constants"
const user = inject("social-user")
const user = inject('social-user')
const postList = reactive([]) const postList = reactive([])
const isLoading = ref(false) const isLoading = ref(false)
const route = useRoute() const route = useRoute()
watch([() => user.value, () => route.query.filterType], () => { watch(
[() => user.value, () => route.query.filterType],
() => {
listPosts() listPosts()
}, { immediate: true }) },
{ immediate: true },
)
onMounted(listPosts) onMounted(listPosts)
function refreshPosts() { function refreshPosts() {
@ -38,28 +41,30 @@ function refreshPosts() {
} }
defineExpose({ defineExpose({
refreshPosts refreshPosts,
}) })
function listPosts() { function listPosts() {
postList.splice(0, postList.length) postList.splice(0, postList.length)
if (!user.value['@id']) { if (!user.value["@id"]) {
return return
} }
const filterType = route.query.filterType const filterType = route.query.filterType
isLoading.value = true isLoading.value = true
const params = { const params = {
socialwall_wallOwner: user.value['id'], socialwall_wallOwner: user.value["id"],
'order[sendDate]': 'desc', "order[sendDate]": "desc",
'exists[parent]': false, "exists[parent]": false,
} }
if (filterType === 'promoted') { if (filterType === "promoted") {
params.type = SOCIAL_TYPE_PROMOTED_MESSAGE params.type = SOCIAL_TYPE_PROMOTED_MESSAGE
} }
axios.get(ENTRYPOINT + 'social_posts', { params }) axios
.then(response => { .get(ENTRYPOINT + "social_posts", { params })
postList.push(...response.data['hydra:member']) .then((response) => {
postList.push(...response.data["hydra:member"])
}) })
.finally(() => { .finally(() => {
isLoading.value = false isLoading.value = false
@ -67,7 +72,7 @@ function listPosts() {
} }
function onPostDeleted(event) { function onPostDeleted(event) {
const index = postList.findIndex(post => post['@id'] === event['@id']) const index = postList.findIndex((post) => post["@id"] === event["@id"])
if (index >= 0) { if (index >= 0) {
postList.splice(index, 1) postList.splice(index, 1)
} }

@ -1,8 +1,17 @@
<template> <template>
<BaseCard plain> <BaseCard plain>
<div class="p-4 text-center user-profile-card"> <div class="p-4 text-center user-profile-card">
<img :src="user.illustrationUrl" class="mb-4 w-24 h-24 mx-auto rounded-full" alt="Profile picture" /> <img
<div v-if="visibility.firstname && visibility.lastname" class="text-xl font-bold">{{ user.fullName }}</div> :src="user.illustrationUrl"
alt="Profile picture"
class="mb-4 w-24 h-24 mx-auto rounded-full"
/>
<div
v-if="visibility.firstname && visibility.lastname"
class="text-xl font-bold"
>
{{ user.fullName }}
</div>
<div v-if="visibility.language && languageInfo"> <div v-if="visibility.language && languageInfo">
<template v-if="flagIconExists(languageInfo.code)"> <template v-if="flagIconExists(languageInfo.code)">
@ -14,34 +23,66 @@
</div> </div>
<div class="mt-4"> <div class="mt-4">
<p v-if="showFullProfile" class="flex items-center justify-center mb-2"> <p
<a v-if="visibility.email && user.email" :href="'/resources/messages/new'" class="flex items-center justify-center mb-2"> v-if="showFullProfile"
class="flex items-center justify-center mb-2"
>
<a
v-if="visibility.email && user.email"
:href="'/resources/messages/new'"
class="flex items-center justify-center mb-2"
>
<i class="mdi mdi-email-outline mr-2"></i> {{ user.email }} <i class="mdi mdi-email-outline mr-2"></i> {{ user.email }}
</a> </a>
</p> </p>
<p v-if="vCardUserLink" class="flex items-center justify-center mb-2"> <p
<a :href="vCardUserLink" target="_blank" class="flex items-center justify-center"> v-if="vCardUserLink"
<i class="mdi mdi-card-account-details-outline mr-2"></i> {{ t('Business card') }} class="flex items-center justify-center mb-2"
>
<a
:href="vCardUserLink"
class="flex items-center justify-center"
target="_blank"
>
<i class="mdi mdi-card-account-details-outline mr-2"></i> {{ t("Business card") }}
</a> </a>
</p> </p>
<p v-if="user.skype" class="flex items-center justify-center mb-2"> <p
v-if="user.skype"
class="flex items-center justify-center mb-2"
>
<i class="mdi mdi-skype mr-2"></i> Skype: {{ user.skype }} <i class="mdi mdi-skype mr-2"></i> Skype: {{ user.skype }}
</p> </p>
<p v-if="user.linkedin" class="flex items-center justify-center"> <p
v-if="user.linkedin"
class="flex items-center justify-center"
>
<i class="mdi mdi-linkedin mr-2"></i> LinkedIn: {{ user.linkedin }} <i class="mdi mdi-linkedin mr-2"></i> LinkedIn: {{ user.linkedin }}
</p> </p>
</div> </div>
<hr /> <hr />
<div v-if="extraInfo && extraInfo.length > 0" class="extra-info-container"> <div
v-if="extraInfo && extraInfo.length > 0"
class="extra-info-container"
>
<dl class="extra-info-list"> <dl class="extra-info-list">
<template v-for="item in extraInfo" :key="item.variable"> <template
v-for="item in extraInfo"
:key="item.variable"
>
<div v-if="item.value"> <div v-if="item.value">
<dt v-if="item.variable !== 'langue_cible'">{{ item.label }}:</dt> <dt v-if="item.variable !== 'langue_cible'">{{ item.label }}:</dt>
<dd v-if="item.variable !== 'langue_cible'">{{ item.value }}</dd> <dd v-if="item.variable !== 'langue_cible'">{{ item.value }}</dd>
<div v-if="item.variable === 'langue_cible'" class="language-target"> <div
<i v-if="flagIconExists(item.value)" :class="`flag-icon flag-icon-${item.value.toLowerCase()}`"></i> v-if="item.variable === 'langue_cible'"
class="language-target"
>
<i
v-if="flagIconExists(item.value)"
:class="`flag-icon flag-icon-${item.value.toLowerCase()}`"
></i>
</div> </div>
</div> </div>
</template> </template>
@ -50,30 +91,36 @@
<div v-if="chatEnabled && isUserOnline && !userOnlyInChat"> <div v-if="chatEnabled && isUserOnline && !userOnlyInChat">
<button @click="chatWith(user.id, user.fullName, user.isOnline, user.illustrationUrl)"> <button @click="chatWith(user.id, user.fullName, user.isOnline, user.illustrationUrl)">
{{ t('Chat') }} ({{ t('Online') }}) {{ t("Chat") }} ({{ t("Online") }})
</button> </button>
</div> </div>
<Divider /> <Divider />
<BaseButton v-if="isCurrentUser || isAdmin" :label="t('Edit profile')" type="primary" class="mt-4" @click="editProfile" icon="edit" /> <BaseButton
v-if="isCurrentUser || isAdmin"
:label="t('Edit profile')"
class="mt-4"
icon="edit"
type="primary"
@click="editProfile"
/>
</div> </div>
</BaseCard> </BaseCard>
</template> </template>
<script setup> <script setup>
import { computed, inject, onMounted, ref, watchEffect } from "vue" import { computed, inject, ref, watchEffect } from "vue"
import { useStore } from 'vuex' import { useStore } from "vuex"
import BaseCard from "../basecomponents/BaseCard.vue" import BaseCard from "../basecomponents/BaseCard.vue"
import BaseButton from "../basecomponents/BaseButton.vue" import BaseButton from "../basecomponents/BaseButton.vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import Divider from 'primevue/divider' import Divider from "primevue/divider"
import axios from "axios" import axios from "axios"
const { t } = useI18n() const { t } = useI18n()
const store = useStore() const store = useStore()
const user = inject('social-user') const user = inject("social-user")
const isCurrentUser = inject('is-current-user') const isCurrentUser = inject("is-current-user")
const isAdmin = ref(false) const isAdmin = ref(false)
const extraInfo = ref([]) const extraInfo = ref([])
const chatEnabled = ref(true) const chatEnabled = ref(true)
@ -81,7 +128,7 @@ const isUserOnline = ref(false)
const userOnlyInChat = ref(false) const userOnlyInChat = ref(false)
const showFullProfile = computed(() => isCurrentUser.value || isAdmin.value) const showFullProfile = computed(() => isCurrentUser.value || isAdmin.value)
const languageInfo = ref(null) const languageInfo = ref(null)
const vCardUserLink = ref('') const vCardUserLink = ref("")
const visibility = ref({}) const visibility = ref({})
watchEffect(() => { watchEffect(() => {
if (user.value && user.value.id) { if (user.value && user.value.id) {
@ -92,6 +139,7 @@ watchEffect(() => {
const editProfile = () => { const editProfile = () => {
window.location = "/account/edit" window.location = "/account/edit"
} }
async function fetchUserProfile(userId) { async function fetchUserProfile(userId) {
try { try {
const response = await axios.get(`/social-network/user-profile/${userId}`) const response = await axios.get(`/social-network/user-profile/${userId}`)
@ -104,17 +152,16 @@ async function fetchUserProfile(userId) {
userOnlyInChat.value = data.userOnlyInChat userOnlyInChat.value = data.userOnlyInChat
chatEnabled.value = data.chatEnabled chatEnabled.value = data.chatEnabled
} catch (error) { } catch (error) {
console.error('Error fetching user profile data:', error) console.error("Error fetching user profile data:", error)
} }
} }
function flagIconExists(code) { function flagIconExists(code) {
const mdiFlagIcons = ['us', 'fr', 'de', 'es', 'it', 'pl'] const mdiFlagIcons = ["us", "fr", "de", "es", "it", "pl"]
return mdiFlagIcons.includes(code.toLowerCase()) return mdiFlagIcons.includes(code.toLowerCase())
} }
function chatWith(userId, completeName, isOnline, avatarSmall) { function chatWith(userId, completeName, isOnline, avatarSmall) {}
}
isAdmin.value = user.value.role === 'admin' isAdmin.value = user.value.role === "admin"
</script> </script>

@ -1,7 +1,5 @@
<template> <template>
<div <div v-if="announcements.length > 0">
v-if="announcements.length > 0"
>
<SystemAnnouncementCard <SystemAnnouncementCard
v-for="announcement in announcements" v-for="announcement in announcements"
:key="announcement.id" :key="announcement.id"
@ -14,18 +12,18 @@
import { ref } from "vue" import { ref } from "vue"
import axios from "axios" import axios from "axios"
import SystemAnnouncementCard from './SystemAnnouncementCard.vue'; import SystemAnnouncementCard from "./SystemAnnouncementCard.vue"
const announcements = ref([]); const announcements = ref([])
axios axios
.get("/news/list") .get("/news/list")
.then((response) => { .then((response) => {
if (Array.isArray(response.data)) { if (Array.isArray(response.data)) {
announcements.value = response.data; announcements.value = response.data
} }
}) })
.catch(function (error) { .catch(function (error) {
console.log(error); console.log(error)
}); })
</script> </script>

@ -1,42 +1,76 @@
<template> <template>
<div class="social-group-show group-info text-center"> <div class="social-group-show group-info text-center">
<div class="group-header"> <div class="group-header">
<h1 class="group-title">{{ groupInfo?.title || '...' }}</h1> <h1 class="group-title">{{ groupInfo?.title || "..." }}</h1>
<p class="group-description">{{ groupInfo?.description }}</p> <p class="group-description">{{ groupInfo?.description }}</p>
</div> </div>
</div> </div>
<div v-if="!isLoading" class="discussion"> <div
<BaseButton @click="goBack" class="back-button mb-8" icon="back" type="button" :label="t('Back to the list')" /> v-if="!isLoading"
<h2>{{ firstMessageTitle || 'Discussion Thread' }}</h2> class="discussion"
>
<BaseButton
:label="t('Back to the list')"
class="back-button mb-8"
icon="back"
type="button"
@click="goBack"
/>
<h2>{{ firstMessageTitle || "Discussion Thread" }}</h2>
<div class="message-list mt-8"> <div class="message-list mt-8">
<MessageItem <MessageItem
v-for="message in messages" v-for="message in messages"
:key="message.id" :key="message.id"
:message="message" :current-user="user"
:currentUser="user"
:indentation="0" :indentation="0"
:isMainMessage="message.parentId === null || message.parentId === 0" :is-main-message="message.parentId === null || message.parentId === 0"
:isModerator="groupInfo.isModerator" :is-moderator="groupInfo.isModerator"
@replyMessage="openDialogForReply" :message="message"
@editMessage="openDialogForEdit" @delete-message="deleteMessage"
@deleteMessage="deleteMessage" @edit-message="openDialogForEdit"
@reply-message="openDialogForReply"
/> />
</div> </div>
</div> </div>
<Dialog header="Reply/Edit Message" v-model:visible="showMessageDialog" modal closable> <Dialog
v-model:visible="showMessageDialog"
closable
header="Reply/Edit Message"
modal
>
<form @submit.prevent="handleSubmit"> <form @submit.prevent="handleSubmit">
<BaseInputText v-if="isEditMode" id="title" :label="t('Title')" v-model="messageTitle" :isInvalid="titleError" /> <BaseInputText
<BaseTinyEditor v-model="messageContent" editor-id="messageEditor" title="Message" /> v-if="isEditMode"
<BaseFileUploadMultiple v-model="files" :label="t('Add files')" accept="image/png, image/jpeg" /> id="title"
<BaseButton type="button" :label="t('Send message')" icon="save" @click="handleSubmit" class="mt-8" /> v-model="messageTitle"
:is-invalid="titleError"
:label="t('Title')"
/>
<BaseTinyEditor
v-model="messageContent"
editor-id="messageEditor"
title="Message"
/>
<BaseFileUploadMultiple
v-model="files"
:label="t('Add files')"
accept="image/png, image/jpeg"
/>
<BaseButton
:label="t('Send message')"
class="mt-8"
icon="save"
type="button"
@click="handleSubmit"
/>
</form> </form>
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { onMounted, reactive, computed, ref, toRefs } from "vue" import { computed, onMounted, reactive, ref, toRefs } from "vue"
import { useRouter, useRoute } from "vue-router" import { useRoute, useRouter } from "vue-router"
import { useSocialInfo } from "../../composables/useSocialInfo" import { useSocialInfo } from "../../composables/useSocialInfo"
import axios from "axios" import axios from "axios"
import MessageItem from "./MessageItem.vue" import MessageItem from "./MessageItem.vue"
@ -60,18 +94,18 @@ const showMessageDialog = ref(false)
const isEditMode = ref(false) const isEditMode = ref(false)
const currentMessageId = ref(null) const currentMessageId = ref(null)
const state = reactive({ const state = reactive({
messageTitle: '', messageTitle: "",
messageContent: '', messageContent: "",
files: [], files: [],
titleError: false, titleError: false,
}) })
const { messageTitle, messageContent, files, titleError } = toRefs(state) const { messageTitle, messageContent, files, titleError } = toRefs(state)
const getIndentation = (message) => { const getIndentation = (message) => {
let indent = 0 let indent = 0
let parent = messages.value.find(m => m.id === message.parentId) let parent = messages.value.find((m) => m.id === message.parentId)
while (parent) { while (parent) {
indent += 30 indent += 30
parent = messages.value.find(m => m.id === parent.parentId) parent = messages.value.find((m) => m.id === parent.parentId)
} }
return `${indent}px` return `${indent}px`
} }
@ -82,9 +116,10 @@ const fetchMessages = async () => {
const response = await axios.get(`/social-network/group/${groupId}/discussion/${discussionId}/messages`) const response = await axios.get(`/social-network/group/${groupId}/discussion/${discussionId}/messages`)
messages.value = response.data messages.value = response.data
} catch (error) { } catch (error) {
console.error('Error fetching messages:', error) console.error("Error fetching messages:", error)
} }
} }
function openDialogForReply(message) { function openDialogForReply(message) {
isEditMode.value = false isEditMode.value = false
currentMessageId.value = message.id currentMessageId.value = message.id
@ -100,33 +135,33 @@ function openDialogForEdit(message) {
} }
async function handleSubmit() { async function handleSubmit() {
if (isEditMode.value && title.value.trim() === '') { if (isEditMode.value && title.value.trim() === "") {
titleError.value = true titleError.value = true
return return
} }
const filesArray = files.value const filesArray = files.value
const formData = new FormData() const formData = new FormData()
formData.append('action', isEditMode.value ? 'edit_message_group' : 'reply_message_group') formData.append("action", isEditMode.value ? "edit_message_group" : "reply_message_group")
formData.append('title', messageTitle.value) formData.append("title", messageTitle.value)
formData.append('content', messageContent.value) formData.append("content", messageContent.value)
if (isEditMode.value) { if (isEditMode.value) {
formData.append('messageId', currentMessageId.value) formData.append("messageId", currentMessageId.value)
} else { } else {
formData.append('parentId', currentMessageId.value) formData.append("parentId", currentMessageId.value)
} }
formData.append('userId', user.value.id) formData.append("userId", user.value.id)
formData.append('groupId', groupInfo.value.id) formData.append("groupId", groupInfo.value.id)
for (let i = 0; i < filesArray.length; i++) { for (let i = 0; i < filesArray.length; i++) {
formData.append('files[]', filesArray[i]) formData.append("files[]", filesArray[i])
} }
try { try {
await axios.post('/social-network/group-action', formData, { await axios.post("/social-network/group-action", formData, {
headers: { 'Content-Type': 'multipart/form-data' }, headers: { "Content-Type": "multipart/form-data" },
}) })
showMessageDialog.value = false showMessageDialog.value = false
await fetchMessages() await fetchMessages()
} catch (error) { } catch (error) {
console.error('Error submitting the form:', error) console.error("Error submitting the form:", error)
} }
} }
@ -137,15 +172,15 @@ const deleteMessage = async (message) => {
return return
} }
const data = { const data = {
action: 'delete_message_group', action: "delete_message_group",
messageId: message.id, messageId: message.id,
userId: user.value.id, userId: user.value.id,
groupId: groupInfo.value.id groupId: groupInfo.value.id,
} }
await axios.post('/social-network/group-action', data) await axios.post("/social-network/group-action", data)
await router.push({ name: 'UserGroupShow', params: { group_id: groupInfo.value.id } }) await router.push({ name: "UserGroupShow", params: { group_id: groupInfo.value.id } })
} catch (error) { } catch (error) {
console.error('Error deleting the message:', error) console.error("Error deleting the message:", error)
} }
} }
onMounted(() => { onMounted(() => {

@ -1,33 +1,55 @@
<template> <template>
<div class="group-members"> <div class="group-members">
<div v-if="groupInfo.isModerator" class="edit-members"> <div
v-if="groupInfo.isModerator"
class="edit-members"
>
<BaseButton <BaseButton
label="Edit members list"
type="primary"
class="edit-members-btn" class="edit-members-btn"
icon="pi pi-plus" icon="pi pi-plus"
label="Edit members list"
type="primary"
@click="editMembers" @click="editMembers"
/> />
</div> </div>
<div class="members-grid"> <div class="members-grid">
<div class="member-card" v-for="member in members" :key="member.id"> <div
v-for="member in members"
:key="member.id"
class="member-card"
>
<div class="member-avatar"> <div class="member-avatar">
<img v-if="member.avatar" :src="member.avatar" alt="Member avatar"> <img
<i v-else class="mdi mdi-account-circle-outline"></i> v-if="member.avatar"
:src="member.avatar"
alt="Member avatar"
/>
<i
v-else
class="mdi mdi-account-circle-outline"
></i>
</div> </div>
<div class="member-name"> <div class="member-name">
{{ member.name }} {{ member.name }}
<i v-if="member.isAdmin" class="mdi mdi-star-outline admin-icon"></i> <i
v-if="member.isAdmin"
class="mdi mdi-star-outline admin-icon"
></i>
</div>
<div
v-if="member.role"
class="member-role"
>
{{ member.role }}
</div> </div>
<div class="member-role" v-if="member.role">{{ member.role }}</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { onMounted, ref } from "vue"
import { useRoute } from 'vue-router' import { useRoute } from "vue-router"
import BaseButton from "../basecomponents/BaseButton.vue" import BaseButton from "../basecomponents/BaseButton.vue"
import axios from "axios" import axios from "axios"
import { useSocialInfo } from "../../composables/useSocialInfo" import { useSocialInfo } from "../../composables/useSocialInfo"
@ -40,24 +62,23 @@ const fetchMembers = async (groupId) => {
if (groupId.value) { if (groupId.value) {
try { try {
const response = await axios.get(`/api/usergroups/${groupId.value}/members`) const response = await axios.get(`/api/usergroups/${groupId.value}/members`)
members.value = response.data['hydra:member'].map(member => ({ members.value = response.data["hydra:member"].map((member) => ({
id: member.id, id: member.id,
name: member.username, name: member.username,
role: member.relationType === 1 ? 'Admin' : 'Member', role: member.relationType === 1 ? "Admin" : "Member",
avatar: member.pictureUri, avatar: member.pictureUri,
isAdmin: member.relationType === 1 isAdmin: member.relationType === 1,
})) }))
} catch (error) { } catch (error) {
console.error('Error fetching group members:', error) console.error("Error fetching group members:", error)
members.value = [] members.value = []
} }
} }
} }
const editMembers = () => { const editMembers = () => {}
}
onMounted(() => { onMounted(() => {
if (groupId) { if (groupId.value) {
fetchMembers(groupId) fetchMembers(groupId)
} }
}) })

@ -1,7 +1,8 @@
import { ref, readonly, onMounted } from "vue" import { onMounted, readonly, ref } from "vue"
import { useStore } from "vuex" import { useStore } from "vuex"
import { useRoute } from "vue-router" import { useRoute } from "vue-router"
import axios from "axios" import axios from "axios"
export function useSocialInfo() { export function useSocialInfo() {
const store = useStore() const store = useStore()
const route = useRoute() const route = useRoute()
@ -9,9 +10,9 @@ export function useSocialInfo() {
const isCurrentUser = ref(true) const isCurrentUser = ref(true)
const groupInfo = ref({ const groupInfo = ref({
isMember: false, isMember: false,
title: '', title: "",
description: '', description: "",
role: '' role: "",
}) })
const isGroup = ref(false) const isGroup = ref(false)
const isLoading = ref(true) const isLoading = ref(true)
@ -40,7 +41,7 @@ export function useSocialInfo() {
const loadUser = async () => { const loadUser = async () => {
try { try {
if (route.query.id) { if (route.query.id) {
user.value = await store.dispatch("user/load", '/api/users/' + route.query.id) user.value = await store.dispatch("user/load", "/api/users/" + route.query.id)
isCurrentUser.value = false isCurrentUser.value = false
} else { } else {
user.value = store.getters["security/getUser"] user.value = store.getters["security/getUser"]

@ -1,9 +1,12 @@
import axios from "axios" import axios from "axios"
/**
* @type {axios.AxiosInstance}
*/
const instance = axios.create({ const instance = axios.create({
baseURL: window.location.origin, baseURL: window.location.origin,
headers: { headers: {
'Accept': 'application/ld+json', Accept: "application/ld+json",
}, },
}) })

@ -0,0 +1,23 @@
export const USER_UNKNOWN = 0
// export const USER_RELATION_TYPE_UNKNOWN = 1;
// export const USER_RELATION_TYPE_PARENT = 2;
export const USER_RELATION_TYPE_FRIEND = 3
export const USER_RELATION_TYPE_GOODFRIEND = 4
// should be deprecated is useless
// export const USER_RELATION_TYPE_ENEMY = 5; // should be deprecated is useless
export const USER_RELATION_TYPE_DELETED = 6
export const USER_RELATION_TYPE_RRHH = 7
export const USER_RELATION_TYPE_BOSS = 8
export const USER_RELATION_TYPE_HRM_REQUEST = 9
export const USER_RELATION_TYPE_FRIEND_REQUEST = 10

@ -1,19 +1,18 @@
import isEmpty from 'lodash/isEmpty'; import isEmpty from "lodash/isEmpty"
import isString from 'lodash/isString'; import isString from "lodash/isString"
import isBoolean from 'lodash/isBoolean'; import isBoolean from "lodash/isBoolean"
import toInteger from 'lodash/toInteger'; import toInteger from "lodash/toInteger"
import { formatDateTime } from '../utils/dates'; import { formatDateTime } from "../utils/dates"
import NotificationMixin from './NotificationMixin'; import NotificationMixin from "./NotificationMixin"
import {ENTRYPOINT} from "../config/entrypoint"; import axios from "axios"
import axios from "axios";
export default { export default {
mixins: [NotificationMixin], mixins: [NotificationMixin],
data() { data() {
return { return {
pagination: { pagination: {
sortBy: 'resourceNode.title', sortBy: "resourceNode.title",
descending: false, descending: false,
page: 1, // page to be displayed page: 1, // page to be displayed
rowsPerPage: 10, // maximum displayed rows rowsPerPage: 10, // maximum displayed rows
@ -25,90 +24,91 @@ export default {
expandedFilter: false, expandedFilter: false,
options: { options: {
page: 1, page: 1,
itemsPerPage: 10 itemsPerPage: 10,
}, },
}; }
}, },
watch: { watch: {
$route() { $route() {
// react to route changes... // react to route changes...
this.resetList = true; this.resetList = true
let nodeId = this.$route.params['node']; let nodeId = this.$route.params["node"]
if (!isEmpty(nodeId)) { if (!isEmpty(nodeId)) {
let cid = toInteger(this.$route.query.cid); let cid = toInteger(this.$route.query.cid)
let sid = toInteger(this.$route.query.sid); let sid = toInteger(this.$route.query.sid)
let gid = toInteger(this.$route.query.gid); let gid = toInteger(this.$route.query.gid)
let id = '/api/resource_nodes/'+ nodeId; let id = "/api/resource_nodes/" + nodeId
const params = {id, cid, sid, gid}; const params = { id, cid, sid, gid }
this.findResourceNode(params); this.findResourceNode(params)
} }
this.onUpdateOptions(this.options); this.onUpdateOptions(this.options)
}, },
deletedItem(item) { deletedItem(item) {
this.showMessage(`${item['@id']} deleted.`); this.showMessage(`${item["@id"]} deleted.`)
}, },
deletedResource(item) { deletedResource(item) {
const message = this.$i18n && this.$i18n.t const message =
? this.$t('{resource} created', {'resource': item['resourceNode'].title}) this.$i18n && this.$i18n.t
: `${item['resourceNode'].title} created`; ? this.$t("{resource} created", { resource: item["resourceNode"].title })
this.showMessage(message); : `${item["resourceNode"].title} created`
this.onUpdateOptions(this.options); this.showMessage(message)
this.onUpdateOptions(this.options)
}, },
error(message) { error(message) {
message && this.showError(message); message && this.showError(message)
}, },
items() { items() {
this.options.totalItems = this.totalItems; this.options.totalItems = this.totalItems
} },
}, },
methods: { methods: {
onRequest(props) { onRequest(props) {
console.log('onRequest'); console.log("onRequest")
console.log(props); console.log(props)
const { page, rowsPerPage: itemsPerPage, sortBy, descending } = props.pagination; const { page, rowsPerPage: itemsPerPage, sortBy, descending } = props.pagination
const filter = props.filter; const filter = props.filter
this.nextPage = page; this.nextPage = page
if (isEmpty(this.nextPage)) { if (isEmpty(this.nextPage)) {
this.nextPage = 1; this.nextPage = 1
} }
let params = {}; let params = {}
if (itemsPerPage > 0) { if (itemsPerPage > 0) {
params = { ...params, itemsPerPage, page }; params = { ...params, itemsPerPage, page }
} }
if (sortBy) { if (sortBy) {
params[`order[${sortBy}]`] = descending ? "desc" : "asc"; params[`order[${sortBy}]`] = descending ? "desc" : "asc"
} }
if (this.$route.params.node) { if (this.$route.params.node) {
params[`resourceNode.parent`] = this.$route.params.node; params[`resourceNode.parent`] = this.$route.params.node
} }
this.resetList = true; this.resetList = true
this.getPage(params).then(() => { this.getPage(params).then(() => {
this.pagination.sortBy = sortBy; this.pagination.sortBy = sortBy
this.pagination.descending = descending; this.pagination.descending = descending
this.pagination.rowsPerPage = itemsPerPage; this.pagination.rowsPerPage = itemsPerPage
}); })
}, },
onUpdateOptions({ page, itemsPerPage, sortBy, sortDesc, totalItems } = {}) { onUpdateOptions({ page, itemsPerPage, sortBy, sortDesc, totalItems } = {}) {
console.log('ListMixin.js: onUpdateOptions'); console.log("ListMixin.js: onUpdateOptions")
this.resetList = true; this.resetList = true
let params = { let params = {
...this.filters ...this.filters,
} }
if (1 === this.filters['loadNode']) { if (1 === this.filters["loadNode"]) {
params[`resourceNode.parent`] = this.$route.params.node; params[`resourceNode.parent`] = this.$route.params.node
} }
/*if (this.$route.params.node) { /*if (this.$route.params.node) {
@ -116,170 +116,165 @@ export default {
}*/ }*/
if (itemsPerPage > 0) { if (itemsPerPage > 0) {
params = { ...params, itemsPerPage, page }; params = { ...params, itemsPerPage, page }
} }
// prime // prime
if (!isEmpty(sortBy)) { if (!isEmpty(sortBy)) {
params[`order[${sortBy}]`] = sortDesc ? 'desc' : 'asc' params[`order[${sortBy}]`] = sortDesc ? "desc" : "asc"
} }
let cid = toInteger(this.$route.query.cid); let cid = toInteger(this.$route.query.cid)
let sid = toInteger(this.$route.query.sid); let sid = toInteger(this.$route.query.sid)
let gid = toInteger(this.$route.query.gid); let gid = toInteger(this.$route.query.gid)
let type = this.$route.query.type; let type = this.$route.query.type
params = { ...params, cid, sid, gid, type }; params = { ...params, cid, sid, gid, type }
/*if (!isEmpty(sortBy) && !isEmpty(sortDesc)) { /*if (!isEmpty(sortBy) && !isEmpty(sortDesc)) {
params[`order[${sortBy[0]}]`] = sortDesc[0] ? 'desc' : 'asc' params[`order[${sortBy[0]}]`] = sortDesc[0] ? 'desc' : 'asc'
}*/ }*/
this.getPage(params).then(() => { this.getPage(params).then(() => {
this.options.sortBy = sortBy; this.options.sortBy = sortBy
this.options.sortDesc = sortDesc; this.options.sortDesc = sortDesc
this.options.itemsPerPage = itemsPerPage; this.options.itemsPerPage = itemsPerPage
this.options.totalItems = totalItems; this.options.totalItems = totalItems
}); })
}, },
fetchNewItems({ page, itemsPerPage, sortBy, sortDesc, totalItems } = {}) { fetchNewItems({ page, itemsPerPage, sortBy, sortDesc, totalItems } = {}) {
console.log('fetchNewItems'); console.log("fetchNewItems")
let params = { let params = {
...this.filters ...this.filters,
}; }
if (itemsPerPage > 0) { if (itemsPerPage > 0) {
params = { ...params, itemsPerPage, page }; params = { ...params, itemsPerPage, page }
} }
if (this.$route.params.node) { if (this.$route.params.node) {
params[`resourceNode.parent`] = this.$route.params.node; params[`resourceNode.parent`] = this.$route.params.node
} }
if (isString(sortBy) && isBoolean(sortDesc)) { if (isString(sortBy) && isBoolean(sortDesc)) {
//params[`order[${sortBy[0]}]`] = sortDesc[0] ? 'desc' : 'asc' //params[`order[${sortBy[0]}]`] = sortDesc[0] ? 'desc' : 'asc'
params[`order[${sortBy}]`] = sortDesc ? 'desc' : 'asc' params[`order[${sortBy}]`] = sortDesc ? "desc" : "asc"
} }
this.options.sortBy = sortBy; this.options.sortBy = sortBy
this.options.sortDesc = sortDesc; this.options.sortDesc = sortDesc
this.options.itemsPerPage = itemsPerPage; this.options.itemsPerPage = itemsPerPage
this.options.totalItems = totalItems; this.options.totalItems = totalItems
}, },
onSendFilter() { onSendFilter() {
console.log('onSendFilter'); console.log("onSendFilter")
this.resetList = true; this.resetList = true
this.pagination.page = 1; this.pagination.page = 1
this.onRequest({pagination: this.pagination}) this.onRequest({ pagination: this.pagination })
}, },
resetFilter() { resetFilter() {
console.log('resetFilter'); console.log("resetFilter")
this.filters = {}; this.filters = {}
this.pagination.page = 1; this.pagination.page = 1
this.onRequest({pagination: this.pagination}) this.onRequest({ pagination: this.pagination })
}, },
addHandler() { addHandler() {
console.log('addHandler'); console.log("addHandler")
let folderParams = this.$route.query; let folderParams = this.$route.query
this.$router.push({name: `${this.$options.servicePrefix}Create`, query: folderParams}); this.$router.push({ name: `${this.$options.servicePrefix}Create`, query: folderParams })
}, },
addDocumentHandler() { addDocumentHandler() {
let folderParams = this.$route.query; let folderParams = this.$route.query
this.$router.push({ name: `${this.$options.servicePrefix}CreateFile` , query: folderParams}); this.$router.push({ name: `${this.$options.servicePrefix}CreateFile`, query: folderParams })
}, },
uploadDocumentHandler() { uploadDocumentHandler() {
let folderParams = this.$route.query; let folderParams = this.$route.query
this.$router.push({ name: `${this.$options.servicePrefix}UploadFile` , query: folderParams}); this.$router.push({ name: `${this.$options.servicePrefix}UploadFile`, query: folderParams })
}, },
sharedDocumentHandler() { sharedDocumentHandler() {
let folderParams = this.$route.query; let folderParams = this.$route.query
this.filters['shared'] = 1; this.filters["shared"] = 1
this.filters['loadNode'] = 0; this.filters["loadNode"] = 0
delete this.filters['resourceNode.parent']; delete this.filters["resourceNode.parent"]
this.resetList = true; this.resetList = true
this.$router.push({ name: `${this.$options.servicePrefix}Shared` , query: folderParams}); this.$router.push({ name: `${this.$options.servicePrefix}Shared`, query: folderParams })
}, },
showHandler(item) { showHandler(item) {
console.log('listmixin showHandler'); console.log("listmixin showHandler")
let folderParams = this.$route.query; let folderParams = this.$route.query
console.log(item); console.log(item)
if (item) { if (item) {
folderParams['id'] = item['@id']; folderParams["id"] = item["@id"]
} }
this.$router.push({ this.$router.push({
name: `${this.$options.servicePrefix}Show`, name: `${this.$options.servicePrefix}Show`,
params: folderParams, params: folderParams,
query: folderParams query: folderParams,
}); })
}, },
handleClick(item) { handleClick(item) {
let folderParams = this.$route.query; let folderParams = this.$route.query
this.resetList = true; this.resetList = true
let resourceId = item['resourceNode']['id']; let resourceId = item["resourceNode"]["id"]
this.$route.params.node = resourceId; this.$route.params.node = resourceId
this.filters[`resourceNode.parent`] = resourceId; this.filters[`resourceNode.parent`] = resourceId
this.$router.push({ this.$router.push({
name: `${this.$options.servicePrefix}List`, name: `${this.$options.servicePrefix}List`,
params: {node: resourceId}, params: { node: resourceId },
query: folderParams, query: folderParams,
}); })
}, },
changeVisibilityHandler(item, slotProps) { changeVisibilityHandler(item, slotProps) {
let folderParams = this.$route.query; let folderParams = this.$route.query
folderParams['id'] = item['@id']; folderParams["id"] = item["@id"]
axios axios.put(item["@id"] + "/toggle_visibility", {}).then((response) => {
.put(item['@id'] + '/toggle_visibility', { let data = response.data
}) item["resourceLinkListFromEntity"] = data["resourceLinkListFromEntity"]
.then(response => {
let data = response.data;
item['resourceLinkListFromEntity'] = data['resourceLinkListFromEntity'];
}) })
;
}, },
editHandler(item) { editHandler(item) {
let folderParams = this.$route.query; let folderParams = this.$route.query
folderParams['id'] = item['@id']; folderParams["id"] = item["@id"]
if ('folder' === item.filetype || isEmpty(item.filetype)) { if ("folder" === item.filetype || isEmpty(item.filetype)) {
this.$router.push({ this.$router.push({
name: `${this.$options.servicePrefix}Update`, name: `${this.$options.servicePrefix}Update`,
params: { id: item['@id'] }, params: { id: item["@id"] },
query: folderParams query: folderParams,
}); })
} }
if ('file' === item.filetype) { if ("file" === item.filetype) {
folderParams['getFile'] = true; folderParams["getFile"] = true
if (item.resourceNode.resourceFile && if (
item.resourceNode.resourceFile &&
item.resourceNode.resourceFile.mimeType && item.resourceNode.resourceFile.mimeType &&
'text/html' === item.resourceNode.resourceFile.mimeType "text/html" === item.resourceNode.resourceFile.mimeType
) { ) {
//folderParams['getFile'] = true; //folderParams['getFile'] = true;
} }
this.$router.push({ this.$router.push({
name: `${this.$options.servicePrefix}UpdateFile`, name: `${this.$options.servicePrefix}UpdateFile`,
params: { id: item['@id'] }, params: { id: item["@id"] },
query: folderParams query: folderParams,
}); })
} }
}, },
deleteHandler(item) { deleteHandler(item) {
this.pagination.page = 1; this.pagination.page = 1
this.deleteItem(item).then(() => this.deleteItem(item).then(() => this.onRequest({ pagination: this.pagination }))
this.onRequest({pagination: this.pagination})
);
}, },
formatDateTime formatDateTime,
} },
}; }

@ -1,4 +1,4 @@
import axios from "axios" import api from "../config/api"
export default { export default {
/** /**
@ -6,7 +6,7 @@ export default {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
registerCampus: async (doNotListCampus) => { registerCampus: async (doNotListCampus) => {
await axios.post("/admin/register-campus", { await api.post("/admin/register-campus", {
donotlistcampus: doNotListCampus, donotlistcampus: doNotListCampus,
}) })
}, },
@ -15,7 +15,7 @@ export default {
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
findAnnouncements: async () => { findAnnouncements: async () => {
const { data } = await axios.get("/main/inc/ajax/admin.ajax.php?a=get_latest_news") const { data } = await api.get("/main/inc/ajax/admin.ajax.php?a=get_latest_news")
return data return data
}, },
@ -24,7 +24,7 @@ export default {
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
findVersion: async () => { findVersion: async () => {
const { data } = await axios.get("/main/inc/ajax/admin.ajax.php?a=version") const { data } = await api.get("/main/inc/ajax/admin.ajax.php?a=version")
return data return data
}, },
@ -42,7 +42,7 @@ export default {
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
findBlocks: async () => { findBlocks: async () => {
const { data } = await axios.get("/admin/index") const { data } = await api.get("/admin/index")
return data return data
}, },

@ -1,15 +1,62 @@
import api from "../config/api" import api from "../config/api"
/** export default {
/**
* @param {string} iri * @param {string} iri
* @returns {Promise<axios.AxiosResponse<any>>} * @param {Object} [params]
* @returns {Promise<Object>}
*/ */
async function find(iri) { async get(iri, params = {}) {
return await api.get(iri) const { data } = await api.get(iri, {
} params,
})
async function post(params) { return data
return await api.post("/api/resource_links", params) },
}
/**
* @param {string} endpoint
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async getCollection(endpoint, searchParams = {}) {
const { data } = await api.get(endpoint, {
params: searchParams,
})
return {
totalItems: data.totalItems,
items: data["hydra:member"],
}
},
/**
* @param {string} endpoint
* @param {Object} [params]
* @returns {Promise<Object>}
*/
async post(endpoint, params) {
const { data } = await api.post(endpoint, params)
export { find, post } return data
},
/**
* @param {string} endpoint
* @param {Object} params
* @returns {Promise<Object>}
*/
async postForm(endpoint, params) {
const { data } = await api.postForm(endpoint, params)
return data
},
/**
* @param {string} iri
* @returns {Promise<void>}
*/
async delete(iri) {
await api.delete(iri)
},
}

@ -0,0 +1,33 @@
import baseService from "./baseService"
/**
* @param {number} cId
* @param {Object} params
* @returns {Promise<Object>}
*/
async function findCourseHomeInro(cId, params) {
return await baseService.get(`/course/${cId}/getToolIntro`, params)
}
/**
* @param {number} cId
* @param {Object} params
* @returns {Promise<Object>}
*/
async function addToolIntro(cId, params) {
return baseService.post(`/course/${cId}/addToolIntro`, params)
}
/**
* @param {number} toolIntroId
* @returns {Promise<Object>}
*/
async function findById(toolIntroId) {
return await baseService.get(`/api/c_tool_intros/${toolIntroId}`)
}
export default {
findCourseHomeInro,
addToolIntro,
findById,
}

@ -0,0 +1,13 @@
import baseService from "./baseService"
/**
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(searchParams) {
return await baseService.getCollection("/api/course_rel_users", searchParams)
}
export default {
findAll,
}

@ -1,27 +1,28 @@
import { ENTRYPOINT } from "../config/entrypoint" import api from "../config/api"
import axios from "axios" import baseService from "./baseService"
export default {
find: baseService.get,
const API_URL = '/course';
const courseService = {
/** /**
* @param {number} courseId * @param {number} courseId
* @param {number=} sessionId * @param {number=} sessionId
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
loadTools: async (courseId, sessionId = 0) => { loadTools: async (courseId, sessionId = 0) => {
const { data } = await axios.get(ENTRYPOINT + `../course/${courseId}/home.json?sid=${sessionId}`) const { data } = await api.get(`/course/${courseId}/home.json?sid=${sessionId}`)
return data return data
}, },
loadCTools: async (courseId, sessionId = 0) => { loadCTools: async (courseId, sessionId = 0) => {
const { data } = await axios.get(ENTRYPOINT + 'c_tools', { const { data } = await api.get("/api/c_tools", {
params: { params: {
cid: courseId, cid: courseId,
sid: sessionId, sid: sessionId,
order: { order: {
position: 'asc' position: "asc",
} },
}, },
}) })
@ -36,13 +37,10 @@ const courseService = {
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
updateToolOrder: async (tool, newIndex, courseId, sessionId = 0) => { updateToolOrder: async (tool, newIndex, courseId, sessionId = 0) => {
const { data } = await axios.post( const { data } = await api.post(`/course/${courseId}/home.json?sid=${sessionId}`, {
ENTRYPOINT + `../course/${courseId}/home.json?sid=${sessionId}`,
{
index: newIndex, index: newIndex,
toolItem: tool, toolItem: tool,
} })
)
return data return data
}, },
@ -53,14 +51,11 @@ const courseService = {
* @returns {Promise<{Object}>} * @returns {Promise<{Object}>}
*/ */
loadHomeIntro: async (courseId, sessionId = 0) => { loadHomeIntro: async (courseId, sessionId = 0) => {
const { data } = await axios.get( const { data } = await api.get(`/course/${courseId}/getToolIntro`, {
ENTRYPOINT + `../course/${courseId}/getToolIntro`,
{
params: { params: {
sid: sessionId, sid: sessionId,
}, },
} })
)
return data return data
}, },
@ -71,14 +66,11 @@ const courseService = {
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
checkLegal: async (courseId, sessionId = 0) => { checkLegal: async (courseId, sessionId = 0) => {
const { data } = await axios.get( const { data } = await api.get(`/course/${courseId}/checkLegal.json`, {
`${API_URL}/${courseId}/checkLegal.json`,
{
params: { params: {
sid: sessionId, sid: sessionId,
}, },
} })
)
return data return data
}, },
@ -89,9 +81,10 @@ const courseService = {
* @returns {Promise<Object>} The server response after creating the course. * @returns {Promise<Object>} The server response after creating the course.
*/ */
createCourse: async (courseData) => { createCourse: async (courseData) => {
const response = await axios.post(`${API_URL}/create`, courseData); const response = await api.post(`/course/create`, courseData)
console.log('response create ::', response); console.log("response create ::", response)
return response.data;
return response.data
}, },
/** /**
@ -99,8 +92,9 @@ const courseService = {
* @returns {Promise<Array>} A list of available categories. * @returns {Promise<Array>} A list of available categories.
*/ */
getCategories: async () => { getCategories: async () => {
const response = await axios.get(`${API_URL}/categories`); const response = await api.get(`/course/categories`)
return response.data;
return response.data
}, },
/** /**
@ -109,14 +103,13 @@ const courseService = {
* @returns {Promise<Array>} A list of templates matching the search term. * @returns {Promise<Array>} A list of templates matching the search term.
*/ */
searchTemplates: async (searchTerm) => { searchTemplates: async (searchTerm) => {
const response = await axios.get(`${API_URL}/search_templates`, { const response = await api.get(`/course/search_templates`, {
params: { search: searchTerm } params: { search: searchTerm },
}); })
return response.data.items.map(item => ({
return response.data.items.map((item) => ({
name: item.name, name: item.name,
value: item.id value: item.id,
})); }))
}, },
} }
export default courseService

@ -7,6 +7,7 @@ export default {
*/ */
getGlossaryTerms: async (params) => { getGlossaryTerms: async (params) => {
const response = await axios.get(ENTRYPOINT + "glossaries", { params }) const response = await axios.get(ENTRYPOINT + "glossaries", { params })
return response.data return response.data
}, },
@ -15,6 +16,7 @@ export default {
*/ */
getGlossaryTerm: async (termId) => { getGlossaryTerm: async (termId) => {
const response = await axios.get(ENTRYPOINT + `glossaries/${termId}`) const response = await axios.get(ENTRYPOINT + `glossaries/${termId}`)
return response.data return response.data
}, },
@ -23,6 +25,7 @@ export default {
*/ */
createGlossaryTerm: async (data) => { createGlossaryTerm: async (data) => {
const response = await axios.post(ENTRYPOINT + `glossaries`, data) const response = await axios.post(ENTRYPOINT + `glossaries`, data)
return response.data return response.data
}, },
@ -32,6 +35,7 @@ export default {
*/ */
updateGlossaryTerm: async (termId, data) => { updateGlossaryTerm: async (termId, data) => {
const response = await axios.put(ENTRYPOINT + `glossaries/${termId}`, data) const response = await axios.put(ENTRYPOINT + `glossaries/${termId}`, data)
return response.data return response.data
}, },
@ -41,6 +45,7 @@ export default {
export: async (formData) => { export: async (formData) => {
const endpoint = `${ENTRYPOINT}glossaries/export` const endpoint = `${ENTRYPOINT}glossaries/export`
const response = await axios.post(endpoint, formData, { responsetype: "blob " }) const response = await axios.post(endpoint, formData, { responsetype: "blob " })
return response.data return response.data
}, },
@ -54,6 +59,7 @@ export default {
"Content-Type": "multipart/form-data", "Content-Type": "multipart/form-data",
}, },
}) })
return response.data return response.data
}, },
@ -63,6 +69,7 @@ export default {
exportToDocuments: async (data) => { exportToDocuments: async (data) => {
const endpoint = `${ENTRYPOINT}glossaries/export_to_documents` const endpoint = `${ENTRYPOINT}glossaries/export_to_documents`
const response = await axios.post(endpoint, data) const response = await axios.post(endpoint, data)
return response.data return response.data
}, },
@ -72,6 +79,7 @@ export default {
deleteTerm: async (termId) => { deleteTerm: async (termId) => {
const endpoint = `${ENTRYPOINT}glossaries/${termId}` const endpoint = `${ENTRYPOINT}glossaries/${termId}`
const response = await axios.delete(endpoint) const response = await axios.delete(endpoint)
return response.data return response.data
}, },
} }

@ -1,13 +1,13 @@
import {ENTRYPOINT} from "../config/entrypoint"; import { ENTRYPOINT } from "../config/entrypoint"
import axios from "axios"; import axios from "axios"
export default { export default {
/** /**
* @param {Object} params * @param {Object} params
*/ */
getLinks: async (params) => { getLinks: async (params) => {
const response = await axios.get(ENTRYPOINT + 'links/', {params}) const response = await axios.get(ENTRYPOINT + "links/", { params })
return response.data return response.data
}, },
@ -15,7 +15,8 @@ export default {
* @param {Number|String} linkId * @param {Number|String} linkId
*/ */
getLink: async (linkId) => { getLink: async (linkId) => {
const response = await axios.get(ENTRYPOINT + 'links/' + linkId + '/details/') const response = await axios.get(ENTRYPOINT + "links/" + linkId + "/details/")
return response.data return response.data
}, },
@ -26,6 +27,7 @@ export default {
const endpoint = `${ENTRYPOINT}links` const endpoint = `${ENTRYPOINT}links`
const response = await axios.post(endpoint, data) const response = await axios.post(endpoint, data)
return response.data return response.data
}, },
@ -35,9 +37,10 @@ export default {
*/ */
updateLink: async (linkId, data) => { updateLink: async (linkId, data) => {
const endpoint = `${ENTRYPOINT}links/${linkId}` const endpoint = `${ENTRYPOINT}links/${linkId}`
data.id = linkId; data.id = linkId
const response = await axios.put(endpoint, data) const response = await axios.put(endpoint, data)
return response.data return response.data
}, },
@ -47,7 +50,8 @@ export default {
*/ */
toggleLinkVisibility: async (linkId, visible) => { toggleLinkVisibility: async (linkId, visible) => {
const endpoint = `${ENTRYPOINT}links/${linkId}/toggle_visibility` const endpoint = `${ENTRYPOINT}links/${linkId}/toggle_visibility`
const response = await axios.put(endpoint, {visible}) const response = await axios.put(endpoint, { visible })
return response.data return response.data
}, },
@ -56,8 +60,9 @@ export default {
* @param {Number} position * @param {Number} position
*/ */
moveLink: async (linkId, position) => { moveLink: async (linkId, position) => {
const endpoint = `${ENTRYPOINT}links/${linkId}/move`; const endpoint = `${ENTRYPOINT}links/${linkId}/move`
const response = await axios.put(endpoint, {position}) const response = await axios.put(endpoint, { position })
return response.data return response.data
}, },
@ -67,19 +72,22 @@ export default {
deleteLink: async (linkId) => { deleteLink: async (linkId) => {
const endpoint = `${ENTRYPOINT}links/${linkId}` const endpoint = `${ENTRYPOINT}links/${linkId}`
const response = await axios.delete(endpoint) const response = await axios.delete(endpoint)
return response.data return response.data
}, },
getCategories: async (parentId) => { getCategories: async (parentId) => {
const response = await axios.get(`${ENTRYPOINT}link_categories?resourceNode.parent=${parentId}`) const response = await axios.get(`${ENTRYPOINT}link_categories?resourceNode.parent=${parentId}`)
return response.data['hydra:member']
return response.data["hydra:member"]
}, },
/** /**
* @param {Number|String} categoryId * @param {Number|String} categoryId
*/ */
getCategory: async (categoryId) => { getCategory: async (categoryId) => {
const response = await axios.get(ENTRYPOINT + 'link_categories/' + categoryId) const response = await axios.get(ENTRYPOINT + "link_categories/" + categoryId)
return response.data return response.data
}, },
@ -89,6 +97,7 @@ export default {
createCategory: async (data) => { createCategory: async (data) => {
const endpoint = `${ENTRYPOINT}link_categories` const endpoint = `${ENTRYPOINT}link_categories`
const response = await axios.post(endpoint, data) const response = await axios.post(endpoint, data)
return response.data return response.data
}, },
@ -99,6 +108,7 @@ export default {
updateCategory: async (categoryId, data) => { updateCategory: async (categoryId, data) => {
const endpoint = `${ENTRYPOINT}link_categories/${categoryId}` const endpoint = `${ENTRYPOINT}link_categories/${categoryId}`
const response = await axios.put(endpoint, data) const response = await axios.put(endpoint, data)
return response.data return response.data
}, },
@ -108,6 +118,7 @@ export default {
deleteCategory: async (categoryId) => { deleteCategory: async (categoryId) => {
const endpoint = `${ENTRYPOINT}link_categories/${categoryId}` const endpoint = `${ENTRYPOINT}link_categories/${categoryId}`
const response = await axios.delete(endpoint) const response = await axios.delete(endpoint)
return response.data return response.data
}, },
@ -116,8 +127,9 @@ export default {
* @param {Boolean} visible * @param {Boolean} visible
*/ */
toggleCategoryVisibility: async (categoryId, visible) => { toggleCategoryVisibility: async (categoryId, visible) => {
const endpoint = `${ENTRYPOINT}link_categories/${categoryId}/toggle_visibility`; const endpoint = `${ENTRYPOINT}link_categories/${categoryId}/toggle_visibility`
const response = await axios.put(endpoint, {visible}) const response = await axios.put(endpoint, { visible })
return response.data return response.data
}, },
@ -127,8 +139,9 @@ export default {
* @param linkId * @param linkId
*/ */
checkLink: async (url, linkId) => { checkLink: async (url, linkId) => {
const endpoint = `${ENTRYPOINT}links/${linkId}/check`; const endpoint = `${ENTRYPOINT}links/${linkId}/check`
const response = await axios.get(endpoint, { params: { url } }); const response = await axios.get(endpoint, { params: { url } })
return response.data;
return response.data
}, },
} }

@ -0,0 +1,26 @@
import baseService from "./baseService"
/**
* @param {Object} params
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(params) {
return await baseService.getCollection("/api/message_tags", params)
}
/**
* @param {string} userIri
* @param {string} searchTerm
* @returns {Promise<{totalItems, items}>}
*/
async function searchUserTags(userIri, searchTerm) {
return await findAll({
user: userIri,
tag: searchTerm,
})
}
export default {
findAll,
searchUserTags,
}

@ -1,10 +1,10 @@
import {ENTRYPOINT} from "../config/entrypoint" import { ENTRYPOINT } from "../config/entrypoint"
import axios from "axios" import axios from "axios"
export default { export default {
findAll: async () => { findAll: async () => {
const response = await axios.get(ENTRYPOINT + 'page_categories') const response = await axios.get(ENTRYPOINT + "page_categories")
return response.data['hydra:member'] return response.data["hydra:member"]
}, },
} }

@ -0,0 +1,11 @@
import api from "../config/api"
async function toogleStudentView() {
const { data } = await api.get("/toggle_student_view")
return data
}
export default {
toogleStudentView,
}

@ -1,5 +1,9 @@
import { post } from "./baseService" import api from "../config/api"
export default { export default {
post, async create(params) {
const { data } = await api.post("/api/resource_links", params)
return data
},
} }

@ -0,0 +1,13 @@
import baseService from "./baseService"
/**
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(searchParams) {
return await baseService.getCollection("/api/session_rel_course_rel_users", searchParams)
}
export default {
findAll,
}

@ -1,8 +1,9 @@
import { ENTRYPOINT } from "../config/entrypoint" import baseService from "./baseService"
import axios from "axios"
const sessionRelUserService = { async function findAll(params) {
findAll: (params) => axios.get(ENTRYPOINT + "session_rel_users", { params }).then((response) => response.data), return await baseService.getCollection("/api/session_rel_users", params)
} }
export default sessionRelUserService export default {
findAll,
}

@ -1,5 +1,13 @@
import { find } from "./baseService" import api from "../config/api"
export default { export default {
find /**
* @param {string} iri
* @returns {Promise<Object>}
*/
async find(iri) {
const { data } = await api.get(iri)
return data
},
} }

@ -1,35 +1,55 @@
import axios from 'axios'; import axios from "axios"
import baseService from "./baseService"
const API_URL = '/social-network'; const API_URL = "/social-network"
/**
* @param {string} postIri
* @returns {Promise<Object>}
*/
async function sendPostLike(postIri) {
return baseService.post(`${postIri}/like`)
}
/**
* @param {string} postIri
* @returns {Promise<Object>}
*/
async function sendPostDislike(postIri) {
return baseService.post(`${postIri}/dislike`)
}
export default { export default {
async fetchPersonalData(userId) { async fetchPersonalData(userId) {
try { try {
const response = await axios.get(`${API_URL}/personal-data/${userId}`); const response = await axios.get(`${API_URL}/personal-data/${userId}`)
return response.data.personalData;
return response.data.personalData
} catch (error) { } catch (error) {
console.error('Error fetching personal data:', error); console.error("Error fetching personal data:", error)
throw error; throw error
} }
}, },
async fetchTermsAndConditions(userId) { async fetchTermsAndConditions(userId) {
try { try {
const response = await axios.get(`${API_URL}/terms-and-conditions/${userId}`); const response = await axios.get(`${API_URL}/terms-and-conditions/${userId}`)
return response.data.terms;
return response.data.terms
} catch (error) { } catch (error) {
console.error('Error fetching terms and conditions:', error); console.error("Error fetching terms and conditions:", error)
throw error; throw error
} }
}, },
async fetchLegalStatus(userId) { async fetchLegalStatus(userId) {
try { try {
const response = await axios.get(`${API_URL}/legal-status/${userId}`); const response = await axios.get(`${API_URL}/legal-status/${userId}`)
return response.data;
return response.data
} catch (error) { } catch (error) {
console.error('Error fetching legal status:', error); console.error("Error fetching legal status:", error)
throw error; throw error
} }
}, },
@ -39,11 +59,12 @@ export default {
explanation, explanation,
userId, userId,
requestType, requestType,
}); })
return response.data;
return response.data
} catch (error) { } catch (error) {
console.error('Error submitting privacy request:', error); console.error("Error submitting privacy request:", error)
throw error; throw error
} }
}, },
@ -51,21 +72,23 @@ export default {
try { try {
const response = await axios.post(`${API_URL}/send-legal-term`, { const response = await axios.post(`${API_URL}/send-legal-term`, {
userId, userId,
}); })
return response.data;
return response.data
} catch (error) { } catch (error) {
console.error('Error accepting the term:', error); console.error("Error accepting the term:", error)
throw error; throw error
} }
}, },
async fetchInvitations(userId) { async fetchInvitations(userId) {
try { try {
const response = await axios.get(`${API_URL}/invitations/${userId}`); const response = await axios.get(`${API_URL}/invitations/${userId}`)
return response.data;
return response.data
} catch (error) { } catch (error) {
console.error('Error fetching invitations:', error); console.error("Error fetching invitations:", error)
throw error; throw error
} }
}, },
@ -74,13 +97,14 @@ export default {
const response = await axios.post(`${API_URL}/user-action`, { const response = await axios.post(`${API_URL}/user-action`, {
userId, userId,
targetUserId, targetUserId,
action: 'add_friend', action: "add_friend",
is_my_friend: true, is_my_friend: true,
}); })
return response.data;
return response.data
} catch (error) { } catch (error) {
console.error('Error accepting invitation:', error); console.error("Error accepting invitation:", error)
throw error; throw error
} }
}, },
@ -89,12 +113,13 @@ export default {
const response = await axios.post(`${API_URL}/user-action`, { const response = await axios.post(`${API_URL}/user-action`, {
userId, userId,
targetUserId, targetUserId,
action: 'deny_friend', action: "deny_friend",
}); })
return response.data;
return response.data
} catch (error) { } catch (error) {
console.error('Error denying invitation:', error); console.error("Error denying invitation:", error)
throw error; throw error
} }
}, },
@ -103,12 +128,13 @@ export default {
const response = await axios.post(`${API_URL}/group-action`, { const response = await axios.post(`${API_URL}/group-action`, {
userId, userId,
groupId, groupId,
action: 'accept', action: "accept",
}); })
return response.data;
return response.data
} catch (error) { } catch (error) {
console.error('Error accepting group invitation:', error); console.error("Error accepting group invitation:", error)
throw error; throw error
} }
}, },
@ -117,12 +143,13 @@ export default {
const response = await axios.post(`${API_URL}/group-action`, { const response = await axios.post(`${API_URL}/group-action`, {
userId, userId,
groupId, groupId,
action: 'deny', action: "deny",
}); })
return response.data;
return response.data
} catch (error) { } catch (error) {
console.error('Error denying group invitation:', error); console.error("Error denying group invitation:", error)
throw error; throw error
} }
}, },
@ -131,12 +158,19 @@ export default {
const response = await axios.post(`${API_URL}/group-action`, { const response = await axios.post(`${API_URL}/group-action`, {
userId, userId,
groupId, groupId,
action: 'join', action: "join",
}); })
return response.data;
return response.data
} catch (error) { } catch (error) {
console.error('Error joining the group:', error); console.error("Error joining the group:", error)
throw error; throw error
} }
}, },
};
sendPostLike,
sendPostDislike,
delete: baseService.delete,
}

@ -0,0 +1,50 @@
import baseService from "./baseService"
import { USER_RELATION_TYPE_FRIEND, USER_RELATION_TYPE_FRIEND_REQUEST } from "../constants/entity/userreluser"
/**
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(searchParams = {}) {
return await baseService.getCollection("/api/user_rel_users", searchParams)
}
/**
* @param {string} userIri
* @returns {Promise<Array<Object>>}
*/
async function getFriendList(userIri) {
const { items } = await findAll({
user: userIri,
relationType: [USER_RELATION_TYPE_FRIEND, USER_RELATION_TYPE_FRIEND_REQUEST],
})
return items
}
async function sendFriendRequest(userIri, friendIri) {
return await baseService.post("/api/user_rel_users", {
user: userIri,
friend: friendIri,
relationType: USER_RELATION_TYPE_FRIEND_REQUEST,
})
}
/**
* @param {string} userIri
* @param {string} searchTerm
* @returns {Promise<{totalItems, items}>}
*/
async function searchRelationshipByUsername(userIri, searchTerm) {
return await findAll({
user: userIri,
"friend.username": searchTerm,
})
}
export default {
findAll,
getFriendList,
sendFriendRequest,
searchRelationshipByUsername,
}

@ -0,0 +1,31 @@
import baseService from "./baseService"
/**
* @param {string} userIri
* @returns {Promise<Object>}
*/
async function find(userIri) {
return await baseService.get(userIri)
}
/**
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(searchParams) {
return await baseService.getCollection("/api/users", searchParams)
}
/**
* @param {string} username
* @returns {Promise<{totalItems, items}>}
*/
async function findByUsername(username) {
return await baseService.getCollection("/api/users", { username })
}
export default {
find,
findAll,
findByUsername,
}

@ -1,25 +1,57 @@
import axios from "axios" import baseService from "./baseService"
export default { export default {
/** /**
* @param {string} searchTerm * @param {string} searchTerm
* @returns {Promise<Object>} { totalItems, items } * @returns {Promise<{totalItems, items}>}
*/ */
search: async (searchTerm) => { search: async (searchTerm) => {
const response = {} return await baseService.getCollection("/api/usergroups/search", {
search: searchTerm,
try {
const { data } = await axios.get("/api/usergroups/search", {
params: { search: searchTerm },
}) })
},
/**
* @param {Object} params
* @returns {Promise<Object>}
*/
async createGroup(params) {
return await baseService.post("/api/usergroups", params)
},
/**
* @param {number} groupId
* @param {Object} params
* @returns {Promise<Object>}
*/
async uploadPicture(groupId, params) {
return await baseService.postForm(`/social-network/upload-group-picture/${groupId}`, params)
},
/**
* @returns {Promise<Array>}
*/
async listNewest() {
const { items } = await baseService.getCollection("/api/usergroup/list/newest")
return items
},
response.totalItems = data["hydra:totalItems"] /**
response.items = data["hydra:member"] * @returns {Promise<Array>}
} catch { */
response.totalItems = 0 async listPopular() {
response.items = [] const { items } = await baseService.getCollection("/api/usergroup/list/popular")
}
return items
},
/**
* @returns {Promise<Array>}
*/
async listMine() {
const { items } = await baseService.getCollection("/api/usergroup/list/my")
return response return items
}, },
} }

@ -1,6 +1,6 @@
import { defineStore } from "pinia" import { defineStore } from "pinia"
import { usePlatformConfig } from "./platformConfig" import { usePlatformConfig } from "./platformConfig"
import courseService from "../services/course" import courseService from "../services/courseService"
import sessionService from "../services/sessionService" import sessionService from "../services/sessionService"
import { computed, ref } from "vue" import { computed, ref } from "vue"
@ -56,7 +56,7 @@ export const useCidReqStore = defineStore("cidReq", () => {
return return
} }
course.value = await courseService.find(iri, { sid }).then((response) => response.json()) course.value = await courseService.find(iri, { sid })
} }
const setSessionByIri = async (iri) => { const setSessionByIri = async (iri) => {
@ -64,9 +64,7 @@ export const useCidReqStore = defineStore("cidReq", () => {
return return
} }
const { data } = await sessionService.find(iri); session.value = await sessionService.find(iri)
session.value = data
} }
const setCourseAndSessionByIri = async (courseIri, sId = 0) => { const setCourseAndSessionByIri = async (courseIri, sId = 0) => {

@ -1,6 +1,6 @@
import { defineStore } from "pinia" import { defineStore } from "pinia"
import axios from "axios" import axios from "axios"
import { computed, ref } from "vue" import { ref } from "vue"
export const useEnrolledStore = defineStore("enrolledStore", () => { export const useEnrolledStore = defineStore("enrolledStore", () => {
// Reactive state to track if the user is enrolled in courses or sessions // Reactive state to track if the user is enrolled in courses or sessions
@ -11,7 +11,7 @@ export const useEnrolledStore = defineStore("enrolledStore", () => {
async function checkEnrollments() { async function checkEnrollments() {
try { try {
const { data } = await axios.get("/course/check-enrollments") const { data } = await axios.get("/course/check-enrollments")
console.log('Check enrollments data:', data) console.log("Check enrollments data:", data)
isEnrolledInCourses.value = data.isEnrolledInCourses isEnrolledInCourses.value = data.isEnrolledInCourses
isEnrolledInSessions.value = data.isEnrolledInSessions isEnrolledInSessions.value = data.isEnrolledInSessions
} catch (error) { } catch (error) {
@ -28,6 +28,6 @@ export const useEnrolledStore = defineStore("enrolledStore", () => {
// Computed properties for reactivity // Computed properties for reactivity
isEnrolledInCourses, isEnrolledInCourses,
isEnrolledInSessions, isEnrolledInSessions,
initialize initialize,
}; }
}); })

@ -1,15 +1,15 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia"
import axios from "axios"; import axios from "axios"
import { computed, ref } from "vue" import { computed, ref } from "vue"
export const usePlatformConfig = defineStore("platformConfig", () => { export const usePlatformConfig = defineStore("platformConfig", () => {
const isLoading = ref(false); const isLoading = ref(false)
const settings = ref([]); const settings = ref([])
const studentView = ref('teacherview'); const studentView = ref("teacherview")
const plugins = ref([]) const plugins = ref([])
async function findSettingsRequest() { async function findSettingsRequest() {
isLoading.value = true; isLoading.value = true
try { try {
const { data } = await axios.get("/platform-config/list") const { data } = await axios.get("/platform-config/list")
@ -27,7 +27,7 @@ export const usePlatformConfig = defineStore("platformConfig", () => {
} }
async function initialize() { async function initialize() {
await findSettingsRequest(); await findSettingsRequest()
} }
const getSetting = computed( const getSetting = computed(
@ -44,5 +44,5 @@ export const usePlatformConfig = defineStore("platformConfig", () => {
initialize, initialize,
getSetting, getSetting,
isStudentViewActive, isStudentViewActive,
}; }
}); })

@ -1,8 +1,7 @@
import { defineStore } from "pinia"
import { defineStore } from 'pinia'
import axios from "axios" import axios from "axios"
export const useSocialStore = defineStore('social', { export const useSocialStore = defineStore("social", {
state: () => ({ state: () => ({
showFullProfile: false, showFullProfile: false,
}), }),
@ -16,7 +15,7 @@ export const useSocialStore = defineStore('social', {
console.error("Error checking user relation:", error) console.error("Error checking user relation:", error)
this.showFullProfile = false this.showFullProfile = false
} }
} },
}, },
getters: { getters: {

@ -14,13 +14,13 @@
/> />
<BaseColorPicker <BaseColorPicker
v-model="colorPrimaryButtonText" v-model="colorPrimaryButtonText"
:label="t('Primary color button text')"
:error="colorPrimaryButtonTextError" :error="colorPrimaryButtonTextError"
:label="t('Primary color button text')"
/> />
<BaseColorPicker <BaseColorPicker
v-model="colorPrimaryButtonAlternativeText" v-model="colorPrimaryButtonAlternativeText"
:label="t('Primary color button alternative text')"
:error="colorPrimaryButtonAlternativeTextError" :error="colorPrimaryButtonAlternativeTextError"
:label="t('Primary color button alternative text')"
/> />
</div> </div>
@ -35,8 +35,8 @@
/> />
<BaseColorPicker <BaseColorPicker
v-model="colorSecondaryButtonText" v-model="colorSecondaryButtonText"
:label="t('Secondary color button text')"
:error="colorSecondaryButtonTextError" :error="colorSecondaryButtonTextError"
:label="t('Secondary color button text')"
/> />
</div> </div>
@ -62,8 +62,8 @@
/> />
<BaseColorPicker <BaseColorPicker
v-model="colorSuccessButtonText" v-model="colorSuccessButtonText"
:label="t('Success color button text')"
:error="colorSuccessButtonTextError" :error="colorSuccessButtonTextError"
:label="t('Success color button text')"
/> />
</div> </div>
@ -78,8 +78,8 @@
/> />
<BaseColorPicker <BaseColorPicker
v-model="colorInfoButtonText" v-model="colorInfoButtonText"
:label="t('Info color button text')"
:error="colorInfoButtonTextError" :error="colorInfoButtonTextError"
:label="t('Info color button text')"
/> />
</div> </div>
@ -94,8 +94,8 @@
/> />
<BaseColorPicker <BaseColorPicker
v-model="colorWarningButtonText" v-model="colorWarningButtonText"
:label="t('Warning color button text')"
:error="colorWarningButtonTextError" :error="colorWarningButtonTextError"
:label="t('Warning color button text')"
/> />
</div> </div>
@ -164,15 +164,15 @@
<div class="flex flex-wrap mb-4 gap-3"> <div class="flex flex-wrap mb-4 gap-3">
<BaseButton <BaseButton
type="primary"
icon="send"
:label="t('Save')" :label="t('Save')"
icon="send"
type="primary"
@click="saveColors" @click="saveColors"
/> />
<BaseButton <BaseButton
type="black"
icon="cog"
:label="isAdvancedMode ? t('Hide advanced mode') : t('Show advanced mode')" :label="isAdvancedMode ? t('Hide advanced mode') : t('Show advanced mode')"
icon="cog"
type="black"
@click="isAdvancedMode = !isAdvancedMode" @click="isAdvancedMode = !isAdvancedMode"
/> />
</div> </div>
@ -184,69 +184,69 @@
<p class="mb-3 text-lg">{{ t("Buttons") }}</p> <p class="mb-3 text-lg">{{ t("Buttons") }}</p>
<div class="flex flex-row flex-wrap mb-3"> <div class="flex flex-row flex-wrap mb-3">
<BaseButton <BaseButton
class="mr-2 mb-2"
:label="t('Primary')" :label="t('Primary')"
type="primary" class="mr-2 mb-2"
icon="eye-on" icon="eye-on"
type="primary"
/> />
<BaseButton <BaseButton
class="mr-2 mb-2"
:label="t('Primary alternative')" :label="t('Primary alternative')"
type="primary-alternative" class="mr-2 mb-2"
icon="eye-on" icon="eye-on"
type="primary-alternative"
/> />
<BaseButton <BaseButton
class="mr-2 mb-2"
:label="t('Secondary')" :label="t('Secondary')"
type="secondary" class="mr-2 mb-2"
icon="eye-on" icon="eye-on"
type="secondary"
/> />
<BaseButton <BaseButton
class="mr-2 mb-2"
:label="t('Tertiary')" :label="t('Tertiary')"
type="black" class="mr-2 mb-2"
icon="eye-on" icon="eye-on"
type="black"
/> />
</div> </div>
<div class="flex flex-row flex-wrap mb-3"> <div class="flex flex-row flex-wrap mb-3">
<BaseButton <BaseButton
class="mr-2 mb-2"
:label="t('Success')" :label="t('Success')"
type="success" class="mr-2 mb-2"
icon="send" icon="send"
type="success"
/> />
<BaseButton <BaseButton
class="mr-2 mb-2"
:label="t('Info')" :label="t('Info')"
type="info" class="mr-2 mb-2"
icon="send" icon="send"
type="info"
/> />
<BaseButton <BaseButton
class="mr-2 mb-2"
:label="t('Warning')" :label="t('Warning')"
type="warning" class="mr-2 mb-2"
icon="send" icon="send"
type="warning"
/> />
<BaseButton <BaseButton
class="mr-2 mb-2"
:label="t('Danger')" :label="t('Danger')"
type="danger" class="mr-2 mb-2"
icon="delete" icon="delete"
type="danger"
/> />
</div> </div>
<div class="flex flex-row flex-wrap mb-3"> <div class="flex flex-row flex-wrap mb-3">
<BaseButton <BaseButton
class="mr-2 mb-2"
:label="t('Disabled')" :label="t('Disabled')"
type="primary" class="mr-2 mb-2"
icon="eye-on"
disabled disabled
icon="eye-on"
type="primary"
/> />
<BaseButton <BaseButton
class="mr-2 mb-2" class="mr-2 mb-2"
type="primary"
icon="cog" icon="cog"
only-icon only-icon
type="primary"
/> />
</div> </div>
</div> </div>
@ -256,10 +256,10 @@
<div class="flex flex-row gap-3"> <div class="flex flex-row gap-3">
<BaseButton <BaseButton
class="mr-3 mb-2" class="mr-3 mb-2"
type="primary"
icon="cog" icon="cog"
popup-identifier="menu"
only-icon only-icon
popup-identifier="menu"
type="primary"
@click="toggle" @click="toggle"
/> />
<BaseMenu <BaseMenu
@ -269,10 +269,6 @@
/> />
<BaseDropdown <BaseDropdown
v-model="dropdown" v-model="dropdown"
class="w-36"
input-id="dropdown"
option-label="label"
option-value="value"
:label="t('Dropdown')" :label="t('Dropdown')"
:options="[ :options="[
{ {
@ -288,7 +284,11 @@
value: 'option_3', value: 'option_3',
}, },
]" ]"
class="w-36"
input-id="dropdown"
name="dropdown" name="dropdown"
option-label="label"
option-value="value"
/> />
</div> </div>
</div> </div>
@ -310,8 +310,8 @@
<div class="mb-2"></div> <div class="mb-2"></div>
<BaseRadioButtons <BaseRadioButtons
v-model="radioValue" v-model="radioValue"
:options="radioButtons"
:initial-value="radioValue" :initial-value="radioValue"
:options="radioButtons"
name="radio" name="radio"
/> />
</div> </div>
@ -337,8 +337,8 @@
:model-value="null" :model-value="null"
/> />
<BaseInputText <BaseInputText
:label="t('This is a form with an error')"
:is-invalid="true" :is-invalid="true"
:label="t('This is a form with an error')"
:model-value="null" :model-value="null"
/> />
<BaseInputDate <BaseInputDate
@ -352,13 +352,13 @@
<p class="mb-3 text-lg">{{ t("Dialogs") }}</p> <p class="mb-3 text-lg">{{ t("Dialogs") }}</p>
<BaseButton <BaseButton
:label="t('Show dialog')" :label="t('Show dialog')"
type="black"
icon="eye-on" icon="eye-on"
type="black"
@click="isDialogVisible = true" @click="isDialogVisible = true"
/> />
<BaseDialogConfirmCancel <BaseDialogConfirmCancel
:title="t('Dialog example')"
:is-visible="isDialogVisible" :is-visible="isDialogVisible"
:title="t('Dialog example')"
@confirm-clicked="isDialogVisible = false" @confirm-clicked="isDialogVisible = false"
@cancel-clicked="isDialogVisible = false" @cancel-clicked="isDialogVisible = false"
/> />
@ -368,8 +368,8 @@
<div class="course-tool cursor-pointer"> <div class="course-tool cursor-pointer">
<div class="course-tool__link hover:primary-gradient hover:bg-primary-gradient/10"> <div class="course-tool__link hover:primary-gradient hover:bg-primary-gradient/10">
<span <span
class="course-tool__icon mdi mdi-bookshelf"
aria-hidden="true" aria-hidden="true"
class="course-tool__icon mdi mdi-bookshelf"
/> />
</div> </div>
<p class="course-tool__title">{{ t("Documents") }}</p> <p class="course-tool__title">{{ t("Documents") }}</p>
@ -436,7 +436,8 @@ const saveColors = async () => {
let colors = getColors() let colors = getColors()
try { try {
await axios.post("/api/color_themes", { await axios.post("/api/color_themes", {
title: themeTitle.value,variables: colors, title: themeTitle.value,
variables: colors,
}) })
showSuccessNotification(t("Colors updated")) showSuccessNotification(t("Colors updated"))
} catch (error) { } catch (error) {

@ -1,8 +1,6 @@
<template> <template>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<CalendarSectionHeader <CalendarSectionHeader @add-click="showAddEventDialog" />
@add-click="showAddEventDialog"
/>
<FullCalendar <FullCalendar
ref="cal" ref="cal"
@ -82,8 +80,9 @@
v-if="allowToEdit && showEditButton" v-if="allowToEdit && showEditButton"
:label="t('Edit')" :label="t('Edit')"
type="secondary" type="secondary"
icon="delete"
@click="dialog = true" @click="dialog = true"
icon="delete"/> />
</template> </template>
</Dialog> </Dialog>
@ -176,7 +175,8 @@ const currentUser = computed(() => store.getters["security/getUser"])
const { t } = useI18n() const { t } = useI18n()
const { appLocale } = useLocale() const { appLocale } = useLocale()
const route = useRoute() const route = useRoute()
const isGlobal = ref(route.query.type === 'global')
const isGlobal = ref(route.query.type === "global")
const selectedEventNotifications = ref([]) const selectedEventNotifications = ref([])
let currentEvent = null let currentEvent = null
@ -244,14 +244,14 @@ async function getCalendarEvents({ startStr, endStr }) {
params.gid = group.value.id params.gid = group.value.id
} }
if (route.query?.type === 'global') { if (route.query?.type === "global") {
params.type = 'global' params.type = "global"
} }
const calendarEvents = await cCalendarEventService.findAll({ params }).then((response) => response.json()) const calendarEvents = await cCalendarEventService.findAll({ params }).then((response) => response.json())
return calendarEvents["hydra:member"].map((event) => { return calendarEvents["hydra:member"].map((event) => {
let color = event.color || '#007BFF' let color = event.color || "#007BFF"
return { return {
...event, ...event,
@ -342,23 +342,23 @@ const calendarOptions = ref({
}) })
const currentContext = computed(() => { const currentContext = computed(() => {
if (route.query.type === 'global') { if (route.query.type === "global") {
return 'global' return "global"
} else if (course.value) { } else if (course.value) {
return 'course' return "course"
} else if (session.value) { } else if (session.value) {
return 'session' return "session"
} else { } else {
return 'personal' return "personal"
} }
}) })
const allowAction = (eventType) => { const allowAction = (eventType) => {
const contextRules = { const contextRules = {
global: ['global'], global: ["global"],
course: ['course'], course: ["course"],
session: ['session'], session: ["session"],
personal: ['personal'] personal: ["personal"],
} }
return contextRules[currentContext.value].includes(eventType) return contextRules[currentContext.value].includes(eventType)
@ -411,7 +411,7 @@ function confirmDelete() {
async function subscribeToEvent() { async function subscribeToEvent() {
try { try {
await resourceLinkService.post({ await resourceLinkService.create({
resourceNode: item.value.resourceNode["@id"], resourceNode: item.value.resourceNode["@id"],
user: currentUser.value["@id"], user: currentUser.value["@id"],
visibility: RESOURCE_LINK_PUBLISHED, visibility: RESOURCE_LINK_PUBLISHED,
@ -518,10 +518,13 @@ async function sendNotifications(eventId, notifications) {
await Promise.allSettled(promises) await Promise.allSettled(promises)
} }
watch(() => route.query.type, (newType) => { watch(
isGlobal.value = newType === 'global' () => route.query.type,
(newType) => {
isGlobal.value = newType === "global"
reFetch() reFetch()
}) },
)
watch( watch(
() => store.state.ccalendarevent.created, () => store.state.ccalendarevent.created,

@ -180,11 +180,11 @@
<CourseTool <CourseTool
v-for="(tool, index) in tools" v-for="(tool, index) in tools"
:key="'tool-' + index.toString()" :key="'tool-' + index.toString()"
:is-allowed-to-edit="isAllowedToEdit"
:change-visibility="changeVisibility" :change-visibility="changeVisibility"
:data-index="index" :data-index="index"
:data-tool="tool.title" :data-tool="tool.title"
:go-to-setting-course-tool="goToSettingCourseTool" :go-to-setting-course-tool="goToSettingCourseTool"
:is-allowed-to-edit="isAllowedToEdit"
:tool="tool" :tool="tool"
/> />
@ -249,9 +249,8 @@ const courseItems = ref([])
const routerTools = ["document", "link", "glossary", "agenda", "student_publication", "course_homepage"] const routerTools = ["document", "link", "glossary", "agenda", "student_publication", "course_homepage"]
courseService.loadCTools(course.value.id, session.value?.id) courseService.loadCTools(course.value.id, session.value?.id).then((cTools) => {
.then((cTools) => { tools.value = cTools.map((element) => {
tools.value = cTools.map(element => {
if (routerTools.includes(element.title)) { if (routerTools.includes(element.title)) {
element.to = element.url element.to = element.url
} }
@ -271,7 +270,7 @@ courseService.loadCTools(course.value.id, session.value?.id)
return false return false
}) })
.map(adminTool => ({ .map((adminTool) => ({
label: adminTool.tool.titleToShow, label: adminTool.tool.titleToShow,
url: adminTool.url, url: adminTool.url,
})) }))
@ -279,7 +278,7 @@ courseService.loadCTools(course.value.id, session.value?.id)
noAdminToolsIndex.reverse().forEach((element) => tools.value.splice(element, 1)) noAdminToolsIndex.reverse().forEach((element) => tools.value.splice(element, 1))
isCourseLoading.value = false isCourseLoading.value = false
}) })
courseService courseService
.loadTools(course.value.id, session.value?.id) .loadTools(course.value.id, session.value?.id)
@ -313,7 +312,15 @@ const setToolVisibility = (tool, visibility) => {
function changeVisibility(tool) { function changeVisibility(tool) {
axios axios
.post(ENTRYPOINT + "../r/course_tool/links/" + tool.resourceNode.id + "/change_visibility?cid=" + course.value.id + "&sid=" + session.value?.id) .post(
ENTRYPOINT +
"../r/course_tool/links/" +
tool.resourceNode.id +
"/change_visibility?cid=" +
course.value.id +
"&sid=" +
session.value?.id,
)
.then((response) => setToolVisibility(tool, response.data.visibility)) .then((response) => setToolVisibility(tool, response.data.visibility))
.catch((error) => console.log(error)) .catch((error) => console.log(error))
} }
@ -360,7 +367,7 @@ async function updateDisplayOrder(htmlItem, newIndex) {
const tool = htmlItem.dataset.tool const tool = htmlItem.dataset.tool
let toolItem = null let toolItem = null
if (typeof tools !== "undefined" && Array.isArray(tools.value)) { if (typeof tools.value !== "undefined" && Array.isArray(tools.value)) {
const toolList = tools.value const toolList = tools.value
toolItem = toolList.find((element) => element.title === tool) toolItem = toolList.find((element) => element.title === tool)
} else { } else {
@ -371,12 +378,7 @@ async function updateDisplayOrder(htmlItem, newIndex) {
console.log(toolItem, newIndex) console.log(toolItem, newIndex)
// Send the updated values to the server // Send the updated values to the server
await courseService.updateToolOrder( await courseService.updateToolOrder(toolItem, newIndex, course.value.id, session.value?.id)
toolItem,
newIndex,
course.value.id,
session.value?.id
)
} }
const isAllowedToEdit = ref(false) const isAllowedToEdit = ref(false)
@ -395,8 +397,6 @@ const onStudentViewChanged = async () => {
const allowEditToolVisibilityInSession = computed(() => { const allowEditToolVisibilityInSession = computed(() => {
const isInASession = session.value?.id const isInASession = session.value?.id
return isInASession return isInASession ? "true" === getSetting.value("course.allow_edit_tool_visibility_in_session") : true
? "true" === getSetting.value("course.allow_edit_tool_visibility_in_session")
: true
}) })
</script> </script>

@ -21,8 +21,8 @@ import useVuelidate from "@vuelidate/core"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import isEmpty from "lodash/isEmpty" import isEmpty from "lodash/isEmpty"
import { RESOURCE_LINK_PUBLISHED } from "../../components/resource_links/visibility.js" import { RESOURCE_LINK_PUBLISHED } from "../../components/resource_links/visibility.js"
import axios from "axios"
import { useCidReq } from "../../composables/cidReq" import { useCidReq } from "../../composables/cidReq"
import cToolIntroService from "../../services/cToolIntroService"
const servicePrefix = "ctoolintro" const servicePrefix = "ctoolintro"
@ -59,23 +59,16 @@ export default {
let ctoolId = route.params.courseTool let ctoolId = route.params.courseTool
async function getIntro() { async function getIntro() {
axios cToolIntroService
.get("/course/" + courseId + "/getToolIntro", { .findCourseHomeInro(courseId, {
params: {
cid: courseId, cid: courseId,
sid: sessionId, sid: sessionId,
},
}) })
.then((response) => { .then((intro) => {
if (response.data) { if (intro.introText) {
if (response.data.introText) { item.value.introText = intro.introText
item.value["introText"] = response.data.introText
}
} }
}) })
.catch(function (error) {
console.log(error)
})
} }
item.value["parentResourceNodeId"] = Number(route.query.parentResourceNodeId) item.value["parentResourceNodeId"] = Number(route.query.parentResourceNodeId)
@ -93,8 +86,8 @@ export default {
function onCreated(item) { function onCreated(item) {
//showNotification(t("Updated")) //showNotification(t("Updated"))
axios cToolIntroService
.post("/course/" + cid + "/addToolIntro", { .addToolIntro(cid, {
iid: item.iid, iid: item.iid,
cid: route.query.cid, cid: route.query.cid,
sid: route.query.sid, sid: route.query.sid,

@ -19,10 +19,9 @@ import UpdateMixin from "../../mixins/UpdateMixin"
import { ref } from "vue" import { ref } from "vue"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import useVuelidate from "@vuelidate/core" import useVuelidate from "@vuelidate/core"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import { RESOURCE_LINK_PUBLISHED } from "../../components/resource_links/visibility" import { RESOURCE_LINK_PUBLISHED } from "../../components/resource_links/visibility"
import { useCidReq } from "../../composables/cidReq" import { useCidReq } from "../../composables/cidReq"
import cToolIntroService from "../../services/cToolIntroService"
const servicePrefix = "ctoolintro" const servicePrefix = "ctoolintro"
@ -46,15 +45,15 @@ export default {
let ctoolintroId = route.query.ctoolintroIid let ctoolintroId = route.query.ctoolintroIid
// Get the current intro text. // Get the current intro text.
axios.get(ENTRYPOINT + "c_tool_intros/" + ctoolintroId) cToolIntroService
.then((response) => { .findById(ctoolintroId)
let data = response.data; .then((toolIntroInfo) => {
item.value["introText"] = data.introText; item.value["introText"] = toolIntroInfo.introText
item.value["parentResourceNodeId"] = Number(route.query.parentResourceNodeId); item.value["parentResourceNodeId"] = Number(route.query.parentResourceNodeId)
}) })
.catch(function (error) { .catch(function (error) {
console.error(error); console.error(error)
}); })
item.value["courseTool"] = "/api/c_tools/" + toolId item.value["courseTool"] = "/api/c_tools/" + toolId
item.value["resourceLinkList"] = [ item.value["resourceLinkList"] = [

@ -1,8 +1,8 @@
<template> <template>
<Toolbar <Toolbar
:handle-back="handleBack"
:handle-reset="resetForm" :handle-reset="resetForm"
:handle-submit="onSendFormData" :handle-submit="onSendFormData"
:handle-back="handleBack"
/> />
<div class="documents-layout"> <div class="documents-layout">
@ -19,8 +19,12 @@
:values="item" :values="item"
/> />
<Panel <Panel
v-if="$route.query.filetype === 'certificate' " v-if="$route.query.filetype === 'certificate'"
:header="$t('Create your certificate copy-pasting the following tags. They will be replaced in the document by their student-specific value:')" :header="
$t(
'Create your certificate copy-pasting the following tags. They will be replaced in the document by their student-specific value:',
)
"
> >
<div v-html="finalTags" /> <div v-html="finalTags" />
</Panel> </Panel>
@ -56,12 +60,12 @@ export default {
Loading, Loading,
Toolbar, Toolbar,
DocumentsForm, DocumentsForm,
Panel Panel,
}, },
mixins: [CreateMixin], mixins: [CreateMixin],
data() { data() {
const filetype = this.$route.query.filetype === 'certificate' ? 'certificate' : 'file'; const filetype = this.$route.query.filetype === "certificate" ? "certificate" : "file"
const finalTags = this.getCertificateTags(); const finalTags = this.getCertificateTags()
return { return {
item: { item: {
newDocument: true, // Used in FormNewDocument.vue to show the editor newDocument: true, // Used in FormNewDocument.vue to show the editor
@ -72,7 +76,7 @@ export default {
}, },
templates: [], templates: [],
finalTags, finalTags,
}; }
}, },
computed: { computed: {
...mapFields(["error", "isLoading", "created", "violations"]), ...mapFields(["error", "isLoading", "created", "violations"]),
@ -91,58 +95,59 @@ export default {
methods: { methods: {
handleBack() { handleBack() {
this.$router.back(); this.$router.back()
}, },
addTemplateToEditor(templateContent) { addTemplateToEditor(templateContent) {
this.item.contentFile = templateContent; this.item.contentFile = templateContent
}, },
fetchTemplates() { fetchTemplates() {
const courseId = this.$route.query.cid; const courseId = this.$route.query.cid
axios.get(`/template/all-templates/${courseId}`) axios
.then(response => { .get(`/template/all-templates/${courseId}`)
this.templates = response.data; .then((response) => {
console.log('Templates fetched successfully:', this.templates); this.templates = response.data
console.log("Templates fetched successfully:", this.templates)
})
.catch((error) => {
console.error("Error fetching templates:", error)
}) })
.catch(error => {
console.error('Error fetching templates:', error);
});
}, },
getCertificateTags(){ getCertificateTags() {
let finalTags = ""; let finalTags = ""
let tags = [ let tags = [
'((user_firstname))', "((user_firstname))",
'((user_lastname))', "((user_lastname))",
'((user_username))', "((user_username))",
'((gradebook_institution))', "((gradebook_institution))",
'((gradebook_sitename))', "((gradebook_sitename))",
'((teacher_firstname))', "((teacher_firstname))",
'((teacher_lastname))', "((teacher_lastname))",
'((official_code))', "((official_code))",
'((date_certificate))', "((date_certificate))",
'((date_certificate_no_time))', "((date_certificate_no_time))",
'((course_code))', "((course_code))",
'((course_title))', "((course_title))",
'((gradebook_grade))', "((gradebook_grade))",
'((certificate_link))', "((certificate_link))",
'((certificate_link_html))', "((certificate_link_html))",
'((certificate_barcode))', "((certificate_barcode))",
'((external_style))', "((external_style))",
'((time_in_course))', "((time_in_course))",
'((time_in_course_in_all_sessions))', "((time_in_course_in_all_sessions))",
'((start_date_and_end_date))', "((start_date_and_end_date))",
'((course_objectives))', "((course_objectives))",
]; ]
for (const tag of tags){ for (const tag of tags) {
finalTags += "<p class=\"m-0\">"+tag+"</p>" finalTags += '<p class="m-0">' + tag + "</p>"
} }
return finalTags; return finalTags
}, },
...mapActions('documents', ['createWithFormData', 'reset']) ...mapActions("documents", ["createWithFormData", "reset"]),
}, },
mounted() { mounted() {
this.fetchTemplates(); this.fetchTemplates()
}, },
}; }
</script> </script>

@ -181,8 +181,8 @@
/> />
<BaseButton <BaseButton
v-if="isCertificateMode" v-if="isCertificateMode"
:class="{ selected: slotProps.data.iid === defaultCertificateId }"
:icon="slotProps.data.iid === defaultCertificateId ? 'certificate-selected' : 'certificate-not-selected'" :icon="slotProps.data.iid === defaultCertificateId ? 'certificate-selected' : 'certificate-not-selected'"
:class="{ 'selected': slotProps.data.iid === defaultCertificateId }"
size="small" size="small"
type="slotProps.data.iid === defaultCertificateId ? 'success' : 'black'" type="slotProps.data.iid === defaultCertificateId ? 'success' : 'black'"
@click="selectAsDefaultCertificate(slotProps.data)" @click="selectAsDefaultCertificate(slotProps.data)"
@ -340,9 +340,9 @@
id="post-file" id="post-file"
:label="t('File upload')" :label="t('File upload')"
accept="image" accept="image"
model-value=""
size="small" size="small"
@file-selected="selectedFile = $event" @file-selected="selectedFile = $event"
model-value=""
/> />
</form> </form>
</BaseDialogConfirmCancel> </BaseDialogConfirmCancel>
@ -371,7 +371,6 @@ import DocumentAudioRecorder from "../../components/documents/DocumentAudioRecor
import { useNotification } from "../../composables/notification" import { useNotification } from "../../composables/notification"
import { useSecurityStore } from "../../store/securityStore" import { useSecurityStore } from "../../store/securityStore"
import prettyBytes from "pretty-bytes" import prettyBytes from "pretty-bytes"
import { ENTRYPOINT } from "../../config/entrypoint"
import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue" import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue"
const store = useStore() const store = useStore()
@ -383,7 +382,7 @@ const { t } = useI18n()
const { filters, options, onUpdateOptions, deleteItem } = useDatatableList("Documents") const { filters, options, onUpdateOptions, deleteItem } = useDatatableList("Documents")
const notification = useNotification() const notification = useNotification()
const { cid, sid, gid } = useCidReq() const { cid, sid, gid } = useCidReq()
const { isImage, isHtml } = useFileUtils(); const { isImage, isHtml } = useFileUtils()
const { relativeDatetime } = useFormatDate() const { relativeDatetime } = useFormatDate()
@ -416,13 +415,13 @@ const hasImageInDocumentEntries = computed(() => {
}) })
const isCertificateMode = computed(() => { const isCertificateMode = computed(() => {
return route.query.filetype === 'certificate'; return route.query.filetype === "certificate"
}) })
const defaultCertificateId = ref(null); const defaultCertificateId = ref(null)
const isHtmlFile = (fileData) => { const isHtmlFile = (fileData) => {
return isHtml(fileData); return isHtml(fileData)
} }
onMounted(() => { onMounted(() => {
@ -685,111 +684,111 @@ function recordedAudioNotSaved(error) {
async function selectAsDefaultCertificate(certificate) { async function selectAsDefaultCertificate(certificate) {
try { try {
const response = await axios.patch(`/gradebook/set_default_certificate/${cid}/${certificate.iid}`); const response = await axios.patch(`/gradebook/set_default_certificate/${cid}/${certificate.iid}`)
if (response.status === 200) { if (response.status === 200) {
loadDefaultCertificate() loadDefaultCertificate()
onUpdateOptions(options.value) onUpdateOptions(options.value)
notification.showSuccessNotification(t('Certificate set as default successfully')); notification.showSuccessNotification(t("Certificate set as default successfully"))
} }
} catch (error) { } catch (error) {
notification.showErrorNotification(t('Error setting certificate as default')); notification.showErrorNotification(t("Error setting certificate as default"))
} }
} }
async function loadDefaultCertificate() { async function loadDefaultCertificate() {
try { try {
const response = await axios.get(`/gradebook/default_certificate/${cid}`); const response = await axios.get(`/gradebook/default_certificate/${cid}`)
defaultCertificateId.value = response.data.certificateId; defaultCertificateId.value = response.data.certificateId
} catch (error) { } catch (error) {
if (error.response && error.response.status === 404) { if (error.response && error.response.status === 404) {
console.error('Default certificate not found.'); console.error("Default certificate not found.")
defaultCertificateId.value = null; defaultCertificateId.value = null
} else { } else {
console.error('Error loading the certificate', error); console.error("Error loading the certificate", error)
} }
} }
} }
const showTemplateFormModal = ref(false); const showTemplateFormModal = ref(false)
const selectedFile = ref(null); const selectedFile = ref(null)
const templateFormData = ref({ const templateFormData = ref({
title: '', title: "",
thumbnail: null, thumbnail: null,
}) })
const currentDocumentId = ref(null); const currentDocumentId = ref(null)
const isDocumentTemplate = async (documentId) => { const isDocumentTemplate = async (documentId) => {
try { try {
const response = await axios.get(`/template/document-templates/${documentId}/is-template`); const response = await axios.get(`/template/document-templates/${documentId}/is-template`)
return response.data.isTemplate; return response.data.isTemplate
} catch (error) { } catch (error) {
console.error('Error verifying the template status:', error); console.error("Error verifying the template status:", error)
return false return false
} }
} }
const deleteDocumentTemplate = async (documentId) => { const deleteDocumentTemplate = async (documentId) => {
try { try {
await axios.post(`/template/document-templates/${documentId}/delete`); await axios.post(`/template/document-templates/${documentId}/delete`)
onUpdateOptions(options.value); onUpdateOptions(options.value)
notification.showSuccessNotification(t('Template successfully deteled.')); notification.showSuccessNotification(t("Template successfully deteled."))
} catch (error) { } catch (error) {
console.error('Error deleting the template:', error); console.error("Error deleting the template:", error)
notification.showErrorNotification(t('Error deleting the template.')); notification.showErrorNotification(t("Error deleting the template."))
} }
} }
const getTemplateIcon = (documentId) => { const getTemplateIcon = (documentId) => {
const document = items.value.find((doc) => doc.iid === documentId); const document = items.value.find((doc) => doc.iid === documentId)
return document && document.template ? 'template-selected' : 'template-not-selected'; return document && document.template ? "template-selected" : "template-not-selected"
} }
const openTemplateForm = async (documentId) => { const openTemplateForm = async (documentId) => {
const isTemplate = await isDocumentTemplate(documentId); const isTemplate = await isDocumentTemplate(documentId)
if (isTemplate) { if (isTemplate) {
await deleteDocumentTemplate(documentId) await deleteDocumentTemplate(documentId)
onUpdateOptions(listaoptions.value); onUpdateOptions(options.value)
} else { } else {
currentDocumentId.value = documentId; currentDocumentId.value = documentId
showTemplateFormModal.value = true; showTemplateFormModal.value = true
} }
} }
const submitTemplateForm = async () => { const submitTemplateForm = async () => {
submitted.value = true; submitted.value = true
if (!templateFormData.value.title || !selectedFile.value) { if (!templateFormData.value.title || !selectedFile.value) {
notification.showErrorNotification(t('The title and thumbnail are required.')); notification.showErrorNotification(t("The title and thumbnail are required."))
return; return
} }
try { try {
const formData = new FormData() const formData = new FormData()
formData.append('title', templateFormData.value.title); formData.append("title", templateFormData.value.title)
formData.append('thumbnail', selectedFile.value); formData.append("thumbnail", selectedFile.value)
formData.append('refDoc', currentDocumentId.value); formData.append("refDoc", currentDocumentId.value)
formData.append('cid', cid); formData.append("cid", cid)
const response = await axios.post('/template/document-templates/create', formData, { const response = await axios.post("/template/document-templates/create", formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', "Content-Type": "multipart/form-data",
}, },
}); })
if (response.status === 200 || response.status === 201) { if (response.status === 200 || response.status === 201) {
notification.showSuccessNotification(t('Template created successfully.')); notification.showSuccessNotification(t("Template created successfully."))
templateFormData.value.title = ''; templateFormData.value.title = ""
selectedFile.value = null; selectedFile.value = null
showTemplateFormModal.value = false; showTemplateFormModal.value = false
onUpdateOptions(options.value); onUpdateOptions(options.value)
} else { } else {
notification.showErrorNotification(t('Error creating the template.')); notification.showErrorNotification(t("Error creating the template."))
} }
} catch (error) { } catch (error) {
console.error('Error submitting the form:', error) console.error("Error submitting the form:", error)
notification.showErrorNotification(t('Error submitting the form.')); notification.showErrorNotification(t("Error submitting the form."))
} }
}; }
</script> </script>

@ -33,7 +33,11 @@
/> />
<div class="field"> <div class="field">
<BaseTinyEditor v-model="item.content" editor-id="message" required /> <BaseTinyEditor
v-model="item.content"
editor-id="message"
required
/>
</div> </div>
<BaseButton <BaseButton
@ -51,14 +55,12 @@ import { useStore } from "vuex"
import MessageForm from "../../components/message/Form.vue" import MessageForm from "../../components/message/Form.vue"
import Loading from "../../components/Loading.vue" import Loading from "../../components/Loading.vue"
import { computed, ref } from "vue" import { computed, ref } from "vue"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import BaseAutocomplete from "../../components/basecomponents/BaseAutocomplete.vue" import BaseAutocomplete from "../../components/basecomponents/BaseAutocomplete.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue" import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import { MESSAGE_TYPE_INBOX } from "../../components/message/constants" import { MESSAGE_TYPE_INBOX } from "../../components/message/constants"
import userService from "../../services/user" import userService from "../../services/userService"
import BaseUserAvatar from "../../components/basecomponents/BaseUserAvatar.vue" import BaseUserAvatar from "../../components/basecomponents/BaseUserAvatar.vue"
import { useNotification } from "../../composables/notification" import { useNotification } from "../../composables/notification"
import { capitalize } from "lodash" import { capitalize } from "lodash"
@ -71,26 +73,13 @@ const { t } = useI18n()
const notification = useNotification() const notification = useNotification()
const asyncFind = (query) => { const asyncFind = async (query) => {
return axios const { items } = await userService.findByUsername(query)
.get(ENTRYPOINT + "users", {
params: {
username: query,
},
})
.then((response) => {
let data = response.data
return ( return items.map((member) => ({
data["hydra:member"]?.map((member) => ({
name: member.fullName, name: member.fullName,
value: member["@id"], value: member["@id"],
})) ?? [] }))
)
})
.catch(function (error) {
console.log(error)
})
} }
const currentUser = computed(() => store.getters["security/getUser"]) const currentUser = computed(() => store.getters["security/getUser"])
@ -201,7 +190,6 @@ if (route.query.send_to_user) {
userService userService
.find("/api/users/" + parseInt(route.query.send_to_user)) .find("/api/users/" + parseInt(route.query.send_to_user))
.then((response) => response.json())
.then((user) => { .then((user) => {
sendToUser.value = user sendToUser.value = user
@ -214,7 +202,11 @@ if (route.query.send_to_user) {
const prefill = capitalize(route.query.prefill) const prefill = capitalize(route.query.prefill)
item.value.title = t(prefill + "Title") item.value.title = t(prefill + "Title")
item.value.content = t(prefill + "Content", [user.firstname, currentUser.value.firstname, currentUser.value.firstname]) item.value.content = t(prefill + "Content", [
user.firstname,
currentUser.value.firstname,
currentUser.value.firstname,
])
} }
}) })
.catch((e) => notification.showErrorNotification(e)) .catch((e) => notification.showErrorNotification(e))

@ -144,8 +144,6 @@ import { useStore } from "vuex"
import Loading from "../../components/Loading.vue" import Loading from "../../components/Loading.vue"
import { computed, ref } from "vue" import { computed, ref } from "vue"
import isEmpty from "lodash/isEmpty" import isEmpty from "lodash/isEmpty"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import BaseButton from "../../components/basecomponents/BaseButton.vue" import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { useConfirm } from "primevue/useconfirm" import { useConfirm } from "primevue/useconfirm"
@ -154,6 +152,7 @@ import BaseChip from "../../components/basecomponents/BaseChip.vue"
import BaseAutocomplete from "../../components/basecomponents/BaseAutocomplete.vue" import BaseAutocomplete from "../../components/basecomponents/BaseAutocomplete.vue"
import { useFormatDate } from "../../composables/formatDate" import { useFormatDate } from "../../composables/formatDate"
import { useMessageRelUserStore } from "../../store/messageRelUserStore" import { useMessageRelUserStore } from "../../store/messageRelUserStore"
import messageTagService from "../../services/messageTagService"
const confirm = useConfirm() const confirm = useConfirm()
const { t } = useI18n() const { t } = useI18n()
@ -250,25 +249,14 @@ function createEvent() {
const foundTag = ref("") const foundTag = ref("")
function onSearchTags(query) { async function onSearchTags(query) {
isLoadingSelect.value = true isLoadingSelect.value = true
return axios const { items } = await messageTagService.searchUserTags(user["@id"], query)
.get(ENTRYPOINT + "message_tags", {
params: {
user: user["@id"],
tag: query,
},
})
.then((response) => {
isLoadingSelect.value = false
return response.data["hydra:member"]
})
.catch(function (error) {
isLoadingSelect.value = false isLoadingSelect.value = false
console.log(error)
}) return items
} }
async function onItemSelect({ value }) { async function onItemSelect({ value }) {

@ -1,13 +1,25 @@
<template> <template>
<StickyCourses /> <StickyCourses />
<hr> <hr />
<div v-if="isLoading" class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> <div
v-if="isLoading"
class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<Skeleton height="16rem" /> <Skeleton height="16rem" />
<Skeleton class="hidden md:block" height="16rem" /> <Skeleton
<Skeleton class="hidden lg:block" height="16rem" /> class="hidden md:block"
<Skeleton class="hidden xl:block" height="16rem" /> height="16rem"
/>
<Skeleton
class="hidden lg:block"
height="16rem"
/>
<Skeleton
class="hidden xl:block"
height="16rem"
/>
</div> </div>
<div <div
@ -25,30 +37,30 @@
</template> </template>
<script setup> <script setup>
import { onMounted, computed } from "vue"; import { onMounted, computed } from "vue"
import { useStore } from "vuex"; import { useStore } from "vuex"
import { useQuery } from "@vue/apollo-composable"; import { useQuery } from "@vue/apollo-composable"
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n"
import { GET_COURSE_REL_USER } from "../../../graphql/queries/CourseRelUser.js"; import { GET_COURSE_REL_USER } from "../../../graphql/queries/CourseRelUser.js"
import Skeleton from "primevue/skeleton"; import Skeleton from "primevue/skeleton"
import StickyCourses from "../../../views/user/courses/StickyCourses.vue"; import StickyCourses from "../../../views/user/courses/StickyCourses.vue"
import CourseCardList from "../../../components/course/CourseCardList.vue"; import CourseCardList from "../../../components/course/CourseCardList.vue"
import EmptyState from "../../../components/EmptyState"; import EmptyState from "../../../components/EmptyState"
const store = useStore(); const store = useStore()
const { t } = useI18n(); const { t } = useI18n()
let user = computed(() => store.getters["security/getUser"]); let user = computed(() => store.getters["security/getUser"])
const { result, loading, refetch } = useQuery(GET_COURSE_REL_USER, () => ({ const { result, loading, refetch } = useQuery(GET_COURSE_REL_USER, () => ({
user: user.value["@id"], user: user.value["@id"],
})); }))
const isLoading = computed(() => loading.value); const isLoading = computed(() => loading.value)
const courses = computed(() => result.value?.courseRelUsers.edges.map(({ node }) => node.course) ?? []); const courses = computed(() => result.value?.courseRelUsers.edges.map(({ node }) => node.course) ?? [])
onMounted(() => { onMounted(() => {
refetch(); refetch()
}); })
</script> </script>

@ -1,48 +1,87 @@
<template> <template>
<div class="invite-friends-container invite-friends"> <div class="invite-friends-container invite-friends">
<div class="invite-friends-header"> <div class="invite-friends-header">
<h2>{{ t('Invite Friends to Group') }}</h2> <h2>{{ t("Invite Friends to Group") }}</h2>
</div> </div>
<div class="invite-friends-body"> <div class="invite-friends-body">
<div class="friends-list"> <div class="friends-list">
<div class="list-header"> <div class="list-header">
<h3>{{ t('Available Friends') }}</h3> <h3>{{ t("Available Friends") }}</h3>
</div> </div>
<div class="list-content"> <div class="list-content">
<div class="friend-entry" v-for="friend in availableFriends" :key="friend.id"> <div
v-for="friend in availableFriends"
:key="friend.id"
class="friend-entry"
>
<div class="friend-info"> <div class="friend-info">
<img :src="friend.avatar" alt="avatar" class="friend-avatar" /> <img
:src="friend.avatar"
alt="avatar"
class="friend-avatar"
/>
<span class="friend-name">{{ friend.name }}</span> <span class="friend-name">{{ friend.name }}</span>
</div> </div>
<button @click="selectFriend(friend)" class="invite-btn">+</button> <button
class="invite-btn"
@click="selectFriend(friend)"
>
+
</button>
</div> </div>
</div> </div>
</div> </div>
<div class="selected-friends-list"> <div class="selected-friends-list">
<div class="list-header"> <div class="list-header">
<h3>{{ t('Selected Friends') }}</h3> <h3>{{ t("Selected Friends") }}</h3>
</div> </div>
<div class="list-content"> <div class="list-content">
<div class="friend-entry" v-for="friend in selectedFriends" :key="friend.id"> <div
v-for="friend in selectedFriends"
:key="friend.id"
class="friend-entry"
>
<div class="friend-info"> <div class="friend-info">
<img :src="friend.avatar" alt="avatar" class="friend-avatar" /> <img
:src="friend.avatar"
alt="avatar"
class="friend-avatar"
/>
<span class="friend-name">{{ friend.name }}</span> <span class="friend-name">{{ friend.name }}</span>
</div> </div>
<button @click="removeFriend(friend)" class="remove-btn">-</button> <button
class="remove-btn"
@click="removeFriend(friend)"
>
-
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="invite-friends-footer"> <div class="invite-friends-footer">
<button @click="sendInvitations" class="send-invites-btn">{{ t('Send Invitations') }}</button> <button
class="send-invites-btn"
@click="sendInvitations"
>
{{ t("Send Invitations") }}
</button>
</div> </div>
<div class="invited-friends-list mt-4"> <div class="invited-friends-list mt-4">
<div class="list-header"> <div class="list-header">
<h3>{{ t('Users Already Invited') }}</h3> <h3>{{ t("Users Already Invited") }}</h3>
</div> </div>
<div class="invited-users-grid mt-4"> <div class="invited-users-grid mt-4">
<div class="user-card" v-for="user in invitedFriends" :key="user.id"> <div
<img :src="user.avatar" alt="avatar" class="user-avatar" /> v-for="user in invitedFriends"
:key="user.id"
class="user-card"
>
<img
:src="user.avatar"
alt="avatar"
class="user-avatar"
/>
<span class="user-name">{{ user.name }}</span> <span class="user-name">{{ user.name }}</span>
</div> </div>
</div> </div>
@ -51,9 +90,9 @@
</template> </template>
<script setup> <script setup>
import { inject, onMounted, ref, computed} from "vue" import { computed, onMounted, ref } from "vue"
import { useI18n } from 'vue-i18n' import { useI18n } from "vue-i18n"
import { useRoute } from 'vue-router' import { useRoute } from "vue-router"
import { useStore } from "vuex" import { useStore } from "vuex"
import axios from "axios" import axios from "axios"
@ -79,7 +118,7 @@ const loadAvailableFriends = async () => {
const response = await axios.get(`/social-network/invite-friends/${userId}/${groupId}`) const response = await axios.get(`/social-network/invite-friends/${userId}/${groupId}`)
availableFriends.value = response.data.friends availableFriends.value = response.data.friends
} catch (error) { } catch (error) {
console.error('Error loading available friends:', error) console.error("Error loading available friends:", error)
} }
} }
onMounted(() => { onMounted(() => {
@ -88,16 +127,16 @@ onMounted(() => {
}) })
const sendInvitations = async () => { const sendInvitations = async () => {
const groupId = route.params.group_id const groupId = route.params.group_id
const userIds = selectedFriends.value.map(friend => friend.id) const userIds = selectedFriends.value.map((friend) => friend.id)
try { try {
await axios.post(`/social-network/add-users-to-group/${groupId}`, { await axios.post(`/social-network/add-users-to-group/${groupId}`, {
userIds, userIds,
}) })
console.log('Users added to group successfully!') console.log("Users added to group successfully!")
selectedFriends.value = [] selectedFriends.value = []
loadInvitedFriends() loadInvitedFriends()
} catch (error) { } catch (error) {
console.error('Error adding users to group:', error) console.error("Error adding users to group:", error)
} }
} }
const loadInvitedFriends = async () => { const loadInvitedFriends = async () => {
@ -106,7 +145,7 @@ const loadInvitedFriends = async () => {
const response = await axios.get(`/social-network/group/${groupId}/invited-users`) const response = await axios.get(`/social-network/group/${groupId}/invited-users`)
invitedFriends.value = response.data.invitedUsers invitedFriends.value = response.data.invitedUsers
} catch (error) { } catch (error) {
console.error('Error loading invited friends:', error) console.error("Error loading invited friends:", error)
} }
} }
</script> </script>

@ -3,47 +3,113 @@
<div class="p-col-12"> <div class="p-col-12">
<div class="p-d-flex p-jc-between p-ai-center p-mb-4"> <div class="p-d-flex p-jc-between p-ai-center p-mb-4">
<h1>Social groups</h1> <h1>Social groups</h1>
<Button label="Create a social group" icon="pi pi-plus" class="create-group-button" @click="showCreateGroupDialog = true" /> <Button
class="create-group-button"
icon="pi pi-plus"
label="Create a social group"
@click="showCreateGroupDialog = true"
/>
</div> </div>
<TabView class="social-group-tabs"> <TabView class="social-group-tabs">
<TabPanel header="Newest" headerClass="tab-header" :class="{ 'active-tab': activeTab === 'Newest' }"> <TabPanel
:class="{ 'active-tab': activeTab === 'Newest' }"
header="Newest"
header-class="tab-header"
>
<div class="group-list"> <div class="group-list">
<div class="group-item" v-for="group in newestGroups" :key="group['@id']"> <div
<img v-if="group.pictureUrl" :src="group.pictureUrl" class="group-image" alt="Group Image" /> v-for="group in newestGroups"
<i v-else class="mdi mdi-account-group-outline group-icon"></i> :key="group['@id']"
class="group-item"
>
<img
v-if="group.pictureUrl"
:src="group.pictureUrl"
alt="Group Image"
class="group-image"
/>
<i
v-else
class="mdi mdi-account-group-outline group-icon"
></i>
<div class="group-details"> <div class="group-details">
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ group.title }}</a> <a
:href="`/resources/usergroups/show/${extractGroupId(group)}`"
class="group-title"
>{{ group.title }}</a
>
<div class="group-info"> <div class="group-info">
<span class="group-member-count">{{ group.memberCount }} {{ t('Member') }}</span> <span class="group-member-count">{{ group.memberCount }} {{ t("Member") }}</span>
<span class="group-description">{{ group.description }}</span> <span class="group-description">{{ group.description }}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</TabPanel> </TabPanel>
<TabPanel header="Popular" headerClass="tab-header" :class="{ 'active-tab': activeTab === 'Popular' }"> <TabPanel
:class="{ 'active-tab': activeTab === 'Popular' }"
header="Popular"
header-class="tab-header"
>
<div class="group-list"> <div class="group-list">
<div class="group-item" v-for="group in popularGroups" :key="group['@id']"> <div
<img v-if="group.pictureUrl" :src="group.pictureUrl" class="group-image" alt="Group Image" /> v-for="group in popularGroups"
<i v-else class="mdi mdi-account-group-outline group-icon"></i> :key="group['@id']"
class="group-item"
>
<img
v-if="group.pictureUrl"
:src="group.pictureUrl"
alt="Group Image"
class="group-image"
/>
<i
v-else
class="mdi mdi-account-group-outline group-icon"
></i>
<div class="group-details"> <div class="group-details">
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ group.title }}</a> <a
:href="`/resources/usergroups/show/${extractGroupId(group)}`"
class="group-title"
>{{ group.title }}</a
>
<div class="group-info"> <div class="group-info">
<span class="group-member-count">{{ group.memberCount }} {{ t('Member') }}</span> <span class="group-member-count">{{ group.memberCount }} {{ t("Member") }}</span>
<span class="group-description">{{ group.description }}</span> <span class="group-description">{{ group.description }}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </TabPanel> </div>
<TabPanel header="My groups" headerClass="tab-header" :class="{ 'active-tab': activeTab === 'My groups' }"> </TabPanel>
<TabPanel
:class="{ 'active-tab': activeTab === 'My groups' }"
header="My groups"
header-class="tab-header"
>
<div class="group-list"> <div class="group-list">
<div class="group-item" v-for="group in myGroups" :key="group['@id']"> <div
<img v-if="group.pictureUrl" :src="group.pictureUrl" class="group-image" alt="Group Image" /> v-for="group in myGroups"
<i v-else class="mdi mdi-account-group-outline group-icon"></i> :key="group['@id']"
class="group-item"
>
<img
v-if="group.pictureUrl"
:src="group.pictureUrl"
alt="Group Image"
class="group-image"
/>
<i
v-else
class="mdi mdi-account-group-outline group-icon"
></i>
<div class="group-details"> <div class="group-details">
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ group.title }}</a> <a
:href="`/resources/usergroups/show/${extractGroupId(group)}`"
class="group-title"
>{{ group.title }}</a
>
<div class="group-info"> <div class="group-info">
<span class="group-member-count">{{ group.memberCount }} {{ t('Member') }}</span> <span class="group-member-count">{{ group.memberCount }} {{ t("Member") }}</span>
<span class="group-description">{{ group.description }}</span> <span class="group-description">{{ group.description }}</span>
</div> </div>
</div> </div>
@ -54,27 +120,32 @@
</div> </div>
</div> </div>
<Dialog header="Add" v-model:visible="showCreateGroupDialog" modal="true" closable="true"> <Dialog
v-model:visible="showCreateGroupDialog"
closable="true"
header="Add"
modal="true"
>
<form @submit.prevent="createGroup"> <form @submit.prevent="createGroup">
<div class="p-fluid"> <div class="p-fluid">
<BaseInputTextWithVuelidate <BaseInputTextWithVuelidate
v-model="groupForm.name" v-model="groupForm.name"
label="Name*"
:vuelidate-property="v$.groupForm.name" :vuelidate-property="v$.groupForm.name"
label="Name*"
/> />
<BaseInputTextWithVuelidate <BaseInputTextWithVuelidate
v-model="groupForm.description" v-model="groupForm.description"
label="Description"
:vuelidate-property="v$.groupForm.description" :vuelidate-property="v$.groupForm.description"
as="textarea" as="textarea"
label="Description"
rows="3" rows="3"
/> />
<BaseInputTextWithVuelidate <BaseInputTextWithVuelidate
v-model="groupForm.url" v-model="groupForm.url"
label="URL"
:vuelidate-property="v$.groupForm.url" :vuelidate-property="v$.groupForm.url"
label="URL"
/> />
<BaseFileUpload <BaseFileUpload
:label="t('Add a picture')" :label="t('Add a picture')"
@ -84,7 +155,13 @@
/> />
<div class="p-field mt-2"> <div class="p-field mt-2">
<label for="groupPermissions">Group Permissions</label> <label for="groupPermissions">Group Permissions</label>
<Dropdown id="groupPermissions" v-model="groupForm.permissions" :options="permissionsOptions" optionLabel="label" placeholder="Select Permission" /> <Dropdown
id="groupPermissions"
v-model="groupForm.permissions"
:options="permissionsOptions"
option-label="label"
placeholder="Select Permission"
/>
</div> </div>
<div class="p-field-checkbox mt-2"> <div class="p-field-checkbox mt-2">
<BaseCheckbox <BaseCheckbox
@ -94,52 +171,62 @@
name="leaveGroup" name="leaveGroup"
/> />
</div> </div>
</div> </div>
<Button label="Add" icon="pi pi-check" class="p-button-rounded p-button-text" @click="createGroup" /> <Button
<Button label="Close" class="p-button-text" @click="showCreateGroupDialog = false" /> class="p-button-rounded p-button-text"
icon="pi pi-check"
label="Add"
@click="createGroup"
/>
<Button
class="p-button-text"
label="Close"
@click="showCreateGroupDialog = false"
/>
</form> </form>
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import Button from 'primevue/button' import Button from "primevue/button"
import TabView from 'primevue/tabview' import TabView from "primevue/tabview"
import TabPanel from 'primevue/tabpanel' import TabPanel from "primevue/tabpanel"
import { ref, onMounted } from 'vue' import { onMounted, ref } from "vue"
import useVuelidate from '@vuelidate/core' import useVuelidate from "@vuelidate/core"
import { required } from '@vuelidate/validators' import { required } from "@vuelidate/validators"
import BaseInputTextWithVuelidate from "../../components/basecomponents/BaseInputTextWithVuelidate.vue" import BaseInputTextWithVuelidate from "../../components/basecomponents/BaseInputTextWithVuelidate.vue"
import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue" import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue"
import BaseCheckbox from "../../components/basecomponents/BaseCheckbox.vue" import BaseCheckbox from "../../components/basecomponents/BaseCheckbox.vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import axios from "axios" import usergroupService from "../../services/usergroupService"
import { ENTRYPOINT } from "../../config/entrypoint"
const {t} = useI18n() const { t } = useI18n()
const newestGroups = ref([]) const newestGroups = ref([])
const popularGroups = ref([]) const popularGroups = ref([])
const myGroups = ref([]) const myGroups = ref([])
const activeTab = ref('Newest') const activeTab = ref("Newest")
const showCreateGroupDialog = ref(false) const showCreateGroupDialog = ref(false)
const selectedFile = ref(null) const selectedFile = ref(null)
const groupForm = ref({ const groupForm = ref({
name: '', name: "",
description: '', description: "",
url: '', url: "",
picture: null, picture: null,
}) })
const v$ = useVuelidate({ const v$ = useVuelidate(
{
groupForm: { groupForm: {
name: { required }, name: { required },
description: {}, description: {},
url: {}, url: {},
} },
}, { groupForm }) },
{ groupForm },
)
const permissionsOptions = [ const permissionsOptions = [
{ label: 'Open', value: '1' }, { label: "Open", value: "1" },
{ label: 'Closed', value: '2' }, { label: "Closed", value: "2" },
] ]
const createGroup = async () => { const createGroup = async () => {
v$.value.$touch() v$.value.$touch()
@ -153,60 +240,38 @@ const createGroup = async () => {
groupType: 1, groupType: 1,
} }
try { try {
const response = await axios.post(ENTRYPOINT + 'usergroups', groupData, { const newGroup = await usergroupService.createGroup(groupData)
headers: {
'Content-Type': 'application/json',
},
})
if (selectedFile.value && response.data && response.data.id) { if (selectedFile.value && newGroup && newGroup.id) {
const formData = new FormData() await usergroupService.uploadPicture(newGroup.id, {
formData.append('picture', selectedFile.value) picture: selectedFile.value,
await axios.post(`/social-network/upload-group-picture/${response.data.id}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}) })
} }
showCreateGroupDialog.value = false showCreateGroupDialog.value = false
resetForm() resetForm()
await updateGroupsList() updateGroupsList()
} catch (error) { } catch (error) {
console.error('Failed to create group or upload picture:', error.response.data) console.error("Failed to create group or upload picture:", error.response.data)
} }
} }
} }
const fetchGroups = async (endpoint) => {
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'])
return data['hydra:member'] const updateGroupsList = () => {
} catch (error) { usergroupService.listNewest().then((newest) => (newestGroups.value = newest))
console.error(error) usergroupService.listPopular().then((popular) => (popularGroups.value = popular))
return [] usergroupService.listMine().then((mine) => (myGroups.value = mine))
}
}
const updateGroupsList = async () => {
newestGroups.value = await fetchGroups('usergroup/list/newest')
popularGroups.value = await fetchGroups('usergroup/list/popular')
myGroups.value = await fetchGroups('usergroup/list/my')
} }
const extractGroupId = (group) => { const extractGroupId = (group) => {
const match = group['@id'].match(/\/api\/usergroup\/(\d+)/) const match = group["@id"].match(/\/api\/usergroup\/(\d+)/)
return match ? match[1] : null return match ? match[1] : null
} }
const redirectToGroupDetails = (groupId) => { const redirectToGroupDetails = (groupId) => {
router.push({ name: 'UserGroupShow', params: { group_id: groupId } }) router.push({ name: "UserGroupShow", params: { group_id: groupId } })
} }
onMounted(async () => { onMounted(async () => {
await updateGroupsList() updateGroupsList()
}) })
const closeDialog = () => { const closeDialog = () => {
@ -214,11 +279,11 @@ const closeDialog = () => {
} }
const resetForm = () => { const resetForm = () => {
groupForm.value = { groupForm.value = {
name: '', name: "",
description: '', description: "",
url: '', url: "",
picture: null, picture: null,
permissions: '', permissions: "",
allowLeave: false, allowLeave: false,
} }
selectedFile.value = null selectedFile.value = null

@ -1,5 +1,8 @@
<template> <template>
<h2 v-t="'Add friends'" class="mr-auto" /> <h2
v-t="'Add friends'"
class="mr-auto"
/>
<hr /> <hr />
<BaseToolbar> <BaseToolbar>
<BaseButton <BaseButton
@ -33,42 +36,37 @@
</div> </div>
</div> </div>
</template> </template>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
<script setup> <script setup>
import { computed, ref, onMounted } from 'vue' import { onMounted, ref } from "vue"
import { useRouter, useRoute } from 'vue-router' import { useRoute, useRouter } from "vue-router"
import { useStore } from 'vuex' import { useStore } from "vuex"
import { useI18n } from 'vue-i18n' import { useI18n } from "vue-i18n"
import { useNotification } from '../../composables/notification' import { useNotification } from "../../composables/notification"
import VueMultiselect from 'vue-multiselect' import VueMultiselect from "vue-multiselect"
import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue" import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue" import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { ENTRYPOINT } from '../../config/entrypoint' import userService from "../../services/userService"
import axios from "axios" import userRelUserService from "../../services/userRelUserService"
const store = useStore() const store = useStore()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const { t } = useI18n() const { t } = useI18n()
const { showSuccessNotification, showErrorNotification } = useNotification() const { showSuccessNotification, showErrorNotification } = useNotification()
const user = store.getters['security/getUser'] const user = store.getters["security/getUser"]
const isAuthenticated = computed(() => store.getters['security/isAuthenticated'])
const isAdmin = computed(() => store.getters['security/isAdmin'])
const currentUser = computed(() => store.getters['security/getUser'])
const users = ref([]) const users = ref([])
const isLoadingSelect = ref(false) const isLoadingSelect = ref(false)
const searchQuery = ref('') const searchQuery = ref("")
const asyncFind = (query) => { const asyncFind = (query) => {
if (query.toString().length < 3) return if (query.toString().length < 3) return
isLoadingSelect.value = true isLoadingSelect.value = true
axios.get(`${ENTRYPOINT}users`, { params: { username: query } })
.then((response) => { userService
users.value = response.data['hydra:member'] .findByUsername(query)
}) .then(({ items }) => (users.value = items))
.catch((error) => { .catch((error) => {
console.error('Error fetching users:', error) console.error("Error fetching users:", error)
}) })
.finally(() => { .finally(() => {
isLoadingSelect.value = false isLoadingSelect.value = false
@ -77,17 +75,15 @@ const asyncFind = (query) => {
const addFriend = (friend) => { const addFriend = (friend) => {
isLoadingSelect.value = true isLoadingSelect.value = true
axios.post(`${ENTRYPOINT}user_rel_users`, {
user: user['@id'], userRelUserService
friend: friend['@id'], .sendFriendRequest(user["@id"], friend["@id"])
relationType: 10,
})
.then(() => { .then(() => {
showSuccessNotification(t('Friend request sent successfully')) showSuccessNotification(t("Friend request sent successfully"))
}) })
.catch((error) => { .catch((error) => {
showErrorNotification(t('Failed to send friend request')) showErrorNotification(t("Failed to send friend request"))
console.error('Error adding friend:', error) console.error("Error adding friend:", error)
}) })
.finally(() => { .finally(() => {
isLoadingSelect.value = false isLoadingSelect.value = false
@ -95,8 +91,9 @@ const addFriend = (friend) => {
} }
const goToBack = () => { const goToBack = () => {
router.push({ name: 'UserRelUserList' }) router.push({ name: "UserRelUserList" })
} }
// Lifecycle hooks // Lifecycle hooks
onMounted(() => { onMounted(() => {
if (route.query.search) { if (route.query.search) {
@ -105,3 +102,5 @@ onMounted(() => {
} }
}) })
</script> </script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

@ -1,5 +1,8 @@
<template> <template>
<h2 v-t="'Search users for friends'" class="mr-auto" /> <h2
v-t="'Search users for friends'"
class="mr-auto"
/>
<hr /> <hr />
<div class="user-rel-user-search"> <div class="user-rel-user-search">
<BaseToolbar> <BaseToolbar>
@ -13,18 +16,22 @@
<div class="search-area mb-4"> <div class="search-area mb-4">
<input <input
v-model="searchQuery" v-model="searchQuery"
type="text"
placeholder="Search Users"
class="search-input mr-3" class="search-input mr-3"
placeholder="Search Users"
type="text"
/> />
<BaseButton <BaseButton
:label="t('Search')" :label="t('Search')"
@click="executeSearch"
class="search-button" class="search-button"
icon="search" icon="search"
type="button"/> type="button"
@click="executeSearch"
/>
</div> </div>
<div v-if="!loadingResults" class="results-list"> <div
v-if="!loadingResults"
class="results-list"
>
<div <div
v-for="(user, index) in foundUsers" v-for="(user, index) in foundUsers"
:key="index" :key="index"
@ -32,24 +39,31 @@
> >
<div class="user-avatar"> <div class="user-avatar">
<img <img
:src="user.illustrationUrl || defaultAvatar"
:alt="`${user.username}'s avatar`" :alt="`${user.username}'s avatar`"
:src="user.illustrationUrl || defaultAvatar"
class="avatar-image" class="avatar-image"
/> />
</div> </div>
<div class="user-details"> <div class="user-details">
<div class="username">{{ user.username }}</div> <div class="username">{{ user.username }}</div>
<div class="user-actions"> <div class="user-actions">
<button class="action-button invite-button" @click="addFriend(user)"> <button
class="action-button invite-button"
@click="addFriend(user)"
>
Send invitation Send invitation
</button> </button>
<a <a
:href="`/main/inc/ajax/user_manager.ajax.php?a=get_user_popup&user_id=${user.id}`"
class="action-button message-button ajax" class="action-button message-button ajax"
data-title="Send message" data-title="Send message"
title="Send message" title="Send message"
:href="`/main/inc/ajax/user_manager.ajax.php?a=get_user_popup&user_id=${user.id}`"
> >
<i class="fa fa-envelope" aria-hidden="true"></i> Send message <i
aria-hidden="true"
class="fa fa-envelope"
></i>
Send message
</a> </a>
</div> </div>
</div> </div>
@ -58,15 +72,15 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { onMounted, ref } from "vue"
import { useRouter } from 'vue-router' import { useRouter } from "vue-router"
import { useStore } from 'vuex' import { useStore } from "vuex"
import { useI18n } from 'vue-i18n' import { useI18n } from "vue-i18n"
import { useNotification } from '../../composables/notification' import { useNotification } from "../../composables/notification"
import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue" import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue" import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { ENTRYPOINT } from '../../config/entrypoint' import userRelUserService from "../../services/userRelUserService"
import axios from "axios" import userService from "../../services/userService"
const store = useStore() const store = useStore()
const router = useRouter() const router = useRouter()
@ -77,28 +91,28 @@ const loadingResults = ref(false)
const user = store.getters["security/getUser"] const user = store.getters["security/getUser"]
const foundUsers = ref([]) const foundUsers = ref([])
const friendsList = ref([]) const friendsList = ref([])
const searchQuery = ref('') const searchQuery = ref("")
const executeSearch = async () => { const executeSearch = async () => {
if (searchQuery.value.trim().length > 0) { if (searchQuery.value.trim().length > 0) {
await router.push({ name: 'UserRelUserSearch', query: { search: searchQuery.value } }) await router.push({ name: "UserRelUserSearch", query: { search: searchQuery.value } })
await fetchFriendsList() await fetchFriendsList()
await asyncFind(searchQuery.value) await asyncFind(searchQuery.value)
} else { } else {
showErrorNotification(t('Please enter a search query')) showErrorNotification(t("Please enter a search query"))
} }
} }
const isFriend = (user) => { const isFriend = (user) => {
return friendsList.value.some(friend => friend.id === user.id) return friendsList.value.some((friend) => friend.id === user.id)
} }
async function fetchFriendsList() { async function fetchFriendsList() {
try { try {
const response = await axios.get(`${ENTRYPOINT}user_rel_users`, { const friendshipList = await userRelUserService.getFriendList(user["@id"])
params: { user: user.id, relationType: [3, 10] }
}) friendsList.value = friendshipList.map((friendship) => friendship.friend.id).concat(user.id)
friendsList.value = response.data['hydra:member'].map(friendship => friendship.friend.id).concat(user.id)
} catch (error) { } catch (error) {
showErrorNotification(t('Error fetching friends list')) showErrorNotification(t("Error fetching friends list"))
console.error('Error fetching friends list:', error) console.error("Error fetching friends list:", error)
} }
} }
@ -106,34 +120,31 @@ const asyncFind = async (query) => {
if (query.length < 3) return if (query.length < 3) return
isLoadingSelect.value = true isLoadingSelect.value = true
try { try {
const { data } = await axios.get(`${ENTRYPOINT}users`, { params: { username: query } }) const { items } = await userService.findByUsername(query)
foundUsers.value = data['hydra:member'].filter(foundUser => !friendsList.value.includes(foundUser.id))
foundUsers.value = items.filter((foundUser) => !friendsList.value.includes(foundUser.id))
} catch (error) { } catch (error) {
showErrorNotification(t('Error fetching users')) showErrorNotification(t("Error fetching users"))
} finally { } finally {
isLoadingSelect.value = false isLoadingSelect.value = false
} }
} }
const addFriend = async (friend) => { const addFriend = async (friend) => {
try { try {
await axios.post(`${ENTRYPOINT}user_rel_users`, { await userRelUserService.sendFriendRequest(user["@id"], friend["@id"])
user: user['@id'], showSuccessNotification(t("Friend request sent successfully"))
friend: friend['@id'],
relationType: 10,
})
showSuccessNotification(t('Friend request sent successfully'))
await fetchFriendsList() await fetchFriendsList()
const searchQuery = router.currentRoute.value.query.search const searchQuery = router.currentRoute.value.query.search
if (searchQuery) { if (searchQuery) {
await asyncFind(searchQuery) await asyncFind(searchQuery)
} }
} catch (error) { } catch (error) {
showErrorNotification(t('Failed to send friend request')) showErrorNotification(t("Failed to send friend request"))
console.error('Error adding friend:', error) console.error("Error adding friend:", error)
} }
} }
const goToBack = () => { const goToBack = () => {
router.push({ name: 'UserRelUserList' }) router.push({ name: "UserRelUserList" })
} }
onMounted(async () => { onMounted(async () => {
const urlSearchQuery = router.currentRoute.value.query.search const urlSearchQuery = router.currentRoute.value.query.search

@ -6,46 +6,18 @@ import InputText from "primevue/inputtext"
import Password from "primevue/password" import Password from "primevue/password"
import Button from "primevue/button" import Button from "primevue/button"
import InputSwitch from "primevue/inputswitch" import InputSwitch from "primevue/inputswitch"
import { useRoute, useRouter } from "vue-router" import { useLogin } from "../../../assets/vue/composables/auth/login"
import { useSecurityStore } from "../../../assets/vue/store/securityStore"
const route = useRoute()
const router = useRouter()
const store = useStore() const store = useStore()
const { t } = useI18n() const { t } = useI18n()
const securityStore = useSecurityStore()
const { performLogin } = useLogin()
const login = ref("") const login = ref("")
const password = ref("") const password = ref("")
const remember = ref(false) const remember = ref(false)
const isLoading = computed(() => store.getters["security/isLoading"]) const isLoading = computed(() => store.getters["security/isLoading"])
async function performLogin() {
let payload = {
login: login.value,
password: password.value,
_remember_me: remember.value,
}
let redirect = route.query.redirect
await store.dispatch("security/login", payload)
if (!store.getters["security/hasError"]) {
securityStore.user = store.state["security/user"]
const responseData = await store.dispatch("security/login", payload)
if (typeof redirect !== "undefined") {
await router.push({ path: redirect.toString() })
} else {
if (responseData.load_terms) {
window.location.href = responseData.redirect
} else {
window.location.href = "/home"
}
}
}
}
</script> </script>
<template> <template>

Loading…
Cancel
Save