index.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <script setup lang="ts">
  2. import { defineAsyncComponent, ref, onMounted } from "vue";
  3. import { useRoute } from "vue-router";
  4. import { useModalsStore } from "@/stores/modals";
  5. import { useUserAuthStore } from "@/stores/userAuth";
  6. import { TableColumn, TableFilter, TableEvents } from "@/types/advancedTable";
  7. const AdvancedTable = defineAsyncComponent(
  8. () => import("@/components/AdvancedTable.vue")
  9. );
  10. const ProfilePicture = defineAsyncComponent(
  11. () => import("@/components/ProfilePicture.vue")
  12. );
  13. const route = useRoute();
  14. const columnDefault = ref(<TableColumn>{
  15. sortable: true,
  16. hidable: true,
  17. defaultVisibility: "shown",
  18. draggable: true,
  19. resizable: true,
  20. minWidth: 150,
  21. maxWidth: 600
  22. });
  23. const columns = ref(<TableColumn[]>[
  24. {
  25. name: "options",
  26. displayName: "Options",
  27. properties: ["_id", "username"],
  28. sortable: false,
  29. hidable: false,
  30. resizable: false,
  31. minWidth: 85,
  32. defaultWidth: 85
  33. },
  34. {
  35. name: "profilePicture",
  36. displayName: "Image",
  37. properties: ["avatar", "name", "username"],
  38. sortable: false,
  39. resizable: false,
  40. minWidth: 71,
  41. defaultWidth: 71
  42. },
  43. {
  44. name: "name",
  45. displayName: "Display Name",
  46. properties: ["name"],
  47. sortProperty: "name"
  48. },
  49. {
  50. name: "username",
  51. displayName: "Username",
  52. properties: ["username"],
  53. sortProperty: "username"
  54. },
  55. {
  56. name: "_id",
  57. displayName: "User ID",
  58. properties: ["_id"],
  59. sortProperty: "_id",
  60. minWidth: 230,
  61. defaultWidth: 230
  62. },
  63. {
  64. name: "githubId",
  65. displayName: "GitHub ID",
  66. properties: ["services.github.id"],
  67. sortProperty: "services.github.id",
  68. minWidth: 115,
  69. defaultWidth: 115
  70. },
  71. {
  72. name: "hasPassword",
  73. displayName: "Has Password",
  74. properties: ["hasPassword"],
  75. sortProperty: "hasPassword"
  76. },
  77. {
  78. name: "role",
  79. displayName: "Role",
  80. properties: ["role"],
  81. sortProperty: "role",
  82. minWidth: 90,
  83. defaultWidth: 90
  84. },
  85. {
  86. name: "emailAddress",
  87. displayName: "Email Address",
  88. properties: ["email.address"],
  89. sortProperty: "email.address",
  90. defaultVisibility: "hidden"
  91. },
  92. {
  93. name: "emailVerified",
  94. displayName: "Email Verified",
  95. properties: ["email.verified"],
  96. sortProperty: "email.verified",
  97. defaultVisibility: "hidden",
  98. minWidth: 140,
  99. defaultWidth: 140
  100. },
  101. {
  102. name: "songsRequested",
  103. displayName: "Songs Requested",
  104. properties: ["statistics.songsRequested"],
  105. sortProperty: "statistics.songsRequested",
  106. minWidth: 170,
  107. defaultWidth: 170
  108. }
  109. ]);
  110. const filters = ref(<TableFilter[]>[
  111. {
  112. name: "_id",
  113. displayName: "User ID",
  114. property: "_id",
  115. filterTypes: ["exact"],
  116. defaultFilterType: "exact"
  117. },
  118. {
  119. name: "name",
  120. displayName: "Display Name",
  121. property: "name",
  122. filterTypes: ["contains", "exact", "regex"],
  123. defaultFilterType: "contains"
  124. },
  125. {
  126. name: "username",
  127. displayName: "Username",
  128. property: "username",
  129. filterTypes: ["contains", "exact", "regex"],
  130. defaultFilterType: "contains"
  131. },
  132. {
  133. name: "githubId",
  134. displayName: "GitHub ID",
  135. property: "services.github.id",
  136. filterTypes: ["contains", "exact", "regex"],
  137. defaultFilterType: "contains"
  138. },
  139. {
  140. name: "hasPassword",
  141. displayName: "Has Password",
  142. property: "hasPassword",
  143. filterTypes: ["boolean"],
  144. defaultFilterType: "boolean"
  145. },
  146. {
  147. name: "role",
  148. displayName: "Role",
  149. property: "role",
  150. filterTypes: ["exact"],
  151. defaultFilterType: "exact",
  152. dropdown: [
  153. ["admin", "Admin"],
  154. ["default", "Default"]
  155. ]
  156. },
  157. {
  158. name: "emailAddress",
  159. displayName: "Email Address",
  160. property: "email.address",
  161. filterTypes: ["contains", "exact", "regex"],
  162. defaultFilterType: "contains"
  163. },
  164. {
  165. name: "emailVerified",
  166. displayName: "Email Verified",
  167. property: "email.verified",
  168. filterTypes: ["boolean"],
  169. defaultFilterType: "boolean"
  170. },
  171. {
  172. name: "songsRequested",
  173. displayName: "Songs Requested",
  174. property: "statistics.songsRequested",
  175. filterTypes: [
  176. "numberLesserEqual",
  177. "numberLesser",
  178. "numberGreater",
  179. "numberGreaterEqual",
  180. "numberEquals"
  181. ],
  182. defaultFilterType: "numberLesser"
  183. }
  184. ]);
  185. const events = ref(<TableEvents>{
  186. adminRoom: "users",
  187. updated: {
  188. event: "admin.user.updated",
  189. id: "user._id",
  190. item: "user"
  191. },
  192. removed: {
  193. event: "user.removed",
  194. id: "userId"
  195. }
  196. });
  197. const { openModal } = useModalsStore();
  198. const { hasPermission } = useUserAuthStore();
  199. const edit = userId => {
  200. openModal({ modal: "editUser", data: { userId } });
  201. };
  202. onMounted(() => {
  203. if (route.query.userId) edit(route.query.userId);
  204. });
  205. </script>
  206. <template>
  207. <div class="admin-tab container">
  208. <page-metadata title="Admin | Users" />
  209. <div class="card tab-info">
  210. <div class="info-row">
  211. <h1>Users</h1>
  212. <p>Manage users</p>
  213. </div>
  214. </div>
  215. <advanced-table
  216. :column-default="columnDefault"
  217. :columns="columns"
  218. :filters="filters"
  219. data-action="users.getData"
  220. name="admin-users"
  221. :max-width="1200"
  222. :events="events"
  223. >
  224. <template #column-options="slotProps">
  225. <div class="row-options">
  226. <button
  227. v-if="hasPermission('users.update')"
  228. class="button is-primary icon-with-button material-icons"
  229. @click="edit(slotProps.item._id)"
  230. :disabled="slotProps.item.removed"
  231. content="Edit User"
  232. v-tippy
  233. >
  234. edit
  235. </button>
  236. <router-link
  237. :to="{ path: `/u/${slotProps.item.username}` }"
  238. target="_blank"
  239. class="button is-primary icon-with-button material-icons"
  240. :disabled="slotProps.item.removed"
  241. content="View Profile"
  242. v-tippy
  243. >
  244. person
  245. </router-link>
  246. </div>
  247. </template>
  248. <template #column-profilePicture="slotProps">
  249. <profile-picture
  250. :avatar="slotProps.item.avatar"
  251. :name="
  252. slotProps.item.name
  253. ? slotProps.item.name
  254. : slotProps.item.username
  255. "
  256. />
  257. </template>
  258. <template #column-name="slotProps">
  259. <span :title="slotProps.item.name">{{
  260. slotProps.item.name
  261. }}</span>
  262. </template>
  263. <template #column-username="slotProps">
  264. <span :title="slotProps.item.username">{{
  265. slotProps.item.username
  266. }}</span>
  267. </template>
  268. <template #column-_id="slotProps">
  269. <span :title="slotProps.item._id">{{
  270. slotProps.item._id
  271. }}</span>
  272. </template>
  273. <template #column-githubId="slotProps">
  274. <span
  275. v-if="slotProps.item.services.github"
  276. :title="slotProps.item.services.github.id"
  277. >{{ slotProps.item.services.github.id }}</span
  278. >
  279. </template>
  280. <template #column-hasPassword="slotProps">
  281. <span :title="slotProps.item.hasPassword">{{
  282. slotProps.item.hasPassword
  283. }}</span>
  284. </template>
  285. <template #column-role="slotProps">
  286. <span :title="slotProps.item.role">{{
  287. slotProps.item.role
  288. }}</span>
  289. </template>
  290. <template #column-emailAddress="slotProps">
  291. <span :title="slotProps.item.email.address">{{
  292. slotProps.item.email.address
  293. }}</span>
  294. </template>
  295. <template #column-emailVerified="slotProps">
  296. <span :title="slotProps.item.email.verified">{{
  297. slotProps.item.email.verified
  298. }}</span>
  299. </template>
  300. <template #column-songsRequested="slotProps">
  301. <span :title="slotProps.item.statistics.songsRequested">{{
  302. slotProps.item.statistics.songsRequested
  303. }}</span>
  304. </template>
  305. </advanced-table>
  306. </div>
  307. </template>
  308. <style lang="less" scoped>
  309. .profile-picture {
  310. max-width: 50px !important;
  311. max-height: 50px !important;
  312. }
  313. :deep(.profile-picture.using-initials span) {
  314. font-size: 20px !important; // 2/5th of .profile-picture height/width
  315. }
  316. </style>